今回は、Pythonのunittestモジュールと、その一般的な使用例について説明します。
しかしその前に、そもそもなぜこのモジュールが必要なのかを理解しておきましょう。
なぜ unittest モジュールを使う必要があるのでしょうか?
大規模なコードベースを扱う場合、アプリケーションの開発はしばしば2つのフェーズに分類されます。
- 開発フェーズ
 - テストフェーズ
 
フェーズ1は開発フェーズで、コアとなるアイデアを骨太のアプリケーションに作り上げます。
しかし、実際に常用する場合は、これだけでは不十分です。
見落としている状況もあったかもしれませんし、実際にプログラムが予期せぬ動作をすることもあります。
このようなエラーを最小限に抑えるために、テストフェーズと呼ばれる別のフェーズがあります。
これは、アプリケーションのさまざまな可能性のあるシナリオをテストし、正しく動作しているかどうかをチェックすることを目的としています。
このフェーズのためのフレームワークが確立されていない場合、すべてのシナリオを手作業で検証しなければならないことがよくありますが、これは面倒な作業です。
開発者の手間を減らすために、Pythonの unittest モジュールを使って、自動テストを行うことで、まさにこの問題を解決することができるのです。
テストの種類
アプリケーションの場合、2種類のテストがあります。
- 統合テスト
 - ユニットテスト
 
統合テストは、アプリケーションのモジュールがお互いに正しく動作しているかどうかをチェックするテストです。
ユニットテストは、アプリケーションの小さなコンポーネントをチェックするテストです。
統合テストとユニットテストの両方を書くことができますが、統合テストはあなたのアプリケーションに大きく依存し、複数のユニットテストを組み合わせることができます。
それでは、このモジュールをどのように使うか見ていきましょう。
Python unittest モジュール
このモジュールは Python 3+ のインストール時に組み込まれているため、pip を使ってインストールする必要はありません。
このモジュールをインポートするには、次のようにタイプします。
import unittest
 | 
Python unittestメソッド
このモジュールには、unittestを実行するためのいくつかのメソッドがあります。
最も一般的なものは以下の表のとおりです。
| メソッド | アサーションチェック | 
| assertEqual(a,b) | a == b | 
| assertNotEqual(a,b) | a != b | 
| assertTrue(x) | bool(x)がTrueである|。 | 
| assertFalse(x) | bool(x)はFalse(偽)である | 
| assertIs(a,b) | aはbである|? | 
| assertIsNot(a, b) | a は b ではない|。 | 
| assertIsNone(x) | x は None | 
| assertIsNotNone(x) | x は None ではない | 
| assertIn(a, b) | aがbにあるとき|。 | 
| assertNotIn(a, b) | a が b にない。 | 
| assertIsInstance(a, b) | isinstance(a, b)です。 | 
| assertNotIsInstance(a, b) | isinstance(a, b)ではない|。 | 
ユニットテストの書き方
テストを適用するためのプログラムが必要です。
では、書いてみましょう。
単純にリストの要素の合計を検証しようとするプログラムを書きます。
そのためのユニテストプログラムを書きます。
さて、個々のテストケースを書くには、 unittest.TestCase クラスを継承し、いくつかのメソッドを使ってオーバーライドする必要があります。
ここでは、私のクラスを MyTestClass と呼ぶことにします。
import unittest
def list_sum(my_list):
    # Sums the elements of the list
    return sum(my_list)
class MyTestClass(unittest.TestCase):
    def test_list(self):
        # Checks if the sum of the below list is as expected
        my_list = [1, 2, 3, 4, 5]
        self.assertEqual(list_sum(my_list), 15, "Should be 15")
    def test_string(self):
        # Checks if the string is 'Hello from Python'
        my_str = 'Hi'
        self.assertEqual(my_str, 'Hello from Python', "Should be 'Hello from Python'")
if __name__ == '__main__':
    # Main module
    unittest.main()
 | 
NOTE: テストメソッドを書くには、メソッド名の前に test_ を付けなければなりません。
つまり、どんなテストメソッドも test_xyz() という形式でなければなりません。
私は、リストの要素の合計が 15 になるかどうかをチェックするメソッド test_list() と、同様に与えられた文字列をチェックする別のメソッドを書いています。
unittestの assertEqual() メソッドを使っていますが、これはunittestを実行してこのアサーションが成立するかどうかをチェックするものです。
では、このファイルをPythonで実行してみましょう。
user@Python $ python my_test.py .F======================================================================FAIL: test_string (__main__.MyTestClass)----------------------------------------------------------------------Traceback (most recent call last):  File "my_test.py", line 16, in test_string
    self.assertEqual(my_str, 'Hello from Python', "Should be 'Hello from Python'")
AssertionError: 'Hi' != 'Hello from Python'
- Hi+ Hello from Python : Should be 'Hello from Python'
----------------------------------------------------------------------Ran 2 tests in 0.000s
FAILED (failures=1) | 
見ての通り、最初のテストは成功しましたが、2番目のテストは文字列が一致しないので失敗しました。
これで、最初のユニテストメソッドを書くことができました。
アプリケーションでユニットテストを実行する
アプリケーション全体を unittest ファイルの中に書くことはないでしょうから、別のプログラムに対して unittest を実行してみましょう!
簡単なアプリケーションプログラムを書いて、それに対してユニットテストを実行してみましょう。
今回は、生徒の名前と点数を保存するための非常に簡単なデータベースとして動作するプログラムを書きます。
次のコードで参照するので、以下のファイルを test_example.py という名前で保存してください。
class MyClass:
    # Database of {'Name': 'Marks'} dict pairs
    db = dict()
    num_students = 0
    def add_db(self, name, marks):
        self.db[name] = marks
        self.num_students += 1
    def rm_db(self, name):
        # Removes key value pair corresponding
        # to student name
        if name in self.db:
            del self.db[name]
        else:
            return f'Student with Name:{name} not in Database'
    def get_marks(self, name):
        if name in self.db:
            return self.db[name]
        else:
            return f'Student with Name:{name} not in Database'
if __name__ == '__main__':
    my_class = MyClass()
    my_class.add_db('John', 47)
    my_class.add_db('Mary', 34)
    print(my_class.get_marks('John'))
 | 
Unit Testsの推奨実行方法
テスト用のモジュールはコアアプリケーションから分離しておくのが一般的なやり方です。
そこで、テストフェーズの間だけ unittest モジュールをインポートすることにします。
Pythonでは、 -m MODULE_NAME オプションを指定することで、これを行うことができます。
ですから、私たちのコマンドは次のようになります。
python -m unittest -v my_test.py
 | 
v` オプションを使うと、有用なメッセージをすべて表示することができます。
これで、アプリケーション上で import unittest を書く必要がなくなりました。
ユニットテストを実行するためには、先程と同じようにプログラムのテストファイルを書かなければなりません。
また、先ほど保存した test_example.py ファイルを参照して、先ほど作成した MyClass をインポートすることにします。
import unittest
from test_example import MyClass
import random
class MyTest(unittest.TestCase):
    # Init the MyClass class
    my_class = MyClass()
    # Database to verify test cases
    database = dict()
    def test_case_1(self):
        print("
)
        name = 'John Doe'
        marks = 50
        self.database[name] = marks
        self.my_class.add_db(name, marks)
        self.assertEqual(self.database, self.my_class.db)
        print(self.database)
        print("
)
    def test_case_2(self):
        print("
)
        for i in range(5):
            name = ''
            for j in range(6):
                name += chr(random.randint(97, 97+25))
            marks = random.randint(0, 100)
            self.database[name] = marks
            # Insert to MyClass Database
            self.my_class.add_db(name, marks)
        # Now check if both databases have the same key:value pairs
        self.assertEqual(self.database, self.my_class.db)
        print(self.database)
        print("
)
if __name__ == '__main__':
    # Run the main unittest code
    unittest.main()
 | 
さて、テストを別々に書いたので、それがうまくいくかどうか検証してみましょう。
python -m unittest run_tests.py
 | 

両方のテストがパスしたので、これは動作します!
最終的なテストデータベースは Test1 と Test2 の両方のレコードを含んでいるので、プログラムに応じてテストの仕組みを操作することが可能です。
この記事もチェック:Pythonのpyfigletモジュールを使ってASCIIアートを作成する方法
まとめ
Pythonの unittest モジュールを使って、テスト段階でサニティチェックを行う方法について理解していただけたでしょうか。
もし何か質問があれば、下のコメント欄に書いてください。