【unittest】Pythonプログラムをunittestでテストする方法

Python プログラムのテストを実行するためのテストフレームワークである unittest モジュールについて解説します。
目次
unittest とは
unittestは、Python 標準のテストフレームワークです。unittest は、もともとは Java の JUnit に触発されて作成されたため、JUnit 等のテストフレームワークを使用したことがある人は似た感覚で使い始められます。
テスト駆動開発 (TDD:Test-Driven Development) という自動テストを中心にした高品質のソフトウェア開発をするための方法論があり、Python コミュニティでは幅広く利用されています。テスト駆動開発においては、unittest のようなテストフレームワークは非常に重要です。テスト駆動開発については「エキスパートPythonプログラミング」という書籍にも記載があるため、参考にしてください。
この記事では、Python の標準テストフレームワークである unittest を使用したテストの基本的な使い方を紹介します。
unittest の使い方
プログラムのテストのイメージに慣れていない方のために、まずは以下の図を使って簡単なテストイメージを紹介します。以降で具体的なプログラムも使って説明をしていきますので、ここではまずイメージをつかんでください。

sample.py というプログラムを作成しテストしたいと考えているとします。このsample.py には Sample というクラスがあり x と y を足して 2 倍にするだけのシンプルな add_and_double というメソッドが定義されています。
このプログラムをテストしたい場合には「test_sample.py」といったテストプログラムを作成します。unittest では test という名前から始まるファイルを探索してまとめて実行することも可能です。テストプログラム内では、sample をインポートして、unittest.TestCase を継承して TestSample のようなクラスを作成します。
unittest では、TestCase を継承したクラス内で「test」で始まるメソッドをテストとして実行します。なお、unittest は unittest.main() で実行します。
テストプログラムでは「self.assertEqual(self.temp.add_and_double(1, 1), 4)」のように「assertXXXX」といった unittest.TestCase で定義されているメソッドを使って判定します。この判定式の結果が一致しない場合にはテスト失敗となります。
なお、assertXXXX については、一致を確認する assertEqual、真 (True) かを判定する assertTrue など各種用意されています。各種メソッドの詳細は unittest.TestCase の公式ドキュメントのこちらを確認してください。
上記例では、x=1, y=1 として引数を渡していますので、(1 + 1) × 2 = 4 を add_and_double メソッドは返却してほしいわけですが、異なる値が返ってくるということはメソッドの処理に何か問題があるということになります。なお、assertXXXX は 1 つのメソッドに複数行でも書くことができます。
テストプログラム内にて、テスト観点毎にテストを実行するメソッドをどんどん追加していくわけです。unittest を実行するとテストプログラム内に定義されているテスト用メソッドを順番に実行し、それぞれが成功したか失敗したかを出力してくれます。
まずはテストに関して上記のようなイメージを持ってもらえればと思います。では、以降では unittest の実行方法や具体的な例を使った説明に進んでいきたいと思います。
unittest の実行方法
unittest では、テストプログラムに unittest.main() を記述することでテストが実行されます。
if __name__ == "__main__":
# unittestを実行する
unittest.main()上記の記載があるテストプログラムを実行すると、unittest.TestCase を継承したクラスの test から始まるメソッドをテスト対象として unittest によるテストが実行されます。なお、「-v」を入れておくと詳細情報が表示されます。
python test_sample.py -v
通常、複数のテストプログラムをまとめて実行したくなるかと思います。unittestは、以下のようなコマンドを使用することで、test から始まる名前のファイルをテスト対象として判断してくれるため該当フォルダ内を探索して test から始まる名前のファイルを順番にテストしてくれます。
python -m unittest -v
unittest によるテストの実装例
ここからは、より具体的にunittestを使用したテスト実装例を見ていきます。まずは、以降で説明する関連プログラムをまとめて示します。テスト対象の sample.py、テストプログラムの test_sample.py があります。詳細内容は後ほど順番に説明します。
sample.py:テスト対象プログラム
class Sample:
"""Sampleクラス"""
def add_and_double(self, x, y):
"""xとyを足して2倍した値を返却する
Args:
x: 入力値1
y: 入力値2
"""
# intでない場合は、ValueErrorとする
if not isinstance(x, int) or not isinstance(y, int):
raise ValueError
# 計算処理
result = x + y
result *= 2
return resulttest_sample.py:テストプログラム
import unittest
import sample
# スキップ用フラグ
SKIP_FLAG = True
class TestSample(unittest.TestCase):
"""Sampleクラスのテスト用クラス"""
@classmethod
def setUpClass(cls):
print("\nStart TestSample")
cls.temp = sample.Sample()
@classmethod
def tearDownClass(cls):
print("\nEnd of TestSample")
del cls.temp
def setUp(self):
print("\nTest Start")
def tearDown(self):
print("End of Test")
def test_add_and_double(self):
"""テストケース1: 正常計算"""
self.assertEqual(self.temp.add_and_double(1, 1), 4)
self.assertEqual(self.temp.add_and_double(2, 2), 8)
# self.assertEqual(self.temp.add_and_double(2, 2), 4)
def test_add_and_double_raise(self):
"""テストケース2: 例外処理"""
with self.assertRaises(ValueError):
self.temp.add_and_double("1", 1)
with self.assertRaises(ValueError):
self.temp.add_and_double(1, "1")
with self.assertRaises(ValueError):
self.temp.add_and_double("1", "1")
# with self.assertRaises(ValueError):
# self.temp.add_and_double(1, 1)
@unittest.skip("skip理由:xxxxxxxxxx")
def test_add_and_double_skip(self):
"""スキップ確認用"""
assert self.temp.add_and_double(1, 1) == 4
@unittest.skipIf(SKIP_FLAG is True, "条件付きskip理由:xxxxxxxxx")
def test_add_and_double_skipif(self):
"""条件付きスキップ確認用"""
assert self.temp.add_and_double(1, 1) == 4
if __name__ == "__main__":
# unittestを実行する
unittest.main()実行結果「python -m unittest -v」または「python test_sample.py -v」実行
> python -m unittest -v Start TestSample test_add_and_double (test_sample.TestSample) テストケース1: 正常計算 ... Test Start End of Test ok test_add_and_double_raise (test_sample.TestSample) テストケース2: 例外処理 ... Test Start End of Test ok test_add_and_double_skip (test_sample.TestSample) スキップ確認用 ... skipped 'skip理由:xxxxxxxxxx' test_add_and_double_skipif (test_sample.TestSample) 条件付きスキップ確認用 ... skipped '条件付きskip理由:xxxxxxxxx' End of TestSample ---------------------------------------------------------------------- Ran 4 tests in 0.004s OK (skipped=2)
以降では上記プログラムを使ってポイントとなるテスト記載方法を説明していきます。
テストの基本要素
テストの基本構成についてまず説明します。以下は、test_sample.py 内の TestSample クラスの一部抜粋です。
class TestSample(unittest.TestCase):
"""Sampleクラスのテスト用クラス"""
def test_add_and_double(self):
"""テストケース1: 正常計算"""
# print("実行:test_add_and_double")
self.assertEqual(self.temp.add_and_double(1, 1), 4)
self.assertEqual(self.temp.add_and_double(2, 2), 8)unittest を使用したテストでは、上記の TestSample クラスのように unittest.TestCase を継承したテストクラスを作成、その中にテスト用メソッドを記載していきます。
test_add_and_double(self) は、正常計算結果を確認することを意図して作成したテストメソッドです。
テスト結果の判定を行う場合には、上記の assertEqual のように unittest に実装されているテスト判定用のメソッドを使用します。第 1 引数は判定対象のメソッドの返り値で、第 2 引数が正解として比較する値を指定しています。この判定が問題なく処理されれば、テストは OK となります。
他にも判定用メソッドは色々ありますので、各種メソッドの詳細は unittest.TestCase の公式ドキュメントのこちらを確認してください。
上記で記載しているケースは問題なく処理がされるため、実行結果を見ると以下のように ok となります。
test_add_and_double (test_sample.TestSample) テストケース1: 正常計算 ... ok
では、失敗する状況を考えてみます。例えば「self.assertEqual(self.temp.add_and_double(2, 2), 4)」のようなテストをした場合には、以下のように失敗となります。これは (2 + 2) × 2 = 8ですので、テストケースが間違っている例です。
======================================================================
FAIL: test_add_and_double (test_sample.TestSample)
テストケース1: 正常計算
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\PythonProjects\python-tech-sample-source\python-tech-sample-source\python-libraries\unittest\test_sample.py", line 39, in test_add_and_double
self.assertEqual(self.temp.add_and_double(2, 2), 4)
AssertionError: 8 != 4想定したものと出力結果が異なる場合には FAIL として失敗が表示されます。
なお、テストの失敗としては「テスト対象プログラムの処理結果が誤っている場合」や上記のように「テストケース自体が誤っているような場合」等、色々ありますのでエラーを見つつ、プログラムを修正していくようにしてください。
例外処理
次に、例外処理をテストする場合のテストメソッドの記載方法です。
def test_add_and_double_raise(self):
"""テストケース2: 例外処理"""
with self.assertRaises(ValueError):
self.temp.add_and_double("1", 1)
with self.assertRaises(ValueError):
self.temp.add_and_double(1, "1")
with self.assertRaises(ValueError):
self.temp.add_and_double("1", "1")今回のテスト対象プログラム sample.py の add_and_double メソッドでは、int ではない引数が渡された場合に ValueError を返却することになっています。
このような場合には、上記のように self.assertRaises を使用した with 句を用いて確認します。もし「self.temp.add_and_double(1, 1)」のように例外をあげないような処理を with 句内に記載するとテストは失敗となります。
setUp と tearDown
次に、各テストの実行前後にテストのセットアップや完了時処理をするための setUp と tearDown についての記載方法です。
@classmethod
def setUpClass(cls):
print("\nStart TestSample")
cls.temp = sample.Sample()
@classmethod
def tearDownClass(cls):
print("\nEnd of TestSample")
del cls.temp
def setUp(self):
print("\nTest Start")
def tearDown(self):
print("End of Test")setUp は、テストの前に準備をするために用意されているもので setUpClass や setUp があります。クラス生成時のセットアップが setUpClass で、メソッドの実行時のセットアップが setUp です。
一方で、tearDown は、テストの後に処理を実行するためのもので、tearDownClass や tearDown があります。それぞれクラスとメソッドに対応します。
上記例では、テスト開始と終了、各メソッドの開始と終了が分かるように print しています。実行結果を見ると以下のように表示されます。
> python -m unittest -v Start TestSample test_add_and_double (test_sample.TestSample) テストケース1: 正常計算 ... Test Start End of Test ok test_add_and_double_raise (test_sample.TestSample) テストケース2: 例外処理 ... Test Start End of Test ok test_add_and_double_skip (test_sample.TestSample) スキップ確認用 ... skipped 'skip理由:xxxxxxxxxx' test_add_and_double_skipif (test_sample.TestSample) 条件付きスキップ確認用 ... skipped '条件付きskip理由:xxxxxxxxx' End of TestSample ---------------------------------------------------------------------- Ran 4 tests in 0.004s OK (skipped=2)
setUpClass と tearDownClass の print は最初と最後のみ表示されていることが分かります。一方で setUp と tearDown の print は各メソッド実行前後で表示されます。
また、setUpClass ではあらかじめテスト対象のクラス Sample をオブジェクト化しており、tearDownClass では (特に必須なわけではないですが) オブジェクトを del しています。このようにすることで、各テストメソッドでは毎回クラスをオブジェクト化する必要がなくなりますし、テスト終了時の処理を必要に応じて記載できます。
テスト前後のタイミングで何か処理を記載したい場合には setUp や tearDown をうまく活用しましょう。
テストのスキップ
テストを多く記載していると一部のテストメソッドをスキップして処理を実行したくなる場合があります。テストをスキップする場合の記載方法について見ていきましょう。
通常のスキップ
一部のテストメソッドをスキップしたい場合には、以下のように @unittest.skip デコレータを使用します。引数にスキップ理由などを記載して実行時に表示可能です。
@unittest.skip("skip理由:xxxxxxxxxx")
def test_add_and_double_skip(self):
"""スキップ確認用"""
assert self.temp.add_and_double(1, 1) == 4スキップされると以下のような実行結果となります。
test_add_and_double_skip (test_sample.TestSample) スキップ確認用 ... skipped 'skip理由:xxxxxxxxxx'
条件付きスキップ
条件に一致する場合にテストをスキップしたいときもあるかと思いますが、その場合は以下のように @unittest.skipIf デコレータを使用して条件を引数に渡します。スキップ理由も引数に渡すことで表示可能です。
# スキップ用フラグ
SKIP_FLAG = True
...(省略)...
@unittest.skipIf(SKIP_FLAG is True, "条件付きskip理由:xxxxxxxxx")
def test_add_and_double_skipif(self):
"""条件付きスキップ確認用"""
assert self.temp.add_and_double(1, 1) == 4スキップされると以下のような実行結果となります。
test_add_and_double_skipif (test_sample.TestSample) 条件付きスキップ確認用 ... skipped '条件付きskip理由:xxxxxxxxx'
まとめ
Python プログラムのテストを実行するためのテストフレームワークである unittest モジュールについて解説しました。unittest は Python 標準のテストフレームワークです。
この記事内では、unittest の実行方法やテスト実装例について紹介しました。また、setUp や teaDown を使ったテスト準備やテストのスキップも紹介しています。
unittest は、Python 標準のテストフレームワークなので皆さんも色々と使い方を調べてもらえたらと思います。
また、テスト駆動開発 (TDD:Test-Driven Development) という自動テストを中心にした高品質のソフトウェア開発をするための方法論は Python コミュニティでは幅広く利用されています。unittest はテスト駆動開発においても使用できるテストフレームワークです。テスト駆動開発については「エキスパートPythonプログラミング」という書籍にも記載があるため、参考にしてください。
上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

