単体テストの基礎となる構築要素は、テストケース -- セットアップと 正しさのチェックを行う、独立したシナリオ -- です。unittestでは、テスト ケースはunittestモジュールのTestCaseクラスのインスタ ンスで示します。テストケースを作成するにはTestCaseのサブクラスを 記述するか、またはFunctionTestCaseを使用します。
TestCaseから派生したクラスのインスタンスは、このオブジェクトだけ で一件のテストと初期設定・終了処理を行います。
TestCaseインスタンスは外部から完全に独立し、単独で実行する事も、 他の任意のテストと一緒に実行する事もできなければなりません。
以下のように、TestCaseのサブクラスはrunTest()をオーバライドし、 必要なテスト処理を記述するだけで簡単に書くことができます:
import unittest class DefaultWidgetSizeTestCase(unittest.TestCase): def runTest(self): widget = Widget('The widget') self.assertEqual(widget.size(), (50,50), 'incorrect default size')
何らかのテストを行う場合、ベースクラスTestCaseの assert*() か fail*()メソッドを使用してください。 テストが失敗すると例外が送出され、unittestはテスト結果を failureとします。その他の例外はerrorとなります。 これによりどこに問題があるかが判ります。failureは間違った結果 (6 になるはずが 5 だった)で発生します。errorは間違ったコード (たとえば間違った関数呼び出しによるTypeError)で発生します。
テストの実行方法については後述とし、まずはテストケースインスタンスの作成 方法を示します。テストケースインスタンスは、以下のように引数なしでコンス トラクタを呼び出して作成します。
testCase = DefaultWidgetSizeTestCase()
似たようなテストを数多く行う場合、同じ環境設定処理を何度も必要となりま す。例えば上記のようなWidgetのテストが100種類も必要な場合、それぞれのサ ブクラスでWidgetオブジェクトを生成する処理を記述するのは好ましくあり ません。
このような場合、初期化処理はsetUp()メソッドに切り出し、テスト実 行時にテストフレームワークが自動的に実行するようにすることができます:
import unittest class SimpleWidgetTestCase(unittest.TestCase): def setUp(self): self.widget = Widget('The widget') class DefaultWidgetSizeTestCase(SimpleWidgetTestCase): def runTest(self): self.failUnless(self.widget.size() == (50,50), 'incorrect default size') class WidgetResizeTestCase(SimpleWidgetTestCase): def runTest(self): self.widget.resize(100,150) self.failUnless(self.widget.size() == (100,150), 'wrong size after resize')
テスト中にsetUp()メソッドで例外が発生した場合、テストフレーム ワークはテストを実行することができないとみなし、runTest()を実行 しません。
同様に、終了処理をtearDown()メソッドに記述すると、 runTest()メソッド終了後に実行されます:
import unittest class SimpleWidgetTestCase(unittest.TestCase): def setUp(self): self.widget = Widget('The widget') def tearDown(self): self.widget.dispose() self.widget = None
setUp()が正常終了した場合、runTest()が成功したかどうかに従って tearDown()が実行されます。
このような、テストを実行する環境をfixtureと呼びます。
JUnitでは、多数の小さなテストケースを同じテスト環境で実行する場合、全て のテストについてDefaultWidgetSizeTestCaseのような SimpleWidgetTestCaseのサブクラスを作成する必要があります。これは 時間のかかる、うんざりする作業ですので、unittestではより簡単なメカニズムを 用意しています:
import unittest class WidgetTestCase(unittest.TestCase): def setUp(self): self.widget = Widget('The widget') def tearDown(self): self.widget.dispose() self.widget = None def testDefaultSize(self): self.failUnless(self.widget.size() == (50,50), 'incorrect default size') def testResize(self): self.widget.resize(100,150) self.failUnless(self.widget.size() == (100,150), 'wrong size after resize')
この例ではrunTest()がありませんが、二つのテストメソッドを定義し
ています。このクラスのインスタンスはtest*()メソッドのどちらか一
方の実行と、self.widget
の生成・解放を行います。この場合、テスト
ケースインスタンス生成時に、コンストラクタの引数として実行するメソッド名
を指定します:
defaultSizeTestCase = WidgetTestCase('testDefaultSize') resizeTestCase = WidgetTestCase('testResize')
unittestではテストスイートによってテストケースインスタンスをテスト 対象の機能によってグループ化することができます。テストスイート は、unittestのTestSuiteクラスで作成します。
widgetTestSuite = unittest.TestSuite() widgetTestSuite.addTest(WidgetTestCase('testDefaultSize')) widgetTestSuite.addTest(WidgetTestCase('testResize'))
各テストモジュールで、テストケースを組み込んだテストスイートオブジェクト を作成する呼び出し可能オブジェクトを用意しておくと、テストの実行や参照が 容易になります:
def suite(): suite = unittest.TestSuite() suite.addTest(WidgetTestCase('testDefaultSize')) suite.addTest(WidgetTestCase('testResize')) return suite
または:
def suite(): tests = ['testDefaultSize', 'testResize'] return unittest.TestSuite(map(WidgetTestCase, tests))
一般的には、TestCaseのサブクラスには良く似た名前のテスト関数が複 数定義されますので、unittestでは テストスイートを作成して個々のテストで満たすプロセスを自動化するのに使う TestLoaderを用意しています。 たとえば、
suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase)
はWidgetTestCase.testDefaultSize()
とWidgetTestCase.testResize
を走らせるテストスイートを作成します。
TestLoaderは自動的にテストメソッドを識別するのに'test'
という
メソッド名の接頭辞を使います。
いろいろなテストケースが実行される順序は、テスト関数名を組み込み関数cmp() でソートして決定されます。
システム全体のテストを行う場合など、テストスイートをさらにグループ化した い場合がありますが、このような場合、TestSuiteインスタンスには TestSuiteと同じようにTestSuiteを追加する事ができます。
suite1 = module1.TheTestSuite() suite2 = module2.TheTestSuite() alltests = unittest.TestSuite([suite1, suite2])
テストケースやテストスイートは (widget.py のような) テスト対象のモジュール内にも記述できますが、テストは (test_widget.py のような) 独立したモジュールに置いた方が 以下のような点で有利です:
ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。