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

테스트 데이터가 있고 각 항목에 대한 단위 테스트를 만들고 싶습니다. 내 첫 번째 아이디어는 다음과 같이하는 것이 었습니다.

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

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



답변

이것을 “파라미터 화”라고합니다.

이 방법을 지원하는 몇 가지 도구가 있습니다. 예 :

결과 코드는 다음과 같습니다.

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


답변

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)


답변

이것은 메타 클래스를 사용하여 우아하게 해결할 수 있습니다.

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


답변

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에서 사용할 수 있습니다.


답변

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


답변

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, 등 …


답변

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모듈 과 함께 작동 합니다.