Skip to content

unittestモジュールの使い方(基本)

はじめに

pythonで標準モジュールとして使えるunittestモジュールの基本的な使い方を順を追って記していきます。

テスト環境

テストを実施する際の環境を以下のように準備します。

  • 動作確認 python3.7
  • テスト対象のスクリプト --- honban_file.py
  • テストを実行する為のスクリプト --- unit_test.py

テスト実施の流れ

以下の流れでunittestモジュールの使い方を見ていきます。

  • テスト対象のスクリプト の内容を確認
  • テストを実行するスクリプト の内容を確認
  • テストの実行結果 を確認

テストコードの作成・実行1

最初にシンプルな内容のテストを実施します。

テスト対象のスクリプト

テストを行う対象のファイル(honban_file.py)が以下のような内容だとします。

honban_file.py

# coding: utf-8

class Functions : 
  def plus(self, a, b):
      return a + b
  • honban_file.pyは、Functionsクラスに足し算を行うplusメソッドが記載されています。
  • plusメソッドは、引数を2つ受け取り、足し算を行います。
  • このplusメソッドが正しく動作しているかを確認するテストを行います。

テストを実行するスクリプト

次に、ユニットテストのコードを以下のように作成します。

unit_test.py


import unittest
import honban_file

honban = honban_file.Functions()

class TestKeisanNormal(unittest.TestCase):

    def test_tashizan_normal(self):
        value1 = 2
        value2 = 6
        expected = 8
        result = honban.plus(value1, value2)
        self.assertEqual(expected, result)

if __name__ == "__main__":
    # コマンドラインで実行する場合は以下。
    # unittest.main()

    # IPython あるいは Jupyter で実行する場合は以下。
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  • import honban_file でテスト対象のスクリプトを呼び出します。
  • test_tashizan_normalメソッドで実際のテスト内容を書きます。
  • assertEqualで、テスト対象の結果と期待する結果が一致しているかをチェックしています。
  • unittestをJupyterなどで試す場合は、unittest.main の箇所に少々工夫が必要になります。
    ( 詳細: https://medium.com/@vladbezden/using-python-unittest-in-ipython-or-jupyter-732448724e31 )

テストの実行結果

以下はテストを実行し、テストが期待通りに終了した例です。

実行コマンド
# python unit_test.py

実行結果

.
----------------------------------------------------------------------
Ran 1 test in 0.005s

OK

テストに失敗すると以下のような結果となります。
失敗したテストが、Fと表示されます。

F
======================================================================
FAIL: test_tashizan_normal (__main__.TestKeisanNormal)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-1-f34dc9daa684>", line 13, in test_tashizan_normal
    self.assertEqual(expected, result)
AssertionError: 9 != 8

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (failures=1)

なお、テスト・コード自体が間違っていると以下のようにEと表示されます。

E
======================================================================
ERROR: test_tashizan_normal (__main__.TestKeisanNormal)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython-input-1-78bd8c44601e>", line 12, in test_tashizan_normal
    result = honban.plus(value1)
TypeError: plus() missing 1 required positional argument: 'b'

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (errors=1)

テストコードの作成・実行2

先ほどは、テスト対象のメソッドが引数を2つ受け取る形でした。今回は、クラス変数が存在する場合を見てみます。

テスト対象のスクリプト

今度は、本番ファイルを以下のように書き換えます。

honban_file.py

# coding: utf-8

class Functions() : 

  def __init__(self, a, b) :
    self.a = a
    self.b = b

  def minus(self,):
      a = self.a
      b = self.b
      return a - b

テストを実行するスクリプト


class TestKeisan2(unittest.TestCase):

    def test_hikizan(self):
        honban = honban_file.Functions(3, 2)
        expected = 1
        result = honban.minus()
        self.assertEqual(expected, result)

if __name__ == "__main__":
    # コマンドラインで実行する場合は以下。
    # unittest.main()

    # IPython あるいは Jupyter で実行する場合は以下。
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

実行結果

クラス変数に3と2を与えて、計算結果は想定通りの1となりました。テスト結果の結果は成功となりました。

.
----------------------------------------------------------------------
Ran 1 test in 0.007s

OK

テストコードの作成・実行3

今度は、本番ファイルのメソッドがリターンする値を強制的に変更してテストしてみます。

テスト対象のスクリプト

テスト対象のスクリプト「honban_file.py」の内容は、minusメソッドが0を返すことになっています。

honban_file.py

# coding: utf-8

class Functions : 
  def minus(self,):
      a = 3
      b = 3
      return a - b

テスト・スクリプト

本番スクリプトのminusメソッドがリターンする数字を強制的に変更していきます。上記のminusメソッドでは、本来であれば、0がリターンされるはずですが、1がリターンされるようにテストスクリプトで上書きします。

honban_file.py

import unittest
import honban_file

honban = honban_file.Functions()

class TestKeisan3(unittest.TestCase):

    def test_hikizan(self):
        def mock_func(*args):
            return 1
        honban.minus = mock_func
        result = honban.minus()
        expected = 1
        self.assertEqual(expected, result)

if __name__ == "__main__":
    # コマンドラインで実行する場合は以下。
    # unittest.main()

    # IPython あるいは Jupyter で実行する場合は以下。
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  • テストを行うメソッド「def test_hikizan(self)」内に、更に関数「def mock_func(*args):」を作ります。
  • 本来は、0をリターンするminus()メソッドに対して、mock_func(*args)を代入します。
  • 結果として、本来は0をリターンするminus()メソッドが1をリターンするようになります。

テストの実行結果

.
----------------------------------------------------------------------
Ran 1 test in 0.008s

OK

テストコードの作成・実行4 組み込み関数

今回は、組み込み関数の出力内容を変更するテスト方法を実行します。

テスト対象のスクリプト

honban_file.py

# coding: utf-8

class Functions : 

  def output_str(self,):
      number = 1
      string = str(number)
      return string
  • 数字の型を、組み込み関数のstr()を使って文字の型に変更しています。
  • 通常であれば、output_strメソッドは、文字型の1を返すはずですが、テストを実行するスクリプトで組み込み関数のstr()が、数字の2をリターンするように変更してみます。

テストを実行するスクリプト

honban_file.py

# coding: utf-8

import unittest
import honban_file

honban = honban_file.Functions()

class TestCasedes(unittest.TestCase):

    def test_str_mock(self):
        def new_str(*args):
          return 2
        __builtins__.str  = new_str
        result = honban.output_str()
        expected = 2 
        print(result)  # 2 が出力される。
        self.assertEqual(expected, result)

if __name__ == "__main__":
    # コマンドラインで実行する場合は以下。
    unittest.main()
  • テストを行うメソッド「def test_str_mock(self):」の中に新たな関数「def new_str(self):」を作成しています。
  • 組み込み関数str()の内容を、new_str()関数で上書きしています。
  • 本コードは、Jupyterでは動きませんでしたので、試してみる場合は、プロンプトなどから実行してみてください。

テストの実行結果

以下のように成功します。想定通りに2も出力されています。

2
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK

補足 1

上記で組み込み関数を上書きしていますが、組み込み関数を上書きする場合は、組み込み関数の中身を下記のように元に戻してあげるとよいでしょう。

class TestCasedes(unittest.TestCase):

    def test_str_mock(self):
        def new_str(*args):
          return 2
        original_str  = __builtins__.str # 元の組み込み関数str()の内容を保存します。
        __builtins__.str  = new_str
        result = honban.output_str()
        __builtins__.str  = original_str # 元の組み込み関数str()の内容に戻します。

        expected = 2 
        print(result)
        self.assertEqual(expected, result)

補足 2

今回は、組み込み関数str()の返り値を変更するために、「builtins.str」という形で呼び出しました。
しかし、本番ファイル(honban_file.py)のstr()関数を変更する場合は、以下のようにhonban_file.str()という形でも呼び出せます。

import unittest
import honban_file

honban = honban_file.Functions()

class TestCasedes(unittest.TestCase):

    def test_str_mock(self):
        def new_str(*args):
          return 2
        honban_file.str  = new_str # ここでのstr()関数の呼び出し方が違います。
        result = honban.output_str()
        expected = 2 
        print(result)  # 2 が出力される。
        self.assertEqual(expected, result)

if __name__ == "__main__":
    # コマンドラインで実行する場合は以下。
    unittest.main()

テストコードの作成・実行5 ファイル読み込み

前回は、組み込み関数str()の返り値を変更してみました。今回は、実践的なテストを想定してファイルの内容を読み取るopen()関数の返り値を変更していきます。
テスト対象の本番ファイル内で、ファイルの読み取りを行っている場合に、その読み取るファイルの内容を変更してみます。

テスト対象のスクリプト

honban_file.py

# coding: utf-8

class Functions : 

  def read_file(self,):
      path = "./honban.txt"
      with open(path) as f:
        line = f.read()
      return line

honban.txt

honban-kakikomi
  • honban_file内で、テキストファイル(honban.txt)を組み込み関数のopenで開いています。
  • テキストファイル(honban.txt)の内容を読み込むと、「honban-kakikomi」と書かれています。
  • 最終的に、「honban-kakikomi」をread_file()のメソッドがリターンします。

テストを実行するスクリプト

  • honban_file内で、テキストファイル(honban.txt)を組み込み関数のopenで開いた際に、どのような内容が読み込まれるかをテストスクリプトで指定します。
  • 比較のために、普通にテキストファイル(honban.txt)を読み込んで中身をテストする方法と、読み込む中身を変更する方法の両方を記載していきます。

honban_file.py

import unittest
from unittest.mock import mock_open
import honban_file

honban = honban_file.Functions()

class Test5(unittest.TestCase):

    def test_read_file_honban(self):
        result = honban.read_file()
        expected = "honban-kakikomi"
        print(result)
        self.assertEqual(expected, result)

    def test_read_file_mock(self):
        changed_open = mock_open(read_data='test-kakikomi')
        __builtins__.open  = changed_open
        result = honban.read_file()
        expected = "test-kakikomi"
        print(result)
        self.assertEqual(expected, result)

if __name__ == "__main__":
    # コマンドラインで実行する場合は以下。
    # unittest.main()

    # IPython あるいは Jupyter で実行する場合は以下。
    unittest.main(argv=['first-arg-is-ignored'], exit=False)
  • 「def test_read_file_honban(self):」が普通にテキストファイル(honban.txt)を読み込んでテストしています。「def test_read_file_mock(self):」で、組み込み関数(builtins.open) の内容を変更しています。
  • 組み込み関数(builtins.open) の内容を、「mock_open」関数で変更しています。(mock_open関数は、「from unittest.mock import mock_open」でimportします。)
  • 「mock_open」関数実行時の「read_data='test-kakikomi'」で、open関数を実行した際にリターンされる内容を指定しています。

テストの実行結果

..
honban-desu
test-desu

----------------------------------------------------------------------
Ran 2 tests in 0.018s

OK

補足

デコレータ(@patch)を使って以下のように書くこともできます。

honban_file.py

import unittest
from unittest.mock import mock_open
from unittest.mock import patch
import honban_file

honban = honban_file.Functions()

class Test5(unittest.TestCase):

    def test_read_file_honban(self):
        result = honban.read_file()
        expected = "honban-kakikomi"
        self.assertEqual(expected, result)

    @patch('honban_file.open')
    def test_read_file_mock(self, MockOpen):
        mock_open(mock=MockOpen, read_data='test-kakikomi')
        result = honban.read_file()
        expected = "test-kakikomi"
        self.assertEqual(expected, result)

if __name__ == "__main__":
    # コマンドラインで実行する場合は以下。
    # unittest.main()

    # IPython あるいは Jupyter で実行する場合は以下。
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

補足

今回の例では、テストとして期待値と実際の値が同じかどうか(assertEqual(expected, result))をチェックしました。
その他のメソッドとして、主なものを下記に列記しておきます。

メソッド 確認内容 追加されたversion
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b 3.1
assertIsNot(a, b) a is not b 3.1
assertIsNone(x) x is None 3.1
assertIsNotNone(x) x is not None 3.1
assertIn(a, b) a in b 3.1
assertNotIn(a, b) a not in b 3.1
assertIsInstance(a, b) isinstance(a, b) 3.2
assertNotIsInstance(a, b) not isinstance(a, b) 3.2
assertGreater(a, b) a > b 3.1
assertGreaterEqual(a, b) a >= b 3.1
assertLess(a, b) a < b 3.1
assertLessEqual(a, b) a <= b 3.1

https://docs.python.org/3/library/unittest.html

参考文献

https://docs.python.org/ja/3.7/library/unittest.mock.html
https://qiita.com/amedama/items/5aff37c75c28b58386d2
https://qiita.com/FGtatsuro/items/eea263bc15ebfb9767cd

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA