unittest とは
Pythonのunittestは、Pythonの標準ライブラリに含まれるテストフレームワークです。unittestを使用すると、Pythonコードのユニットテストを書くことができます。
unittestは、次の機能を提供します。
- テストクラスの作成
- テストメソッドの作成
- テストの自動実行
- アサーションの使用
- テストのスキップや無視
以下は、unittestを使用してテストを書く例です。
import unittest
class MyTest(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)
def test_subtraction(self):
self.assertEqual(3 - 1, 2)
if __name__ == '__main__':
unittest.main()
この例では、MyTestという名前のテストクラスを定義し、test_addition
とtest_subtraction
という2つのテストメソッドを定義しています。それぞれ、1 + 1が2に等しいことと、3 - 1が2に等しいことをアサーションしています。
最後に、unittest.main()
を呼び出してテストを自動実行しています。
unittestを使用することで、コードの品質を向上させ、バグを見つけやすくなります。また、開発者がコードを修正する際に、テストが失敗したことを知らせてくれるため、開発プロセス全体を改善することができます。
unittest の用語
Pythonのunittestには、次のような用語があります。
用語 | 説明 |
---|---|
テストケース(Test case) | unittest で実行されるテストの単位。通常、1 つのテスト関数に対応する。 |
テストスイート(Test suite) | 複数のテストケースをグループ化したもの。unittest.TestSuite オブジェクトを使用して作成する。 |
テストランナー(Test runner) | テストスイートを実行するためのツール。unittest.TextTestRunner やunittest.XMLTestRunner などがある。 |
テストフィクスチャ(Test fixture) | テストケースの実行に必要な事前準備や後処理を行うためのコード。setUp() やtearDown() メソッドによって定義される。 |
アサーション(Assertion) | テスト結果を判定するためのコード。assertEqual() やassertTrue() などがある。 |
テストランナー出力(Test runner output) | テストランナーが出力する情報。テスト結果や実行時間などが含まれる。 |
スキップ(Skip) | テストをスキップするための機能。@unittest.skip デコレータやskipTest() メソッドを使用する。 |
アサーションエラー(Assertion error) | アサーションが失敗した場合に発生する例外。 |
テストランのエラー(Test run error) | テストスイートの実行中に発生したエラー。通常は、テスト関数のエラーなどが含まれる。 |
テストランの失敗(Test run failure) | テストスイートの実行中に、アサーションが失敗した場合に発生するエラー。 |
unittest の手順
- テスト対象の関数やクラスを定義します。
- unittestの
TestCase
クラスを継承したクラスを作成します。 - テストメソッドを作成します。テストメソッドは、"test_"で始まるメソッド名にすることが推奨されています。
- テストメソッド内で、テスト対象の関数やクラスを呼び出します。
- assert文を使用して、期待される結果が得られたかどうかを確認します。
- テストクラスを
unittest.main()
で実行します。
以下に、具体例を示します。ここでは、テスト対象の関数として、文字列を反転させるreverse_string
関数を定義し、その関数をテストするテストクラスを作成します。
def reverse_string(s):
return s[::-1]
import unittest
class TestReverseString(unittest.TestCase):
def test_reverse_string(self):
self.assertEqual(reverse_string('hello'), 'olleh')
self.assertEqual(reverse_string('world'), 'dlrow')
if __name__ == '__main__':
unittest.main()
上記のコードでは、テストクラスとしてTestReverseStringクラスを定義し、その中にテストメソッドとしてtest_reverse_stringメソッドを作成しています。test_reverse_stringメソッドでは、reverse_string関数に対して、文字列を渡して返り値が正しいかどうかをassertEqualメソッドを使用して確認しています。
最後に、if name == 'main':
の部分で、このファイルが直接実行された場合にunittest.main()
を呼び出してテストを実行しています。
unittest の詳細な使い方
unittestの詳細な使い方について説明します。
テストクラスの作成
unittestを使用する場合、テストケースを定義するためにテストクラスを作成する必要があります。テストクラスは、unittest.TestCase
クラスを継承する必要があります。テストメソッドを定義する場合は、メソッド名がtest_
で始まる必要があります。
import unittest
class MyTest(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)
def test_subtraction(self):
self.assertEqual(3 - 1, 2)
アサーションの使用
unittestでは、アサーションを使用してテストの成否を判断します。アサーションは、テストの実行結果が期待通りであることを確認するために使用されます。
例えば、次のようなアサーションを使用して、値が等しいことを確認できます。
import unittest
class MyTest(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)
def test_subtraction(self):
self.assertEqual(3 - 1, 2)
テストの自動実行
unittestを使用する場合、テストを自動的に実行することができます。もっとも簡単な方法は、unittest.main()
を呼び出すことです。
if __name__ == '__main__':
unittest.main()
この方法で実行する場合、次のコマンドを実行します。
$ python test_module.py
テストのスキップや無視
テストをスキップする場合は、unittest.skip()
を使用します。スキップする理由を指定することもできます。
import unittest
class MyTest(unittest.TestCase):
@unittest.skip("reason for skipping here")
def test_addition(self):
self.assertEqual(1 + 1, 2)
unittest のアサーションメソッド
assert文は、テスト対象の関数が期待通りの結果を返すかどうかを検証するために使用されます。例えば、次のようにテストケースを定義することができます。
import unittest
def add(a, b):
return a + b
class MyTest(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)
self.assertNotEqual(add(2, 3), 6)
self.assertTrue(add(2, 3) > 4)
self.assertFalse(add(2, 3) < 4)
上記の例では、add()
関数をテスト対象の関数として定義しています。テストケースでは、add()
関数が期待通りの結果を返すかどうかを検証しています。例えば、self.assertEqual(add(2, 3), 5)
は、add(2, 3)
の結果が5であることを検証します。全てのassert文が成功すると、テストケースはパスします。assert文のいずれかが失敗すると、テストケースは失敗します。
以下に、主要なアサーションメソッドを紹介します。
assertEqual(a, b)
aとbが等しいことを検証します。a == b
と等価です。
self.assertEqual(1 + 2, 3)
assertNotEqual(a, b)
aとbが等しくないことを検証します。a != b
と等価です。
self.assertNotEqual(1 + 2, 4)
assertTrue(x)
xがTrue
であることを検証します。
self.assertTrue(1 + 2 == 3)
assertFalse(x)
xがFalse
であることを検証します。
self.assertFalse(1 + 2 == 4)
assertIs(a, b)
aとbが同じオブジェクトであることを検証します。a is b
と等価です。
self.assertIs([], [])
assertIsNot(a, b)
aとbが同じオブジェクトではないことを検証します。a is not b
と等価です。
self.assertIsNot([], [])
assertIsNone(x)
xがNone
であることを検証します。x is None
と等価です。
self.assertIsNone(None)
assertIsNotNone(x)
xがNone
でないことを検証します。x is not None
と等価です。
self.assertIsNotNone(1 + 2)
assertIn(a, b)
aがbに含まれていることを検証します。a in b
と等価です。
self.assertIn(1, [1, 2, 3])
assertNotIn(a, b)
aがbに含まれていないことを検証します。a not in b
と等価です。
self.assertNotIn(4, [1, 2, 3])
assertIsInstance(a, b)
aがbのインスタンスかどうかを確認します。
self.assertIsInstance([], list)
assertNotIsInstance(a, b)
aがbのインスタンスでないかどうかを確認します。
self.assertNotIsInstance([], dict)
assertAlmostEqual(a, b, places=x)
aとbが指定された桁数内で等しいかどうかを確認します。
self.assertAlmostEqual(1/3, 0.3333333333333333, places=5)
assertNotAlmostEqual(a, b, places=x)
aとbが指定された桁数内で等しくないかどうかを確認します。
self.assertNotAlmostEqual(1/3, 0.333333333, places=7)
assertRaises(exc, func, *args, **kwargs)
指定した例外が発生することを期待して、テストを実行します。つまり、テスト対象の関数が例外を発生させることを確認するために使用されます。
assertRaises
メソッドは、2つの引数を受け取ります。第1引数には、期待される例外の型を指定し、第2引数には、テスト対象の関数を渡します。以下は、assertRaisesメソッドを使用した例です。
import unittest
def divide(a, b):
if b == 0:
raise ZeroDivisionError('division by zero')
return a / b
class MyTest(unittest.TestCase):
def test_divide(self):
self.assertRaises(ZeroDivisionError, divide, 1, 0)
上記の例では、divide()
関数がゼロ除算の場合にZeroDivisionError
例外を発生させるように定義されています。MyTest
クラスのtest_divide
メソッドでは、assertRaises
メソッドを使用して、divide()
関数がZeroDivisionError
例外を発生させることを確認しています。assertRaises
メソッドの第1引数には、ZeroDivisionError
を指定し、第2引数には、divide()
関数とその引数を渡します。つまり、divide(1, 0)
を実行して、ZeroDivisionError
が発生することを期待しています。
assertRaises
メソッドは、指定した例外が発生しなかった場合にテストを失敗させます。また、例外が発生した場合には、例外オブジェクトが返されます。例外オブジェクトを使用して、例外が正しい種類のものであることを検証することもできます。例えば、次のようにassertRaises
メソッドを使用することができます。
import unittest
def divide(a, b):
if b == 0:
raise ZeroDivisionError('division by zero')
return a / b
class MyTest(unittest.TestCase):
def test_divide(self):
with self.assertRaises(ZeroDivisionError) as cm:
divide(1, 0)
self.assertEqual(str(cm.exception), 'division by zero')
上記の例では、assertRaises
メソッドをwith
文で使用しています。with
文を使用することで、assertRaises
メソッドの返り値である例外オブジェクトを取得し、例外が正しい種類のものであることを検証しています。cm.exception
は、例外オブジェクト自体を表します。str(cm.exception)
は、例外オブジェクトのエラーメッセージを表す文字列を返します。self.assertEqual
メソッドを使用して、cm.exception
のエラーメッセージが期待されるものであるかを検証しています。
assertRaises
メソッドは、単一の例外のみをチェックできます。複数の例外をチェックする場合は、assertRaises
メソッドを複数回呼び出す必要があります。また、引数を渡さない場合は、AssertionError
を発生させます。
例えば、次のようにassertRaises
メソッドを使用することができます。
import unittest
def divide(a, b):
if b == 0:
raise ZeroDivisionError('division by zero')
elif a < 0:
raise ValueError('a must be non-negative')
return a / b
class MyTest(unittest.TestCase):
def test_divide(self):
self.assertRaises(ZeroDivisionError, divide, 1, 0)
self.assertRaises(ValueError, divide, -1, 2)
上記の例では、divide()
関数がゼロ除算またはValueError
例外を発生させるように定義されています。MyTest
クラスのtest_divide
メソッドでは、assertRaises
メソッドを使用して、divide()
関数が指定した例外を発生させることを確認しています。assertRaises
メソッドを複数回呼び出すことで、複数の例外をチェックしています。
なお、assertRaises
メソッドは、コンテキストマネージャーを使用して次のように書くこともできます。
import unittest
def divide(a, b):
if b == 0:
raise ZeroDivisionError('division by zero')
elif a < 0:
raise ValueError('a must be non-negative')
return a / b
class MyTest(unittest.TestCase):
def test_divide(self):
with self.assertRaises(ZeroDivisionError):
divide(1, 0)
with self.assertRaises(ValueError):
divide(-1, 2)
この場合、assertRaises
メソッドの第1引数に例外を指定する必要はありません。また、コンテキストマネージャーを使用することで、with
ブロック内で発生した例外のみがキャッチされます。
unittest のテストランナー
unittestでは、テストの結果を報告するためのテストランナーが提供されています。テストランナーは、テストの実行を管理し、結果を報告するためのインターフェースを提供します。以下に、unittestで利用可能な主要なテストランナーを紹介します。
unittest.main()
このテストランナーは、unittestが提供するデフォルトのテストランナーです。このランナーを使うと、コマンドラインからテストを実行することができます。次のように、テストファイルを直接実行することでテストを実行することができます。
$ python test_module.py
unittest.TextTestRunner()
このテストランナーは、テストの結果をテキスト形式で報告するためのものです。次のように、テスト結果をコンソールに表示することができます。
import unittest
if __name__ == '__main__':
suite = unittest.TestLoader().discover('.')
unittest.TextTestRunner().run(suite)
unittest.HTMLTestRunner()
このテストランナーは、テストの結果をHTML形式で報告するためのものです。次のように、HTML形式のレポートを生成することができます。
import unittest
import HTMLTestRunner
if __name__ == '__main__':
suite = unittest.TestLoader().discover('.')
with open('test_report.html', 'wb') as f:
HTMLTestRunner.HTMLTestRunner(stream=f).run(suite)
unittest の特別なメソッド
unittestには、テストのセットアップやクリーンアップを行うための特別なメソッドがあります。それらのメソッドを使うことで、テストコードをより効果的に書くことができます。以下に、主要な特別なメソッドを紹介します。
setUp()
setUp()
メソッドは、テストケースの実行前に実行される処理を定義します。例えば、テストケースで共通して使用するオブジェクトの初期化や、データベースの接続を確立するなどの処理を定義することができます。setUp()
メソッドは、テストケースごとに一度だけ実行されます。
import unittest
class MyTest(unittest.TestCase):
def setUp(self):
self.my_object = MyObject()
def test_something(self):
result = self.my_object.do_something()
self.assertEqual(result, expected_result)
上記の例では、setUp()
メソッドでMyObject
オブジェクトを初期化し、テストケースで使用できるようにしています。
tearDown()
tearDown()
メソッドは、テストケースの実行後に実行される処理を定義します。例えば、データベースの接続を切断するなどの処理を定義することができます。tearDown()
メソッドは、テストケースごとに一度だけ実行されます。
import unittest
class MyTest(unittest.TestCase):
def setUp(self):
self.my_object = MyObject()
def tearDown(self):
self.my_object.close()
def test_something(self):
result = self.my_object.do_something()
self.assertEqual(result, expected_result)
上記の例では、tearDown()
メソッドでMyObject
オブジェクトの接続を切断しています。
setUp() と tearDown() の継承
setUp()
とtearDown()
メソッドは、継承を利用して複数のテストケースで共通の前処理・後処理を定義することもできます。例えば、複数のテストケースで共通の初期化処理を行う場合は、共通の親クラスを作成し、setUp()
メソッドを親クラスに定義します。
import unittest
class MyObjectTestCase(unittest.TestCase):
def setUp(self):
self.my_object = MyObject()
def tearDown(self):
self.my_object.close()
class TestSomething(MyObjectTestCase):
def test_something(self):
result = self.my_object.do_something()
self.assertEqual(result, expected_result)
class TestSomethingElse(MyObjectTestCase):
def test_something_else(self):
result = self.my_object.do_something_else()
self.assertEqual(result, expected_result)
上記の例では、MyObjectTestCase
クラスが共通の初期化処理を行う親クラスとして定義されています。TestSomething
クラスとTestSomethingElse
クラスは、それぞれMyObjectTestCase
クラスを継承しており、MyObject
オブジェクトを使用するためのsetUp()
メソッドとtearDown()
メソッドが自動的に継承されます。
このように、unittestではsetUp()
とtearDown()
メソッドを利用して、テストケースの前処理・後処理を柔軟に定義することができます。また、クラスや継承を利用することで、複数のテストケースで共通の前処理・後処理を簡単に実装することができます。
setUpClass() と tearDownClass()
setUpClass()
は、テストクラスが実行される前に呼び出されるメソッドです。このメソッドは、テストクラスで使用されるリソースをセットアップするために使用されます。
tearDownClass()
は、テストクラスが実行された後に呼び出されるメソッドです。このメソッドは、全てのテストが完了した後に実行され、テストクラスで使用されたリソースをクリーンアップするために使用されます。
setUpClass()
メソッドやtearDownClass()
メソッドは、次のように定義されます。
@classmethod
def setUpClass(cls):
pass
@classmethod
def tearDownClass(cls):
pass
setUpClass()
メソッドやtearDownClass()
メソッドは、クラスメソッドとして定義されます。このため、このメソッドはクラスレベルで実行され、クラス変数やクラスメソッドを使用することができます。
setUpClass()
メソッドは、例えば、データベースコネクションを確立したり、テスト用のファイルを作成したり、他のリソースをセットアップするために使用することができます。
tearDownClass()
メソッドは、例えば、データベースコネクションをクローズしたり、テスト用のファイルを削除したり、他のリソースを解放するために使用することができます。
以下は、setUpClass()
メソッドやtearDownClass()
メソッドを使用する例です。
import unittest
import os
class MyTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_dir = 'test'
os.makedirs(cls.test_dir)
def test_example(self):
test_file = os.path.join(self.test_dir, 'test.txt')
with open(test_file, 'w') as f:
f.write('test')
@classmethod
def tearDownClass(cls):
os.rmdir(cls.test_dir)
上記の例では、setUpClass()
メソッドでtest
という名前のディレクトリを作成しています。test_example()
メソッドでは、test.txtというファイルをtest
ディレクトリに作成しています。tearDownClass()
メソッドでtest
ディレクトリを削除しています。このように、テストで使用するリソースをsetUpClass()
メソッドでセットアップすることで、テスト間の相互影響を避けることができます。
subTest
subTest()
は、Pythonのunittestで複数の入力値を使用して同じテストを繰り返し実行するために使用される機能です。通常、単一のテスト関数に対して、複数の異なる入力値を使用してテストを行う場合に使用されます。
subTest()
を使用すると、テストケースが失敗した場合に、失敗した入力値を特定することができます。また、subTest()
を使用することで、テスト関数が失敗しても、それ以降のテストケースの実行を続けることができます。
以下は、subTest()
を使用した例です。
import unittest
class TestMath(unittest.TestCase):
def test_add(self):
test_cases = [(2, 3, 5), (-2, 3, 1), (0, 0, 0)]
for a, b, expected in test_cases:
with self.subTest(a=a, b=b):
result = a + b
self.assertEqual(result, expected)
この例では、test_add()
関数が、複数の異なる入力値を使用して同じテストを実行します。subTest()
を使用することで、各入力値のテスト結果を独立して表示することができます。
subTest()
は、with
ステートメントと共に使用されます。with
ブロック内でアサーションを行うことで、入力値ごとにテストケースが独立して実行されます。また、subTest()
の引数には、キーワード引数として渡される入力値を指定することができます。
unittest を使ってテストを書くときの注意点
以下にunittestを使ってテストを書くときの注意点を紹介します。
- テストデータの適切な準備
テストを書く際には、適切なテストデータを用意する必要があります。データが不足している場合や、間違ったデータを使用している場合は、テストが不十分になる可能性があります。また、テストが複雑になることもあります。 - テストの依存関係を避ける
テストの実行順序に依存するようなテストを書くのは避けるべきです。それらは脆弱で、テストの実行順序が変わると失敗することがあります。 - テストの粒度
テストの粒度は、非常に重要です。小さなテストを書くことで、問題が発生した場合にその原因を特定しやすくなります。また、小さなテストを組み合わせて大きなテストを作成することもできます。 - エラーメッセージの分かりやすさ
テストが失敗した場合、エラーメッセージが分かりにくいと問題の特定に時間がかかることがあります。エラーメッセージは明確かつ分かりやすくするように努めるべきです。 - テストの実行順序
unittestでは、テストの実行順序が保証されていないため、テスト間で状態が共有されることがあります。そのため、各テストメソッドは、前のテストメソッドの影響を受けずに独立して実行されるようにする必要があります。
参考