312

ある種のテスト データがあり、各項目の単体テストを作成したいと考えています。私の最初のアイデアは、次のようにすることでした。

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequence(unittest.TestCase):
    def testsample(self):
        for name, a,b in l:
            print "test", name
            self.assertEqual(a,b)

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

これの欠点は、1 つのテストですべてのデータを処理することです。その場で各項目に対して 1 つのテストを生成したいと思います。助言がありますか?

4

25 に答える 25

239

これを「パラメータ化」と呼びます。

このアプローチをサポートするツールがいくつかあります。例えば:

結果のコードは次のようになります。

from parameterized import parameterized

class TestSequence(unittest.TestCase):
    @parameterized.expand([
        ["foo", "a", "a",],
        ["bar", "a", "b"],
        ["lee", "b", "b"],
    ])
    def test_sequence(self, name, a, b):
        self.assertEqual(a,b)

テストを生成するもの:

test_sequence_0_foo (__main__.TestSequence) ... ok
test_sequence_1_bar (__main__.TestSequence) ... FAIL
test_sequence_2_lee (__main__.TestSequence) ... ok

======================================================================
FAIL: test_sequence_1_bar (__main__.TestSequence)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/parameterized/parameterized.py", line 233, in <lambda>
    standalone_func = lambda *a: func(*(a + p.args), **p.kwargs)
  File "x.py", line 12, in test_sequence
    self.assertEqual(a,b)
AssertionError: 'a' != 'b'

歴史的な理由から、2008年頃の元の回答を残します):

私は次のようなものを使用します:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequense(unittest.TestCase):
    pass

def test_generator(a, b):
    def test(self):
        self.assertEqual(a,b)
    return test

if __name__ == '__main__':
    for t in l:
        test_name = 'test_%s' % t[0]
        test = test_generator(t[1], t[2])
        setattr(TestSequense, test_name, test)
    unittest.main()
于 2008-08-28T18:02:33.027 に答える
210

unittest の使用(3.4 以降)

Python 3.4 以降、標準ライブラリunittestパッケージにはsubTestコンテキスト マネージャが含まれています。

ドキュメントを参照してください。

例:

from unittest import TestCase

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

class TestDemonstrateSubtest(TestCase):
    def test_works_as_expected(self):
        for p1, p2 in param_list:
            with self.subTest():
                self.assertEqual(p1, p2)

カスタム メッセージとパラメータ値をsubTest()次のように指定することもできます。

with self.subTest(msg="Checking if p1 equals p2", p1=p1, p2=p2):

鼻の使い方

ノーズテスト フレームワークはこれをサポートします

例 (以下のコードは、テストを含むファイルの内容全体です):

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

def test_generator():
    for params in param_list:
        yield check_em, params[0], params[1]

def check_em(a, b):
    assert a == b

ノーズテスト コマンドの出力:

> nosetests -v
testgen.test_generator('a', 'a') ... ok
testgen.test_generator('a', 'b') ... FAIL
testgen.test_generator('b', 'b') ... ok

======================================================================
FAIL: testgen.test_generator('a', 'b')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.5/site-packages/nose-0.10.1-py2.5.egg/nose/case.py", line 203, in runTest
    self.test(*self.arg)
  File "testgen.py", line 7, in check_em
    assert a == b
AssertionError

----------------------------------------------------------------------
Ran 3 tests in 0.006s

FAILED (failures=1)
于 2008-08-29T07:10:31.867 に答える
87

これは、メタクラスを使用してエレガントに解決できます。

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequenceMeta(type):
    def __new__(mcs, name, bases, dict):

        def gen_test(a, b):
            def test(self):
                self.assertEqual(a, b)
            return test

        for tname, a, b in l:
            test_name = "test_%s" % tname
            dict[test_name] = gen_test(a,b)
        return type.__new__(mcs, name, bases, dict)

class TestSequence(unittest.TestCase):
    __metaclass__ = TestSequenceMeta

if __name__ == '__main__':
    unittest.main()
于 2014-01-01T16:52:24.967 に答える
56

Python 3.4 以降、この目的のためにサブテストがunittestに導入されました。詳細については、ドキュメントを参照してください。TestCase.subTest は、テスト内のアサートを分離して、パラメーター情報とともに失敗が報告されるようにするコンテキスト マネージャーですが、テストの実行を停止しません。ドキュメントの例は次のとおりです。

class NumbersTest(unittest.TestCase):

def test_even(self):
    """
    Test that numbers between 0 and 5 are all even.
    """
    for i in range(0, 6):
        with self.subTest(i=i):
            self.assertEqual(i % 2, 0)

テスト実行の出力は次のようになります。

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

これはunittest2の一部でもあるため、以前のバージョンの Python で使用できます。

于 2015-04-01T06:46:45.903 に答える
41

load_testsは、TestSuite を動的に作成するために 2.7 で導入されたあまり知られていないメカニズムです。これにより、パラメータ化されたテストを簡単に作成できます。

例えば:

import unittest

class GeneralTestCase(unittest.TestCase):
    def __init__(self, methodName, param1=None, param2=None):
        super(GeneralTestCase, self).__init__(methodName)

        self.param1 = param1
        self.param2 = param2

    def runTest(self):
        pass  # Test that depends on param 1 and 2.


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        test_cases.addTest(GeneralTestCase('runTest', p1, p2))
    return test_cases

そのコードは、load_tests によって返された TestSuite 内のすべての TestCases を実行します。検出メカニズムによって他のテストが自動的に実行されることはありません。

または、次のチケットに示されているように継承を使用することもできます: http://bugs.python.org/msg151444

于 2014-05-07T03:55:21.470 に答える
36

pytestを使用して実行できます。コンテンツを含むファイルtest_me.pyを書くだけです:

import pytest

@pytest.mark.parametrize('name, left, right', [['foo', 'a', 'a'],
                                               ['bar', 'a', 'b'],
                                               ['baz', 'b', 'b']])
def test_me(name, left, right):
    assert left == right, name

そして command でテストを実行しますpy.test --tb=short test_me.py。出力は次のようになります。

=========================== test session starts ============================
platform darwin -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_me.py .F.

================================= FAILURES =================================
_____________________________ test_me[bar-a-b] _____________________________
test_me.py:8: in test_me
    assert left == right, name
E   AssertionError: bar
==================== 1 failed, 2 passed in 0.01 seconds ====================

簡単です!また、 pytestには、、、などfixturesの機能がさらにあります。markassert

于 2014-09-02T15:08:01.877 に答える
15

ddtライブラリを使用します。テストメソッド用のシンプルなデコレータを追加します:

import unittest
from ddt import ddt, data
from mycode import larger_than_two

@ddt
class FooTestCase(unittest.TestCase):

    @data(3, 4, 12, 23)
    def test_larger_than_two(self, value):
        self.assertTrue(larger_than_two(value))

    @data(1, -3, 2, 0)
    def test_not_larger_than_two(self, value):
        self.assertFalse(larger_than_two(value))

このライブラリは でインストールできますpip。を必要とせずnose、標準ライブラリunittestモジュールとうまく連携します。

于 2015-04-21T08:24:35.353 に答える
6

ファズまたはプロパティベースのテストを追加する仮説もあります。

これは非常に強力なテスト方法です。

于 2015-11-24T02:00:00.277 に答える
4

nodes-ittrプラグイン ( )を使用できますpip install nose-ittr

既存のテストとの統合は非常に簡単で、最小限の変更 (もしあれば) が必要です。また、ノーズマルチプロセッシング プラグインもサポートしています。

setupテストごとにカスタマイズ機能を使用することもできます。

@ittr(number=[1, 2, 3, 4])
def test_even(self):
    assert_equal(self.number % 2, 0)

nosetest組み込みの plugin のようにパラメーターを渡すこともできますattrib。このようにして、特定のパラメーターを使用して特定のテストのみを実行できます。

nosetest -a number=2
于 2014-12-02T13:39:03.587 に答える
2

先日、 radonのソースコードを見ているときにParamUnittestに出会いました( GitHub リポジトリでの使用例)。TestCase を拡張する他のフレームワーク (Nose など) と連携する必要があります。

次に例を示します。

import unittest
import paramunittest


@paramunittest.parametrized(
    ('1', '2'),
    #(4, 3),    <---- Uncomment to have a failing test
    ('2', '3'),
    (('4', ), {'b': '5'}),
    ((), {'a': 5, 'b': 6}),
    {'a': 5, 'b': 6},
)
class TestBar(TestCase):
    def setParameters(self, a, b):
        self.a = a
        self.b = b

    def testLess(self):
        self.assertLess(self.a, self.b)
于 2015-03-06T01:21:50.240 に答える
1

特に、データのコレクションに対してわずかに異なるプロセスを実行するテストを生成する必要がある場合は、これが私の目的に適していることがわかりました。

import unittest

def rename(newName):
    def renamingFunc(func):
        func.__name__ == newName
        return func
    return renamingFunc

class TestGenerator(unittest.TestCase):

    TEST_DATA = {}

    @classmethod
    def generateTests(cls):
        for dataName, dataValue in TestGenerator.TEST_DATA:
            for func in cls.getTests(dataName, dataValue):
                setattr(cls, "test_{:s}_{:s}".format(func.__name__, dataName), func)

    @classmethod
    def getTests(cls):
        raise(NotImplementedError("This must be implemented"))

class TestCluster(TestGenerator):

    TEST_CASES = []

    @staticmethod
    def getTests(dataName, dataValue):

        def makeTest(case):

            @rename("{:s}".format(case["name"]))
            def test(self):
                # Do things with self, case, data
                pass

            return test

        return [makeTest(c) for c in TestCluster.TEST_CASES]

TestCluster.generateTests()

このTestGeneratorクラスを使用して、 のようなさまざまなテスト ケースのセットを生成できますTestCluster

TestClusterインターフェイスの実装と考えることができTestGeneratorます。

于 2019-08-06T14:10:26.113 に答える
1

私は非常に特殊なスタイルのパラメーター化されたテストで問題を抱えていました。Selenium テストはすべてローカルで実行できますが、SauceLabs の複数のプラットフォームに対してリモートで実行することもできます。基本的に、既に作成されたテスト ケースを大量に取得し、可能な限り最小限のコード変更でそれらをパラメーター化したかったのです。さらに、パラメーターを setUp メソッドに渡すことができるようにする必要がありましたが、これは他の方法では解決できませんでした。

これが私が思いついたものです:

import inspect
import types

test_platforms = [
    {'browserName': "internet explorer", 'platform': "Windows 7", 'version': "10.0"},
    {'browserName': "internet explorer", 'platform': "Windows 7", 'version': "11.0"},
    {'browserName': "firefox", 'platform': "Linux", 'version': "43.0"},
]


def sauce_labs():
    def wrapper(cls):
        return test_on_platforms(cls)
    return wrapper


def test_on_platforms(base_class):
    for name, function in inspect.getmembers(base_class, inspect.isfunction):
        if name.startswith('test_'):
            for platform in test_platforms:
                new_name = '_'.join(list([name, ''.join(platform['browserName'].title().split()), platform['version']]))
                new_function = types.FunctionType(function.__code__, function.__globals__, new_name,
                                                  function.__defaults__, function.__closure__)
                setattr(new_function, 'platform', platform)
                setattr(base_class, new_name, new_function)
            delattr(base_class, name)

    return base_class

これで、単純なデコレータ @sauce_labs() を通常の古い各 TestCase に追加するだけで済みました。これらを実行すると、すべてのテスト メソッドがパラメータ化され、名前が変更されるように、ラップされて書き直されます。LoginTests.test_login(self) は、LoginTests.test_login_internet_explorer_10.0(self)、LoginTests.test_login_internet_explorer_11.0(self)、および LoginTests.test_login_firefox_43.0(self) として実行され、それぞれにパラメーター self.platform があり、ブラウザー/これは、SauceLabs への接続が初期化される場所であるため、私のタスクにとって非常に重要です。

とにかく、これが、テストの同様の「グローバル」パラメーター化を行うことを検討している誰かの助けになることを願っています!

于 2016-04-22T08:02:46.267 に答える
-1

setattr を使用する以外に、Python 3.2 以降ではload_testsを使用できます。

class Test(unittest.TestCase):
    pass

def _test(self, file_name):
    open(file_name, 'r') as f:
        self.assertEqual('test result',f.read())

def _generate_test(file_name):
    def test(self):
        _test(self, file_name)
    return test

def _generate_tests():
    for file in files:
        file_name = os.path.splitext(os.path.basename(file))[0]
        setattr(Test, 'test_%s' % file_name, _generate_test(file))

test_cases = (Test,)

def load_tests(loader, tests, pattern):
    _generate_tests()
    suite = TestSuite()
    for test_class in test_cases:
        tests = loader.loadTestsFromTestCase(test_class)
        suite.addTests(tests)
    return suite

if __name__ == '__main__':
    _generate_tests()
    unittest.main()
于 2016-05-26T20:20:39.993 に答える
-1

以下は私の解決策です。これは次の場合に便利です。

  1. unittest.Testcase と unittest discover で動作するはずです

  2. さまざまなパラメーター設定に対して一連のテストを実行します。

  3. 非常にシンプルで、他のパッケージに依存しません

     import unittest
    
     class BaseClass(unittest.TestCase):
         def setUp(self):
             self.param = 2
             self.base = 2
    
         def test_me(self):
             self.assertGreaterEqual(5, self.param+self.base)
    
         def test_me_too(self):
             self.assertLessEqual(3, self.param+self.base)
    
    
      class Child_One(BaseClass):
         def setUp(self):
             BaseClass.setUp(self)
             self.param = 4
    
    
      class Child_Two(BaseClass):
         def setUp(self):
             BaseClass.setUp(self)
             self.param = 1
    
于 2016-08-02T16:35:17.440 に答える