파이썬에서 동적 (매개 변수화 된) 단위 테스트를 어떻게 생성합니까?


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

이것의 단점은 한 번의 테스트로 모든 데이터를 처리한다는 것입니다. 각 항목에 대해 하나의 테스트를 즉시 생성하고 싶습니다. 어떤 제안?



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
실제로, bignose,이 코드는 각 테스트마다 다른 이름을 생성합니다 (실제로는 다르게 작동하지 않습니다). 주어진 예제에서 실행 된 테스트의 이름은 각각 "test_foo", "test_bar"및 "test_lee"입니다. 따라서 합리적인 이름을 생성하는 한 언급 한 이점 (그리고 큰 이점)이 유지됩니다.
Toji

1
@codeape 상태의 답변으로 코가 이것을 처리합니다. 그러나 코는 유니 코드를 처리하지 않는 것 같습니다. 따라서 나에게 이것은 바람직한 해결책입니다. +1
Keith Pinson

5
따라서 중복 질문 에보다 적절한 답변이 제공됩니다 . stackoverflow.com/a/2799009/322020- 테스트 .__name__ =를 활성화 하는 데 사용.exact_method
Nakilon

7
클래스를 수정하는 코드가 if __name__ == '__main__'조건부에 나타나는 이유는 무엇 입니까? 분명히 가져 오기 시간에 실행하기 위해 외부로 나가야합니다 (파이썬 모듈은 여러 곳에서 가져온 경우에도 한 번만 가져옵니다.)
SpoonMeiser

4
나는 이것이 좋은 해결책이라고 생각하지 않습니다. unittest의 코드는 그것이 호출되는 방식에 의존해서는 안됩니다. TestCase는 코 또는 pytest 또는 다른 테스트 환경에서 사용할 수 있어야합니다.
guettli

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
테스트 사례를 동적으로 생성하는 매우 깨끗한 방법입니다.
gaborous

그러나 'setup ()'은 어떤 변수가 인수로 사용되는지 알지 못합니다. 실제로 setup ()은 어떤 테스트가 실행되고 있는지 또는 test_generator ()에 vars가 설정되어 있는지 알 수 없습니다. 이것은 setup () 내에서 위생 검사를 복잡하게 만들고 일부 사람들이 py.test를 선호하는 이유 중 하나입니다.
Scott Prive 2016 년

1
업데이트 섹션으로 상향 조정되었습니다. 정확히 내가 필요한 것. :)
Saurabh Shrivastava 2016

1
pytest를 사용하여 unittest 버전을 실행하는 방법이 있습니까? 그래서 모든 경우를 실행하고 첫 번째 실패한 매개 변수에서 멈추지 않습니다.
kakk11

1
@ kakk11에서 언급 했듯이이 답변 (및 일반적으로 subTest)은 pytest에서 작동하지 않습니다. 이것은 알려진 문제입니다. 이 작업을 수행하기 위해 적극적으로 개발 된 플러그인이 있습니다 : github.com/pytest-dev/pytest-subtests
Jérémie

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 코드 웰

5
이 솔루션은 허용 된 IMHO로 선택된 솔루션보다 낫습니다.
petroslamb

2
@petroslamb __new__메타 클래스 의 메소드는 첫 번째 인스턴스가 작성 될 때가 아니라 클래스 자체가 정의 될 때 호출됩니다. 테스트 메소드를 동적으로 작성하는이 메소드가 unittest클래스에 몇 개의 테스트가 있는지 판별하는 데 사용되는 내부 검사와 더 호환된다고 생각합니다 (즉, 해당 클래스의 인스턴스를 작성하기 전에 테스트 목록을 컴파일 할 수 있음).
BillyBBone

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
이 접근 방식과 별도의 테스트를 수행하는 것의 주요 차이점은 테스트 상태가 매번 재설정되지 않는다는 것입니다. (즉, setUp()tearDown()서브 테스트 사이에 실행되지 않습니다.)
케빈 크리스토퍼 헨리

1
@KevinChristopherHenry 네,하지만 self.setUp()이론적으로 하위 테스트 내에서 수동으로 호출 할 수 있습니다. 에 관해서tearDown , 그것은 말의 힘에 충분할에서 자동으로 호출하는 데.
Acumenus

위의 메타 클래스 접근 방식과 함께 사용하면 이것이 강력 할 수 있다고 생각합니다.
Nathan Chappell

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 개 제공)
max

2
생성자 추가 매개 변수에 null 기본값이 추가되었습니다.
Javier

@ mojo 's answer 에서 nose-parameterize 코드를 선호 하지만 클라이언트의 경우 추가 종속성을 피하는 것이 너무 유용하므로이를 위해 사용할 것입니다.
Sage

1
이 솔루션은이 페이지에서 제가 가장 좋아했습니다. 두 현재 최고 대답에 제안, 그 포크 Nose2은 단지 유지 보수, 그리고 후자는 사용자가 대신하려고 제안 pytest을 . 정말 엉망입니다-나는 이런 기본 접근 방식을 고수 할 것입니다!
Sean

1
보너스 : 매개 변수로 전달 된 출력에 대해 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는 같은 더 많은 기능이있다 fixtures, mark, assert, 등 ...


1
py.test로 테스트 사례를 매개 변수화하는 방법에 대한 간단하고 간단한 예를 찾고있었습니다. 대단히 감사합니다!
timgeb

@timgeb 도와 드리겠습니다. 더 많은 예를 보려면 py.test 태그를 확인하십시오 . 또한 인간이 읽을 수있는 뮤 처로 설탕을 약간 첨가하여 햄 크레스트 를 사용하는 것이 좋습니다 . 또한 우리는 다음 과 같은 멋진 보고서 생성 기능인 allure-python 을 가지고 있습니다.py.test
Sergey Voronezhskiy

감사. 방금 unittestpy.test로 이동하기 시작했습니다 . 예전에는 TestCase클래스 변수로 저장할 다른 인수로 자식을 동적으로 만들 수 있는 기본 클래스를 사용했습니다 .
timgeb

1
@timgeb 네 맞습니다. 가장 킬러 기능 의가 py.test있다 yield_fixtures . 어떤 설정을 할 수 있는지 , 유용한 데이터를 테스트에 반환하고 테스트가 끝난 후 해체하십시오 . 고정물매개 변수화 할 수 있습니다 .
Sergey Voronezhskiy

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는 파이썬 단위 테스트 스타일 테스트를위한 깨끗한 의존성 주입을 제공합니다. 인터페이스 테스트 (단일 테스트 스위트를 통해 많은 구현을 테스트) 또는 클래식 종속성 주입 (테스트 코드 자체 외부의 종속성이있는 테스트를 제공하여 다양한 상황에서 쉽게 테스트 할 수 있음)에 사용할 수 있습니다.



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__listb__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의에 REPO에 사용 예 ). 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

@Raychaser ... 당신은 맞습니다 .. 나는 그것을 고쳤지만 여기에서 그것을 업데이트하지 않았다 ...
Arindam Roychowdhury 2012 년

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

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

이 솔루션은 작동 unittestnose파이썬이 파이썬 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

이것으로 내가해야 할 일은 각각의 일반 오래된 TestCase에 간단한 데코레이터 @sauce_labs ()를 추가하는 것뿐입니다. 이제 그들을 실행할 때 모든 테스트 메소드가 매개 변수화되고 이름이 변경되도록 랩핑되고 다시 작성됩니다. 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()

반복하지 말아야 할 두 번째 제안은 @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")

하나의 특정 예제 만 실행하려면 다른 예제를 주석 처리 할 수 ​​있습니다 (제공된 예제가 먼저 실행 됨). 을 사용하고 싶을 수도 있습니다 @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을 사용하는 것 외에도 파이썬 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에 대해 작동해야합니다. 3. 다른 패키지 가져 오기 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

이것은 즉시 테스트를 생성하는 것에 관한 질문에 대답하지 않습니다.
lenz

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