29

重複の可能性:
Pythonで動的(パラメータ化された)単体テストを生成する方法は?

unittest パッケージを使用してテストを作成していますが、コードの繰り返しを避けたいと考えています。非常によく似た方法を必要とする多くのテストを実行しますが、毎回異なる値は 1 つだけです。単純で役に立たない例は次のとおりです。

class ExampleTestCase(unittest.TestCase):

    def test_1(self):
        self.assertEqual(self.somevalue, 1)

    def test_2(self):
        self.assertEqual(self.somevalue, 2)

    def test_3(self):
        self.assertEqual(self.somevalue, 3)

    def test_4(self):
        self.assertEqual(self.somevalue, 4)

毎回すべてのコードを繰り返さずに上記の例を書く方法はありますか?

    def test_n(self, n):
        self.assertEqual(self.somevalue, n)

異なる入力でこのテストを試すように unittest に指示しますか?

4

6 に答える 6

11

Python でパラメータ化されたテストを実行するために使用できるツールの一部は次のとおりです。

于 2012-10-05T06:44:23.647 に答える
3

本当に複数の unitttest が必要な場合は、複数のメソッドが必要です。これを取得する唯一の方法は、ある種のコード生成を使用することです。これは、メタクラスを使用するか、クラス デコレータを使用して (Python 2.6 を使用している場合) など、定義後にクラスを微調整することによって行うことができます。

これは、特別な「multitest」および「multitest_values」メンバーを探し、それらを使用してその場でテスト メソッドを構築するソリューションです。エレガントではありませんが、大まかにあなたが望むことをします:

import unittest
import inspect

class SomeValue(object):
    def __eq__(self, other):
        return other in [1, 3, 4]

class ExampleTestCase(unittest.TestCase):
    somevalue = SomeValue()

    multitest_values = [1, 2, 3, 4]
    def multitest(self, n):
        self.assertEqual(self.somevalue, n)

    multitest_gt_values = "ABCDEF"
    def multitest_gt(self, c):
        self.assertTrue(c > "B", c)


def add_test_cases(cls):
    values = {}
    functions = {}
    # Find all the 'multitest*' functions and
    # matching list of test values.
    for key, value in inspect.getmembers(cls):
        if key.startswith("multitest"):
            if key.endswith("_values"):
                values[key[:-7]] = value
            else:
                functions[key] = value

    # Put them together to make a list of new test functions.
    # One test function for each value
    for key in functions:
        if key in values:
            function = functions[key]
            for i, value in enumerate(values[key]):
                def test_function(self, function=function, value=value):
                    function(self, value)
                name ="test%s_%d" % (key[9:], i+1)
                test_function.__name__ = name
                setattr(cls, name, test_function)

add_test_cases(ExampleTestCase)

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

これは、実行したときの出力です

% python stackoverflow.py
.F..FF....
======================================================================
FAIL: test_2 (__main__.ExampleTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "stackoverflow.py", line 34, in test_function
    function(self, value)
  File "stackoverflow.py", line 13, in multitest
    self.assertEqual(self.somevalue, n)
AssertionError: <__main__.SomeValue object at 0xd9870> != 2

======================================================================
FAIL: test_gt_1 (__main__.ExampleTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "stackoverflow.py", line 34, in test_function
    function(self, value)
  File "stackoverflow.py", line 17, in multitest_gt
    self.assertTrue(c > "B", c)
AssertionError: A

======================================================================
FAIL: test_gt_2 (__main__.ExampleTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "stackoverflow.py", line 34, in test_function
    function(self, value)
  File "stackoverflow.py", line 17, in multitest_gt
    self.assertTrue(c > "B", c)
AssertionError: B

----------------------------------------------------------------------
Ran 10 tests in 0.001s

FAILED (failures=3)

コード生成で発生するいくつかの問題をすぐに確認できます。「test_gt_1」はどこから来たのですか? 名前をより長い「test_multitest_gt_1」に変更できますが、どのテストが 1 ですか? ここでは、_1 ではなく _0 から開始することをお勧めします。おそらく、あなたのケースでは、値を Python 関数名として使用できることがわかっています。

私はこのアプローチが好きではありません。私は、テスト メソッドを自動生成するコード ベース (ある場合はメタクラスを使用) に取り組みましたが、有用というよりも理解するのがはるかに難しいことがわかりました。テストが失敗した場合、失敗の原因を突き止めるのは難しく、失敗の原因を突き止めるためにコードをデバッグするのは困難でした。

(ここに書いた例の失敗をデバッグすることは、私が使用しなければならなかった特定のメタクラス アプローチほど難しくありません。)

于 2009-11-05T11:56:33.177 に答える
1

あなたが望むのは「パラメータ化されたテスト」だと思います。

残念ながら unittest モジュールがこれをサポートしているとは思いませんが、この機能を追加すると、次のようになります。

# Will run the test for all combinations of parameters
@RunTestWith(x=[0, 1, 2, 3], y=[-1, 0, 1])
def testMultiplication(self, x, y):
  self.assertEqual(multiplication.multiply(x, y), x*y)

既存の unittest モジュールでは、このような単純なデコレーターではテストを複数回「複製」することはできませんが、デコレーターとメタクラス (メタクラスはすべての 'test*' メソッドを監視する必要があります) の組み合わせを使用してこれを実行できると思いますデコレータが適用されたものを (別の自動生成された名前で) 複製します)。

于 2009-11-04T22:46:01.107 に答える
1

よりデータ指向のアプローチは、Andrew Dalke回答で使用されているものよりも明確である可能性があります。

"""Parametrized unit test.

Builds a single TestCase class which tests if its
  `somevalue` method is equal to the numbers 1 through 4.

This is accomplished by
  creating a list (`cases`)
  of dictionaries which contain test specifications
  and then feeding the list to a function which creates a test case class.

When run, the output shows that three of the four cases fail,
  as expected:

>>> import sys
>>> from unittest import TextTestRunner
>>> run_tests(TextTestRunner(stream=sys.stdout, verbosity=9))
... # doctest: +ELLIPSIS
Test if self.somevalue equals 4 ... FAIL
Test if self.somevalue equals 1 ... FAIL
Test if self.somevalue equals 3 ... FAIL
Test if self.somevalue equals 2 ... ok
<BLANKLINE>
======================================================================
FAIL: Test if self.somevalue equals 4
----------------------------------------------------------------------
Traceback (most recent call last):
  ...
AssertionError: 2 != 4
<BLANKLINE>
======================================================================
FAIL: Test if self.somevalue equals 1
----------------------------------------------------------------------
Traceback (most recent call last):
  ...
AssertionError: 2 != 1
<BLANKLINE>
======================================================================
FAIL: Test if self.somevalue equals 3
----------------------------------------------------------------------
Traceback (most recent call last):
  ...
AssertionError: 2 != 3
<BLANKLINE>
----------------------------------------------------------------------
Ran 4 tests in ...s
<BLANKLINE>
FAILED (failures=3)
"""

from unittest import TestCase, TestSuite, defaultTestLoader

cases = [{'name': "somevalue_equals_one",
          'doc': "Test if self.somevalue equals 1",
          'value': 1},
         {'name': "somevalue_equals_two",
          'doc': "Test if self.somevalue equals 2",
          'value': 2},
         {'name': "somevalue_equals_three",
          'doc': "Test if self.somevalue equals 3",
          'value': 3},
         {'name': "somevalue_equals_four",
          'doc': "Test if self.somevalue equals 4",
          'value': 4}]

class BaseTestCase(TestCase):
    def setUp(self):
        self.somevalue = 2

def test_n(self, n):
    self.assertEqual(self.somevalue, n)

def make_parametrized_testcase(class_name, base_classes, test_method, cases):
    def make_parametrized_test_method(name, value, doc=None):
        def method(self):
            return test_method(self, value)
        method.__name__ = "test_" + name
        method.__doc__ = doc
        return (method.__name__, method)

    test_methods = (make_parametrized_test_method(**case) for case in cases)
    class_dict = dict(test_methods)
    return type(class_name, base_classes, class_dict)


TestCase = make_parametrized_testcase('TestOneThroughFour',
                                      (BaseTestCase,),
                                      test_n,
                                      cases)

def make_test_suite():
    load = defaultTestLoader.loadTestsFromTestCase
    return TestSuite(load(TestCase))

def run_tests(runner):
    runner.run(make_test_suite())

if __name__ == '__main__':
    from unittest import TextTestRunner
    run_tests(TextTestRunner(verbosity=9))

テストが実行される順序を決定するのにどのブードゥーが関与しているかはわかりませんが、少なくとも私にとっては、doctest は一貫してパスします。

より複雑な状況でvaluesは、辞書の要素を、cases引数のリストとキーワード引数の dict を含むタプルに置き換えることができます。ただし、その時点では、基本的に Python で Lisp をコーディングしています。

于 2010-08-18T22:13:40.337 に答える
0

すべてのテストを実行してすべての結果をキャプチャする単一のテスト メソッドを作成し、独自の診断メッセージを stderr に書き込み、サブテストのいずれかが失敗した場合はテストを失敗させます。

def test_with_multiple_parameters(self):
    failed = False
    for k in sorted(self.test_parameters.keys()):
        if not self.my_test(self.test_parameters[k]):
           print >> sys.stderr, "Test {0} failed.".format(k)
           failed = True
    self.assertFalse(failed)            

my_test()もちろんの名前を で始めることはできないことに注意してくださいtest

于 2009-11-05T15:12:44.833 に答える
-1

おそらく次のようなものです:

def test_many(self):
    for n in range(0,1000):
        self.assertEqual(self.somevalue, n)
于 2009-11-04T20:09:18.797 に答える