(最終更新月:2023年6月)
✔ 当記事は以下のような方に向けて書かれています
「Python unittestってどんな機能があるの?」
「unittestの使い方を学びたい」
「Python unittestの実例を参考にしたい」
✔ 当記事でお伝えすること
- Python unittestの基本概念
- unittestの使い方や応用方法
- Python unittestの具体的な実例
当記事では、Python unittestの基本からオプションを活用した応用方法まで、具体的な例を交えてわかりやすく解説します。
ぜひ最後までご覧ください。
Python unittestの基本
当記事では、「テストの3要素」「unittestモジュールのインストール」「基本的な例で学ぶunittest実装」についてお伝えします。
- テストの3要素
- unittestモジュールのインストール
- 基本的な例で学ぶunittest実装
- unittestの書き方
- unittestにおけるアサーションメソッド一覧
テストの3要素
ユニットテストは、基本的に「準備」「実行」「検証」の3つのステップで構成されます。
- 「準備」:テストに必要なデータや状態を整える
- 「実行」:テスト対象のコードを実行する
- 「検証」:実行結果が期待したものであるかを確認する
これらのステップを適切に設計・実装することで、信頼性の高いテストコードを作れるようになります。
unittestモジュールのインストール
Pythonでは、テストを行うために、特別なインストール作業は必要ありません。
なぜならPythonでは、標準でunittestというユニットテスト用のフレームワークが組み込まれているからです。
Pythonをインストールしてある環境であれば、すぐにunittestを利用することが可能です。
基本的な例で学ぶunittest実装
unittestの基本的な使い方を、単純な足し算の関数に対するテストを例に説明します。
まず、テスト対象のコードを用意しましょう
def add(a, b):
return a + b
以下のようにunittestを使ってこの関数のテストを書きます。
import unittest
class TestAdd(unittest.TestCase):
def test_add(self):
self.assertEqual(add(1, 2), 3)
if __name__ == '__main__':
unittest.main()
unittestの書き方
unittestの書き方をみていきましょう。
手順は以下の通りです。
- unittest.TestCaseを継承したクラスを作成する
- クラスの中にテストメソッド(ここでは
test_add
)を定義する - その中で
assertEqual
などのアサーションメソッドを使って期待値と実際の結果を比較する unittest.main()
を呼び出してテストを実行する
テスト関数では、値を返す必要はありません。
以下のメソッドを使い、期待値と実際の値を比較します。
assertEqual(引数1,引数2)
引数1と引数2が一致しているとTrueを返します。
値が意図したものと等しいかを確認してください。
unittestにおけるアサーションメソッド一覧
以下はunittestで、期待値と結果を検証するメソッドの一覧です。
アサーション名 | 説明 | 例 |
---|---|---|
assertEqual(a, b) | aとbが等しいかどうかを確認します。 | assertEqual(5, 5) |
assertNotEqual(a, b) | aとbが等しくないかどうかを確認します。 | assertNotEqual(2, 3) |
assertTrue(x) | xがTrueであるかどうかを確認します。 | assertTrue(4 < 5) |
assertFalse(x) | xがFalseであるかどうかを確認します。 | assertFalse(10 > 100) |
assertIs(a, b) | aとbが同じオブジェクトかどうかを確認します。 | assertIs(obj1, obj2) |
assertIsNot(a, b) | aとbが異なるオブジェクトかどうかを確認します。 | assertIsNot(obj1, obj2) |
assertIsNone(x) | xがNoneであるかどうかを確認します。 | assertIsNone(result) |
assertIsNotNone(x) | xがNoneでないかどうかを確認します。 | assertIsNotNone(value) |
assertIn(a, b) | aがbに含まれているかどうかを確認します。 | assertIn(3, [1, 2, 3, 4]) |
assertNotIn(a, b) | aがbに含まれていないかどうかを確認します。 | assertNotIn(5, [1, 2, 3, 4]) |
assertRaises(exception, callable, *args) | callableが例外exceptionを発生させるかどうかを確認します。 | assertRaises(ValueError, int, “abc”) |
コマンドラインとオプション
こちらでは、「コマンドラインインターフェイスの利用方法」と「オプションで機能制御」についてお伝えします。
- コマンドラインインターフェイスの利用方法
- オプションで機能制御
- unittestのオプション一覧
コマンドラインインターフェイスの利用方法
Pythonのunittestモジュールは、コマンドラインインターフェイスからも利用可能です。
これを利用すると、特定のテストだけを実行したり、テスト結果を詳細に表示したりできます。
以下に、コマンドラインからunittestを実行する基本的な方法を示します。
python -m unittest test_module.TestClass.test_method
test_module
は、テストコードが記述されているPythonファイル(.pyを除く)です。
TestClass
はその中のテストケースが定義されているクラス、test_method
は実行したいテストメソッドを指しています。
オプションで機能制御
unittestのコマンドラインインターフェイスには多くのオプションがあります。
オプションにより、テストの実行方法を細かく制御できるのです。
一例を挙げると、-v
オプションを付けるとテストの実行結果が詳細に表示されます。
python -m unittest -v test_module.TestClass
このコマンドを実行すると、TestClass
内の全てのテストが実行され、その結果とともに各テストメソッドの名前も表示されます。
unittestのオプション一覧
以下はunittestのオプション一覧です。
オプション名 | 説明 | 例 |
---|---|---|
-b, –buffer | バッファリングを有効にします。 | python -m unittest -b |
-c, –catch | テストメソッド内での例外をキャッチします。 | python -m unittest -c |
-f, –failfast | 最初のテスト失敗時に実行を停止します。 | python -m unittest -f |
-k PATTERN, –keyword PATTERN | テストメソッド名にパターンを適用して実行します。 | python -m unittest -k test_case |
-s START, –start-directory START | テストを開始するディレクトリを指定します。 | python -m unittest -s tests |
-p PATTERN, –pattern PATTERN | テストファイル名にパターンを適用して実行します。 | python -m unittest -p test_*.py |
-t TOP, –top-level-directory TOP | トップレベルディレクトリを指定します。 | python -m unittest -t /path/to/tests |
-v, –verbose | 詳細な出力モードで実行します。 | python -m unittest -v |
–locals | 失敗したテストの詳細情報にローカル変数を含めます。 | python -m unittest –locals |
–failfast | 最初のテスト失敗時に実行を停止します(省略形)。 | python -m unittest –failfast |
–buffer | バッファリングを有効にします(省略形)。 | python -m unittest –buffer |
–catch | テストメソッド内での例外をキャッチします(省略形)。 | python -m unittest –catch |
これらのオプションは、Pythonのunittestフレームワークを使用してテストスクリプトを実行する際に利用できます。
テストディスカバリ
こちらでは、「トップダウン探索法」と「load_testsプロトコル」についてお伝えします。
- トップダウン探索法
- load_testsプロトコル
トップダウン探索法
unittestモジュールにはテストディスカバリという機能があります。
特定のディレクトリから再帰的にテストを探索・実行できるものです。
動作は、トップダウン探索法に基づいておこなわれます。
以下のコマンドを実行しましょう。
python -m unittest discover
現在のディレクトリから始めて下位のディレクトリに対して再帰的にテストを探索し、見つけたテストを全て実行。
デフォルトでは、test*.py
という名前のPythonファイルがテストとして扱われます。
load_testsプロトコル
load_tests
プロトコルはテストディスカバリの一部です。
load_testsを利用すると、テストの探索の方法をカスタマイズできます。
特定のテストだけを実行したり、特定の順序でテストを実行したりといったことが可能になるのです。
テストモジュールまたはテストケースの中に以下のシグネチャの関数を定義しましょう。
load_tests(loader, tests, pattern)
この関数がテストディスカバリ時に呼び出されるのです。
この関数内で任意のテストをTestSuite
オブジェクトとして返すことで、それらのテストが実行されます。
高度なテスト構造
こちらでは、より高度なテスト構造についてご覧いただきます。
テストを使いこなすことで、確実性の高いアプリケーションの作成が可能になるでしょう。
- クラスと関数の利用
- テストグループ化&ロード/起動
- サブテストを利用した繰り返しテストの区別
クラスと関数の利用
Pythonのunittestフレームワークでは、テストケースを表すためにクラスと関数が使用されます。
unittest.TestCaseを継承したクラスを定義して、そのクラス内にテスト内容をメソッドとして記述。
各メソッドがひとつのテストケースを表すので、testで始まる名前にしてください。
import unittest
class TestMyFunction(unittest.TestCase):
def test_addition(self):
self.assertEqual(add(1, 2), 3)
上記の例では、TestMyFunctionというクラスの中にtest_additionというメソッドがテストケースとして定義されています。
テストグループ化&ロード/起動
テストのグループ化について見ていきましょう。
グループ化のメリットは以下の通り。
- 複数のテストをグループに分けて管理できる
- 特定の条件下でのみ一部のテストを実行できる
具体的には、TestSuiteクラスのインスタンスを活用し、グループ化をおこないます。
suite = unittest.TestSuite()
suite.addTest(TestMyFunction('test_addition'))
unittest.TextTestRunner().run(suite)
他にも、unittestのTestLoaderクラスが使えます。
特定の条件を満たすテストケースだけを探索し、テストスイートを作成できるものです。
サブテストを利用した繰り返しテストの区別
テストケースの中で同じコードを繰り返し実行できます。
繰り返し実行する理由は、関数にさまざまな値を入れて試す必要があるからです。
しかしループを使ったテストでは、どの入力値で問題が生じたのかが一見して分かりにくいのがデメリット。
これを解消するために、unittestモジュールではサブテストを使いましょう。
サブテストを使用すると、各繰り返しで異なるテストケースが生成され、テスト結果が個々に報告されるのです。
class TestMathFunc(unittest.TestCase):
def test_add(self):
for i in range(5):
with self.subTest(i=i):
self.assertEqual(add(i, i), i*2)
どの値でエラーが起きたか明確にわかるのが、サブテストを利用するメリットです。
テストの管理
こちらでは、テストの管理方法をみていきましょう。
テストは開発時だけでなく、修正などを加えた際にも使えるもので、長く管理しておくことが大切です。
- テストのスキップと予期された失敗
- クラスとモジュールのフィクスチャ
- シグナルハンドリング
テストのスキップと予期された失敗
unittestモジュールには、@unittest.skip
デコレータや@unittest.expectedFailure
デコレータが存在します。
テストをスキップする必要がある場合や、予期された失敗を示すことに使うものです。
これらのデコレータをテストケースのメソッドに適用することで、そのテストケースがスキップされる、あるいは失敗が予期されることを示すことができます。
@unittest.skip("demonstrating skipping")
def test_nothing(self):
self.fail("shouldn't happen")
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")
上記の例では、test_nothing
は常にスキップされ、test_fail
は常に失敗することが期待されるようになっています。
クラスとモジュールのフィクスチャ
テストを行う前後で何らかの設定やクリーンアップが必要な場合、テストフィクスチャを使用します。
テストフィクスチャは、テストの前後に行われる設定やクリーンアップを含むコードで、それによりテストが一定の環境下で行われることを保証します。
unittestモジュールでは、テストケースクラスや全テストケースで共有されるセットアップやクリーンアップを定義するためのメソッドが提供されています。
class MyTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls._connection = create_expensive_connection_object()
@classmethod
def tearDownClass(cls):
cls._connection.destroy()
上記の例では、setUpClass
とtearDownClass
メソッドを使用して、全テストケースで共有するリソースのセットアップとクリーンアップを定義しています。
シグナルハンドリング
Pythonのunittestフレームワークは、テスト実行中に受信したシグナルを適切に処理する機能も提供します。
これにより、例えば長時間実行するテストがシステムに対して中断シグナル(SIGINT)を送信した場合でも、適切にテストのクリーンアップを行い、テスト結果をレポートすることができます。
class TestSignalHandling(unittest.TestCase):
def test_long_running(self):
for i in range(10000):
if i % 1000 == 0:
print('Still running...')
do_something_expensive()
上記の例のテストは非常に時間がかかるため、中断シグナルが送信されるとテストが適切にクリーンアップされ、その時点での結果がレポートされます。
まとめ
当記事では、Pythonのunittestモジュールについて学習してきました。
- テストの自動発見
- テストケースのグループ化
- テスト実行前後の設定/クリーンアップ
- シグナルハンドリング
unittestを用いることで、ソフトウェアが正確に動作していることを確認し、バグの早期発見・修正を可能にします。
このガイドではunittestの基本的な使い方を紹介しましたが、unittestにはまだ探求できる多くの機能が存在します。
さらに深く学びたい方は、Python公式のドキュメンテーションを参照してください。
Pythonのunittestを使いこなすことで、あなたのコードはより信頼性の高いものとなり、あなた自身もよりプロフェッショナルな開発者となるでしょう。
これからもPythonとともに、あなたのプログラミングスキルがさらに向上することを願っています。