Pythonで動的な(パラメーター化された)単体テストをどのように生成しますか?


234

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

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つのテストを生成したいと思います。助言がありますか?



2
答えを提供することができる優れたリンク:eli.thegreenplace.net/2014/04/02/...
gaborous

回答:


173

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

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

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

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()

24
実際、このコードはテストごとに異なる名前を生成します(実際には、それ以外の場合は機能しません)。与えられた例では、実行されるテストはそれぞれ「test_foo」、「test_bar」、および「test_lee」という名前になります。したがって、あなたが言及する(そしてそれが大きなものである)利点は、あなたが賢明な名前を生成する限り保持されます。
東寺

1
@codeapeの答えが示すように、鼻はこれを処理します。ただし、鼻はUnicodeを処理しないようです。したがって、私にとってこれは好ましい解決策です。+1
キース・ピンソン

5
したがって、より適切な回答が重複した質問で与えられていることに注意してください:stackoverflow.com/a/2799009/322020-テスト.__name__ =を有効にするために使用する必要があり.exact_methodます
Nakilon

7
クラスを変更するコードがif __name__ == '__main__'条件付きで表示されるのはなぜですか?確かに、インポート時に実行するためにこれの外側に移動する必要があります(いくつかの異なる場所からインポートされた場合でも、Pythonモジュールは一度だけインポートされることに
注意してください

4
これは良い解決策ではないと思います。ユニットテストのコードは、それが呼び出される方法に依存すべきではありません。TestCaseは、nose、pytest、または別のテスト環境で使用できる必要があります。
guettli 2014

146

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コマンドの出力:

> 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)

3
これは、動的にテストケースを生成する非常にクリーンな方法です。
15

ただし、 'setup()'では、yieldの引数としてどの変数が使用されているかがわかりません。実際、setup()は実行中のテスト、またはtest_generator()内で設定された変数を認識しません。これは、setup()内の健全性チェックを複雑にします。これは、一部の人々がpy.testを好む理由の1つです。
Scott Prive

1
更新セクションに賛成。まさに私が必要としたもの。:)
Saurabh Shrivastava

1
unittestバージョンをpytestで実行して、すべてのケースを実行し、最初の失敗したパラメーターで停止しないようにする方法はありますか?
kakk11

1
@ kakk11で述べたように、この回答(および一般にsubTest)はpytestでは機能しません。これは既知の問題です。この作品を作るために積極的に開発のプラグインがあります:github.com/pytest-dev/pytest-subtests
ジェレミー

76

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

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()

1
これは、Seleniumで私にとって非常にうまくいきました。注として、クラスTestSequenceでは、setUp(self)、is_element_present(self、how、what)、... tearDown(self)などの「静的」メソッドを定義できます。「metaclass = TestSequenceMeta」ステートメントの後にそれらを置くとうまくいくようです。
愛と平和-Joe Codeswell

5
このソリューションは、受け入れられたIMHOとして選択されたソリューションよりも優れています。
ペトロスラム

2
@petroslamb __new__メタクラスのメソッドは、最初のインスタンスが作成されたときではなく、クラス自体が定義されたときに呼び出されます。テストメソッドを動的に作成するこのメソッドはunittest、クラスに含まれるテストの数を決定するために使用されるイントロスペクションとの互換性が高いと思います(つまり、クラスのインスタンスを作成する前にテストのリストをコンパイルする場合があります)。
BillyBBone 2016

11
注:のpython 3で、これを変更しますclass TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du

3
dct代わりに使用していただけませんdictか?キーワードを変数名として使用すると、混乱を招き、エラーが発生しやすくなります。
npfoss

49

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で使用できます。


1
Python 3.4以降を使用する場合の最適なソリューション。
Max Malysh

4
unittest2を使用すると、これはPython 2.7でも使用できます。
Bernhard

11
このアプローチと個別のテストを行うことの大きな違いの1つは、テスト状態が毎回リセットされないことです。(つまり、サブテストの合間に実行されるわけsetUp()tearDown()はありません。)
ケビンクリストファーヘンリー

1
@KevinChristopherHenryはい、ただしself.setUp()理論的にはサブテスト内から手動で呼び出すことができます。についてtearDownは、最後に自動的に呼び出せば十分でしょう。
Acumenus 2017年

これは、上記のメタクラスアプローチと組み合わせて使用​​すると強力になると思います。
ネイサンチャペル

36

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内のすべてのTestCaseを実行します。他のテストは、検出メカニズムによって自動的に実行されません。

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


1
上記のコードは失敗します。TypeError:__init __()は最大で2つの引数(4つが指定されます)を取ります
最大

2
コンストラクターの追加パラメーターにnullデフォルトを追加しました。
ハビエル

私は@mojoの回答のnose-parameterizeコードを好みますが、クライアントにとっては、余分な依存関係を回避するのはあまりにも便利なので、これをクライアントに使用します。
セージ

1
このソリューションは、このページで私のお気に入りでした。両方の鼻の現在のトップの答えで提案、及びそのフォークNose2は唯一のメンテナンスであり、後者は、ユーザーが代わりにしようと提案しpytestを。なんてめちゃくちゃ-私はこのようなネイティブのアプローチに固執します!
Sean、

1
ボーナス:paramsで渡される出力のshortDescriptionメソッドを再定義する機能
fun_vit

33

これは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

そして、コマンドでテストを実行します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は、のような多くの機能があるfixturesmarkassert、など...


1
私は、py.testを使用してテストケースをパラメーター化する方法の簡単でわかりやすい例を探していました。どうもありがとうございました!
timgeb 2016年

@timgebお役に立ててうれしいです。その他の例については、py.testタグを確認してください。また、人間が読めるマッチャーを使用してアサートに砂糖を追加するためにhamcrestを使用することをお勧めします。プラス、私たちは持っている魅力-pythonの、見栄えの良いレポート生成のためのpy.test
セルゲイVoronezhskiy

ありがとう。unittestからpy.testに移行し始めました。私は以前TestCase、クラス変数として格納するさまざまな引数を持つ子を動的に作成できる基本クラスを使用していました...これは少し扱いに​​くいものでした。
timgeb 2016年

1
@timgebうん、そうだね。ほとんどのキラー機能のはpy.testあるyield_fixtures。これは、セットアップを実行、いくつかの有用なデータをテストに返し、テストの終了後にティアダウンを実行できます。フィクスチャはパラメータ化することもできます。
Sergey Voronezhskiy 2016年

12

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モジュールで良好に動作します。


6

TestScenariosライブラリを試すとメリットがあります。

testscenariosは、python unittestスタイルのテストに依存関係の注入を提供します。これは、インターフェーステスト(単一のテストスイートを介して多くの実装をテストする)または従来の依存関係注入(テストコード自体の外部に依存関係があるテストを提供し、さまざまな状況で簡単にテストできるようにする)に使用できます。



4

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

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

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

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

nosetestビルドインプラグインのようにパラメーターを渡すこともできますattrib。これにより、特定のパラメーターを使用して特定のテストのみを実行できます。

nosetest -a number=2

私はこのアプローチ、特にそれがサポートするメソッドごとのレベルが好きです。
Matt

3

私はテストを生成するためにメタクラスとデコレーターを使用しています。私の実装python_wrap_casesを確認できます。このライブラリは、テストフレームワークを必要としません。

あなたの例:

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case("foo", "a", "a")
    @wrap_case("bar", "a", "b")
    @wrap_case("lee", "b", "b")
    def testsample(self, name, a, b):
        print "test", name
        self.assertEqual(a, b)

コンソール出力:

testsample_u'bar'_u'a'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test bar
FAIL
testsample_u'foo'_u'a'_u'a' (tests.example.test_stackoverflow.TestSequence) ... test foo
ok
testsample_u'lee'_u'b'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test lee
ok

また、ジェネレータを使用することもできます。たとえば、このコードは、引数a__listとテストのすべての可能な組み合わせを生成し、b__list

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case(a__list=["a", "b"], b__list=["a", "b"])
    def testsample(self, a, b):
        self.assertEqual(a, b)

コンソール出力:

testsample_a(u'a')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... ok
testsample_a(u'a')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... ok

2

私が出会った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)

2
import unittest

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

def add_test_methods(test_class):
    #First element of list is variable "a", then variable "b", then name of test case that will be used as suffix.
    test_list = [[2,3, 'one'], [5,5, 'two'], [0,0, 'three']]
    for case in test_list:
        test = generator(test_class, case[0], case[1])
        setattr(test_class, "test_%s" % case[2], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print 'Setup'
        pass

    def tearDown(self):
        print 'TearDown'
        pass

_add_test_methods(TestAuto)  # It's better to start with underscore so it is not detected as a test itself

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

結果:

>>> 
Setup
FTearDown
Setup
TearDown
.Setup
TearDown
.
======================================================================
FAIL: test_one (__main__.TestAuto)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/inchowar/Desktop/PyTrash/test_auto_3.py", line 5, in test
    self.assertEqual(a, b)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 3 tests in 0.019s

FAILED (failures=1)

1
def add_test_methods関数の軽微な問題。def _add_test_methods 私は考える必要があります
Raychaser 2017年

@Raychaser ...あなたは正しいです..私はそれを修正しましたが、ここでは更新しませんでした....それをキャッチしてくれてありがとう
Arindam Roychowdhury 2017年

1

ここに示すように、メタクラスを使用するだけです。

class DocTestMeta(type):
    """
    Test functions are generated in metaclass due to the way some
    test loaders work. For example, setupClass() won't get called
    unless there are other existing test methods, and will also
    prevent unit test loader logic being called before the test
    methods have been defined.
    """
    def __init__(self, name, bases, attrs):
        super(DocTestMeta, self).__init__(name, bases, attrs)

    def __new__(cls, name, bases, attrs):
        def func(self):
            """Inner test method goes here"""
            self.assertTrue(1)

        func.__name__ = 'test_sample'
        attrs[func.__name__] = func
        return super(DocTestMeta, cls).__new__(cls, name, bases, attrs)

class ExampleTestCase(TestCase):
    """Our example test case, with no methods defined"""
    __metaclass__ = DocTestMeta

出力:

test_sample (ExampleTestCase) ... OK

1

TestSuiteカスタムTestCaseクラスを使用できます。

import unittest

class CustomTest(unittest.TestCase):
    def __init__(self, name, a, b):
        super().__init__()
        self.name = name
        self.a = a
        self.b = b

    def runTest(self):
        print("test", self.name)
        self.assertEqual(self.a, self.b)

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(CustomTest("Foo", 1337, 1337))
    suite.addTest(CustomTest("Bar", 0xDEAD, 0xC0DE))
    unittest.TextTestRunner().run(suite)

TestSuiteが機能している間、引数は__init__関数に渡されません。
jadelord 2017

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

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


1

このソリューションは、と連携unittestし、nosePythonの2とPython 3のために:

#!/usr/bin/env python
import unittest

def make_function(description, a, b):
    def ghost(self):
        self.assertEqual(a, b, description)
    print(description)
    ghost.__name__ = 'test_{0}'.format(description)
    return ghost


class TestsContainer(unittest.TestCase):
    pass

testsmap = {
    'foo': [1, 1],
    'bar': [1, 2],
    'baz': [5, 5]}

def generator():
    for name, params in testsmap.iteritems():
        test_func = make_function(name, params[0], params[1])
        setattr(TestsContainer, 'test_{0}'.format(name), test_func)

generator()

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

アップグレードされたバージョン<3をありがとう@ guillaume-jacquenot!
モップ

0

非常に特殊なスタイルのパラメーター化されたテストで問題が発生していました。すべての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への接続が初期化されるところであるので、LoginTests.setUpでさえ、実行するプラットフォーム。

とにかく、私はこれが彼らのテストの同様の「グローバルな」パラメーター化を行うことを探している誰かに役立つことを願っています!


0

メタクラスベースの回答はPython3でも機能し__metaclass__ますが、属性の代わりに、次のようにmetaclassパラメーターを使用する必要があります。

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

0

メタプログラミングは楽しいですが、途中で始めることができます。ここでのほとんどのソリューションは、次のことを困難にします。

  • 選択的にテストを起動する
  • 指定されたテストの名前のコードに戻る

したがって、私の最初の提案は、単純な/明示的なパスに従うことです(任意のテストランナーで機能します)。

import unittest

class TestSequence(unittest.TestCase):

    def _test_complex_property(self, a, b):
        self.assertEqual(a,b)

    def test_foo(self):
        self._test_complex_property("a", "a")
    def test_bar(self):
        self._test_complex_property("a", "b")
    def test_lee(self):
        self._test_complex_property("b", "b")

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

繰り返してはいけないので、私の2番目の提案は、@ Javierの答えに基づいています。プロパティベースのテストを採用することです。仮説ライブラリ:

  • 「私たち人間よりもテストケースの生成について執拗にうそをつく」
  • 簡単なカウント例を提供します
  • すべてのテストランナーで動作します
  • さらに多くの興味深い機能(統計、追加のテスト出力など)があります。

    クラスTestSequence(unittest.TestCase):

    @given(st.text(), st.text())
    def test_complex_property(self, a, b):
        self.assertEqual(a,b)

特定の例をテストするには、以下を追加します。

    @example("a", "a")
    @example("a", "b")
    @example("b", "b")

特定の例を1つだけ実行するには、他の例をコメント化します(提供されている例が最初に実行されます)。使用したいかもしれません@given(st.nothing())。別のオプションは、ブロック全体を次のように置き換えることです。

    @given(st.just("a"), st.just("b"))

わかりました、あなたは明確なテスト名を持っていません。しかし、多分あなたはただ必要です:

  • テスト中のプロパティの説明的な名前。
  • どの入力が失敗につながるか(偽りの例)。

おかしな例


0

パーティーには超遅刻したが、でこれらを機能させるのに苦労しsetUpClass

これは、動的に割り当てられた属性へのアクセスを提供する@Javierの回答のバージョンsetUpClassです。

import unittest


class GeneralTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print ''
        print cls.p1
        print cls.p2

    def runTest1(self):
        self.assertTrue((self.p2 - self.p1) == 1)

    def runTest2(self):
        self.assertFalse((self.p2 - self.p1) == 2)


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        clsname = 'TestCase_{}_{}'.format(p1, p2)
        dct = {
            'p1': p1,
            'p2': p2,
        }
        cls = type(clsname, (GeneralTestCase,), dct)
        test_cases.addTest(cls('runTest1'))
        test_cases.addTest(cls('runTest2'))
    return test_cases

アウトプット

1
2
..
3
4
..
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

0

ミックスに別のソリューションを投入するだけです;)

これは、parameterized上記と実質的に同じですが、以下に固有unittestです。

def sub_test(param_list):
    """Decorates a test case to run it as a set of subtests."""

    def decorator(f):

        @functools.wraps(f)
        def wrapped(self):
            for param in param_list:
                with self.subTest(**param):
                    f(self, **param)

        return wrapped

    return decorator

使用例:

class TestStuff(unittest.TestCase):
    @sub_test([
        dict(arg1='a', arg2='b'),
        dict(arg1='x', arg2='y'),
    ])
    def test_stuff(self, a, b):
        ...

-1

setattrを使用する以外に、Python 3.2以降、load_testsを使用できます。ブログ投稿blog.livreuro.com/en/coding/python/how-to-generate-discoverable-unit-tests-in-python-dynamically/を参照してください

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()

-1

以下は私の解決策です。1. unittest.Testcaseとunittest discoverで機能するはずです。2.さまざまなパラメーター設定で実行する一連のテストを用意します。3.他のパッケージに依存しない非常に単純なインポートユニットテスト

    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

これは、その場でテストを生成することに関する質問には答えません。
レンツ

-1
import unittest

def generator(test_class, a, b,c,d,name):
    def test(self):
        print('Testexecution=',name)
        print('a=',a)
        print('b=',b)
        print('c=',c)
        print('d=',d)

    return test

def add_test_methods(test_class):
    test_list = [[3,3,5,6, 'one'], [5,5,8,9, 'two'], [0,0,5,6, 'three'],[0,0,2,3,'Four']]
    for case in test_list:
        print('case=',case[0], case[1],case[2],case[3],case[4])
        test = generator(test_class, case[0], case[1],case[2],case[3],case[4])
        setattr(test_class, "test_%s" % case[4], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print ('Setup')
        pass

    def tearDown(self):
        print ('TearDown')
        pass

add_test_methods(TestAuto)

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

そこのフォーマットを失ったようです。
Arturo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.