python unittest 試験対策

unittestについて

unittestは、Python標準ライブラリに含まれるテストフレームワーク

unittestを使うと、テストケースをクラスとして定義し、テストメソッドを記述することができる テストメソッドは

  • test_で始まる名前

  • unittest.TestCaseのサブクラスに属する という条件を満たす必要がある

例えばsample.pyというファイルに以下のコードを記述したとする

def add(a: int, b: int) -> int:
   return a + b

from sanple import add
import unittest

以下は実際は別のスクリプトファイルで定義されているとする


class TestAdd(unittest.TestCase):
    def test_add(self):
        """正しく足し算ができるかテストする"""
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(3, 4), 7)

    def test_add_fail(self):
        """テストが失敗する例"""
        self.assertEqual(add(1, 2), 4)
        self.assertEqual(add(3, 4), 8)
       
## 上記のコードを実行するには、以下のコードを実行する
if __name__ == "__main__":
    unittest.main()

上記の場合、以下のような結果が表示される

.F
======================================================================
FAIL: test_add_fail (__main__.TestAdd.test_add_fail)
テストが失敗する例
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shirotabi/Dev/myModule/myPython/MyPythonModule/exam/howtounittest.py", line 34, in test_add_fail
    self.assertEqual(add(1, 2), 4)
AssertionError: 3 != 4

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=1)

通常、テストが成功した場合は.が表示され、失敗した場合はFが表示される また、テストが失敗した場合は、失敗したテストの内容が表示される 上記のテストで不便なのは、失敗を検知した時点でテストが中断されるため、すべてのテストを実行することができないこと この問題を解決するために、subtestという機能を使う

subtestについて

unittestには、subtestという機能がある subtestを使うと、複数のテストを1つのテストとしてまとめることができる これにより、テストの実行時間を短縮することができる また、テストの結果も1つにまとめられるため、テスト結果の確認がしやすくなる

以下は、subtestを使ったテストの例


class TestAdd(unittest.TestCase):
      
    def test_subtest(self):
        """subtestを使ったテストの例"""
        test_cases = [
            (1, 2, 3),
            (3, 4, 7),
            (5, 6, 11),
            # 以下のテストケースは失敗する
            (7, 8, 1),
            (9, 10, 2),
            (11, 12, 3),
        ]
        for a, b, expected in test_cases:
            with self.subTest(a=a, b=b, expected=expected):
                self.assertEqual(add(a, b), expected)
       
if __name__ == "__main__":
    unittest.main()

.F.
======================================================================
FAIL: test_add_fail (__main__.TestAdd.test_add_fail)
テストが失敗する例
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shirotabi/Dev/myModule/myPython/MyPythonModule/exam/howtounittest.py", line 88, in test_add_fail
    self.assertEqual(add(1, 2), 4)
AssertionError: 3 != 4

----------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (failures=1)
(pyenv) (base) shirotabi@shirotabinoMacBook-Pro exam % python howtounittest.py
FFF
======================================================================
FAIL: test_subtest (__main__.TestAdd.test_subtest) (a=7, b=8, expected=1)
subtestを使ったテストの例
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shirotabi/Dev/myModule/myPython/MyPythonModule/exam/howtounittest.py", line 95, in test_subtest
    self.assertEqual(add(a, b), expected)
AssertionError: 15 != 1

======================================================================
FAIL: test_subtest (__main__.TestAdd.test_subtest) (a=9, b=10, expected=2)
subtestを使ったテストの例
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shirotabi/Dev/myModule/myPython/MyPythonModule/exam/howtounittest.py", line 95, in test_subtest
    self.assertEqual(add(a, b), expected)
AssertionError: 19 != 2

======================================================================
FAIL: test_subtest (__main__.TestAdd.test_subtest) (a=11, b=12, expected=3)
subtestを使ったテストの例
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shirotabi/Dev/myModule/myPython/MyPythonModule/exam/howtounittest.py", line 95, in test_subtest
    self.assertEqual(add(a, b), expected)
AssertionError: 23 != 3

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=3)

このようにsubtestを使うと、複数のテストを1つのテストとしてまとめることができ, また失敗したテストがあっても、そこでテストが中断されずにすべてのテストが実行される

subtestを使うことで、テストの実行時間を短縮することができる

setUpとsetUpClass, tearDownとtearDownClassについて

unittestには、テストメソッドの前後に特定の処理を実行するためのメソッドが用意されている これらのメソッドを使うことで、テストメソッドの前後に共通の処理を実行することができる

試験で聞かれるであろう内容は実行順序について

それぞれのメソッドをすべて使用した場合の実行順序は以下の通り

  1. setUpClass

  2. setUp

  3. testメソッド

  4. tearDown

  5. tearDownClass

以下は、setUp, tearDown, setUpClass, tearDownClassを使ったテストの例


class Database:
   """簡易データベースを模したクラス"""
   def __init__(self):
       self.data: dict[str, str] = {}

   def connect(self):
       print("データベース接続")

   def close(self):
       print("データベース接続終了")

   def insert(self, key: str, value: str) -> None:
       self.data[key] = value

   def get(self, key: str) -> str | None:
       return self.data.get(key)

   def delete(self, key: str) -> None:
       if key in self.data:
           del self.data[key]

class TestDatabase(unittest.TestCase):
   @classmethod
   def setUpClass(cls):
       print("setUpClass: データベースの接続")
       cls.db = Database()
       cls.db.connect()
   
   def setUp(self):
       print("setUp: テストデータの準備")
       self.db.insert("user1", "Alice")
   
   def tearDown(self):
       print("tearDown: テストデータの削除")
       self.db.delete("user1")

   @classmethod
   def tearDownClass(cls):
       print("tearDownClass: データベース接続の終了")
       cls.db.close()

   def test_get_user(self):
       """データベースからの取得をテストする"""
       print("test_get_userが実行されました")
       user = self.db.get("user1")
       self.assertEqual(user, "Alice")

   def test_user_not_found(self):
       print("test_user_not_foundが実行されました")
       """存在しないユーザー取得のテスト"""
       user = self.db.get("user2")
       self.assertIsNone(user)

if __name__ == "__main__":
   unittest.main()


setUpClass: データベースの接続
データベース接続
setUp: テストデータの準備
test_get_userが実行されました
tearDown: テストデータの削除
.setUp: テストデータの準備
test_user_not_foundが実行されました
tearDown: テストデータの削除
.tearDownClass: データベース接続の終了
データベース接続終了

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

実行順を再度確認すると、以下の通り

  1. setUpClass

  2. setUp

  3. test_get_user

  4. tearDown

  5. setUp

  6. test_user_not_found

  7. tearDown

  8. tearDownClass

setUpは、各テストメソッドの前に実行される
tearDownは、各テストメソッドの後に実行される
なので、テストが2回あれば、setUpとtearDownも2回実行される

setUpClassは、テストクラスの最初に1度だけ実行される
tearDownClassは、テストクラスの最後に1度だけ実行される

このように、setUp, tearDown, setUpClass, tearDownClassを使うことで、テストメソッドの前後に共通の処理を実行することができる

コマンドラインからテストを実行する方法について

unittestを使ってテストを実行する場合、コマンドラインからテストを実行することができる

特定のオプションを使うことで、テストの結果や詳細を制御することも可能です。この記事では、基本的なテストの実行方法から便利なオプションまでを解説します。

基本の実行方法

まず、Pythonのunittestを使ってテストをコマンドラインから実行する最も基本的な方法を紹介します。

python -m unittest <テストファイル名>

例えば、テストファイルがtest_sample.pyである場合、次のように実行します。

python -m unittest test_sample.py

これにより、ファイル内の全テストが実行され、結果が出力されます。

すべてのテストファイルを一括実行する方法

ディレクトリ内に複数のテストファイルがある場合、全てのテストファイルを一括で実行することも可能です。テストファイルがtest_で始まるファイル名で保存されている場合、次のコマンドで一括実行できます。

python -m unittest discover

このコマンドは、カレントディレクトリ内のtest_*.py形式のファイルを自動的に探して実行します。特定のディレクトリを指定することもできます。

python -m unittest discover -s tests

-sオプションでディレクトリ(testsディレクトリ)を指定すると、そのディレクトリ内のテストファイルが実行されます。

便利なオプション

  1. テストの詳細表示(-vオプション)

テストを実行したときに、どのテストが実行されているのかを詳しく知りたい場合には、-v(verbose)オプションを使います。

python -m unittest -v test_sample.py

このオプションを使うと、各テストメソッドの名前とその結果が詳細に表示されます。

例:

test_get_user (test_sample.TestDatabase) ... ok
test_user_not_found (test_sample.TestDatabase) ... ok
  1. 特定のテストケースやメソッドのみを実行する

テストファイル全体ではなく、特定のクラスやメソッドのみを実行する場合、次のようにクラス名やメソッド名を指定します。

特定のテストクラスを実行する場合:

python -m unittest test_sample.TestDatabase

特定のテストメソッドを実行する場合:

python -m unittest test_sample.TestDatabase.test_get_user

これにより、狙ったテストだけを効率的に実行することができます。

  1. エラーが発生したテストだけを再実行する(-fオプション) 複数のテストを実行していると、すべてのテストが成功したかどうか確認するために時間がかかることがあります。-f(failfast)オプションを使うと、最初のエラーが発生した時点でテストの実行を停止します。

python -m unittest -f test_sample.py

これにより、エラーが発生したらすぐに問題のある箇所を修正することができるため、時間の節約になります。

  1. 特定のパターンに一致するテストだけを実行する

unittest discoverを使う際、-pオプションを使用することで、特定のパターンに一致するファイルのみを対象にテストを実行することが可能です。

python -m unittest discover -s tests -p 'test_*.py'

このコマンドは、testsディレクトリ内のtest_で始まるファイルを探してテストを実行します。特定のパターンに基づいてテストを絞り込みたい場合に便利です。

その他のオプション

-q(quiet)オプション

テスト実行時の出力を簡略化するために使います。

python -m unittest -q test_sample.py

結果だけを確認したい場合などに使うと、冗長な出力を避けることができます。

-b(buffer)オプション

テスト中の標準出力や標準エラー出力をバッファリングして、テストの失敗時のみ表示します。

python -m unittest -b test_sample.py

テストが大量の出力を行う場合でも、失敗時の情報だけを出力したい場合に役立ちます。

この記事が気に入ったらサポートをしてみませんか?