기본 및 하위 클래스를 사용한 Python 단위 테스트


149

현재 공통 테스트 세트를 공유하는 몇 가지 단위 테스트가 있습니다. 예를 들면 다음과 같습니다.

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

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

위의 결과는 다음과 같습니다.

Calling BaseTest:testCommon
.Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

첫 번째 testCommon가 호출되지 않도록 위의 내용을 다시 쓰는 방법이 있습니까?

편집 : 위의 5 가지 테스트를 실행하는 대신 SubTest1에서 2 개, SubTest2에서 2 개를 4 개만 실행하려고합니다. 파이썬 unittest는 독자적인 BaseTest를 자체적으로 실행하고있는 것으로 보이며 그 발생을 막기위한 메커니즘이 필요합니다.


아무도 언급하지 않았지만 기본 부분을 변경하고 BaseTest의 모든 하위 클래스가있는 테스트 스위트를 실행할 수있는 옵션이 있습니까?
kon psych

답변:


154

다중 상속을 사용하므로 일반적인 테스트를 수행하는 클래스 자체는 TestCase에서 상속되지 않습니다.

import unittest

class CommonTests(object):
    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(unittest.TestCase, CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(unittest.TestCase, CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

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

1
그것은 지금까지 가장 우아한 솔루션입니다.
Thierry Lam

27
이 메소드는 기본 클래스의 순서를 반대로하는 경우 setUp 및 tearDown 메소드에만 작동합니다. 메소드는 unittest.TestCase에 정의되어 있으며 super ()를 호출하지 않으므로 CommonTests의 setUp 및 tearDown 메소드가 MRO에 먼저 있어야합니다. 그렇지 않으면 전혀 호출되지 않습니다.
Ian Clelland

32
Ian Clelland의 발언을 명확히하기 위해 나와 같은 사람들에게 더 명확해질 것입니다. 클래스에 메소드를 추가 setUp하고 파생 클래스의 각 테스트에 대해 호출하려면 기본 클래스의 순서를 바꿔야합니다. 그렇게 될 것입니다 : . tearDownCommonTestsclass SubTest1(CommonTests, unittest.TestCase)
데니스 골 로마 조프

6
나는이 접근법의 팬이 아니다. 이렇게하면 클래스에서 unittest.TestCase 에서 모두 상속해야하는 코드 계약이 설정 CommonTests됩니다. 나는 생각 setUpClass아래의 방법이 최고이며, 인간의 오류 가능성이 줄어 듭니다. 조금 더 해킹되었지만 테스트 실행 인쇄물에서 건너 뛰기 메시지를 피하는 컨테이너 클래스에서 BaseTest 클래스를 래핑하거나 래핑하십시오.
David Sanders

10
이 클래스의 문제는 pylint가 CommonTests해당 클래스에 존재하지 않는 메소드를 호출 하기 때문에 적합 하다는 것입니다.
MadScientist

146

다중 상속을 사용하지 마십시오 . 나중에 물릴 것 입니다.

대신 기본 클래스를 별도의 모듈로 옮기거나 빈 클래스로 래핑 할 수 있습니다.

class BaseTestCases:

    class BaseTest(unittest.TestCase):

        def testCommon(self):
            print('Calling BaseTest:testCommon')
            value = 5
            self.assertEqual(value, 5)


class SubTest1(BaseTestCases.BaseTest):

    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTestCases.BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

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

출력 :

Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

6
이것은 내가 가장 좋아하는 것입니다. 해킹이 가장 적은 수단이며 재정의 메서드를 방해하지 않으며 MRO를 변경하지 않으며 기본 클래스에서 setUp, setUpClass 등을 정의 할 수 있습니다.
Hannes

6
난 심각하지 마십시오 (여기서 마법에서 오는가?)하지만 : 자바에서오고, 나는 ... 다중 상속을 싫어 나를에 따라 지금까지 최고의 솔루션입니다
에두아르 베르트

4
@Edouardb unittest는 TestCase에서 상속 된 모듈 수준 클래스 만 실행합니다. 그러나 BaseTest는 모듈 수준이 아닙니다.
JoshB

매우 유사한 대안으로, 호출 될 때 ABC를 리턴하는 인수 없음 함수 내에서 ABC를 정의 할 수 있습니다.
Anakhand

34

단일 명령으로이 문제를 해결할 수 있습니다.

del(BaseTest)

따라서 코드는 다음과 같습니다.

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

del(BaseTest)

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

3
BaseTest는 정의되는 동안 모듈의 멤버이므로 SubTest의 기본 클래스로 사용할 수 있습니다. 정의가 완료되기 직전에 del ()은이를 멤버로 제거하므로 unittest 프레임 워크는 모듈에서 TestCase 서브 클래스를 검색 할 때이를 찾지 않습니다.
mhsmith

3
이것은 멋진 답변입니다! 그의 솔루션에서는 self.assert*표준 객체에 메소드가 없기 때문에 pylint에서 구문 오류가 발생하기 때문에 @MatthewMarshall보다 더 좋아 합니다.
SimplyKnownAsG

1
BaseTest가 기본 클래스 또는 하위 클래스의 다른 곳에서 참조되는 경우 (예 : 메소드 재정의에서 super ()를 호출 할 때) 작동하지 않습니다. super( BaseTest, cls ).setUpClass( )
Hannes

1
@Hannes 적어도 파이썬 3에서는 하위 클래스를 BaseTest통해 참조 super(self.__class__, self)하거나 super()서브 클래스 에서만 참조 할 수 있지만 생성자를 상속 해야하는 경우는 아닙니다 . 어쩌면 기본 클래스가 자체를 참조해야 할 때 그러한 "익명"대안이있을 수도 있습니다 (클래스가 자체를 참조해야하는 시점에 대한 아이디어는 아님).
Stein

29

Matthew Marshall의 대답은 훌륭하지만 각 테스트 사례에서 오류가 발생하기 쉬운 두 클래스에서 상속해야합니다. 대신, 나는 이것을 사용합니다 (python> = 2.7) :

class BaseTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        if cls is BaseTest:
            raise unittest.SkipTest("Skip BaseTest tests, it's a base class")
        super(BaseTest, cls).setUpClass()

3
그거 멋지다. 건너 뛰기를 사용해야하는 방법이 있습니까? 나에게 건너 뛰기는 바람직하지 않으며 현재 테스트 계획 (코드 또는 테스트 중 하나)의 문제를 나타내는 데 사용됩니까?
Zach Young

잘 모르겠습니다. 아마도 다른 답변이 도움이 될 수 있습니다.
Dennis Golomazov

@ZacharyYoung이 문제를 해결하려고했습니다. 제 답변을 참조하십시오.
simonzack

두 클래스에서 상속받을 때 본질적으로 오류가 발생하기 쉬운 것은 명확하지 않습니다
jwg

@jwg 허용되는 답변에 대한 의견을보십시오. :) 두 개의 기본 클래스에서 각 테스트 클래스를 상속해야합니다. 올바른 순서를 유지해야합니다. 다른 기본 테스트 클래스를 추가하려면 클래스를 상속해야합니다. 믹스 인에는 아무런 문제가 없지만이 경우 간단한 건너 뛰기로 바꿀 수 있습니다.
Dennis Golomazov

7

당신은 무엇을 달성하려고합니까? 당신이 일반적인 테스트 코드 (주장, 템플릿 테스트 등)이 있다면,로 시작하지 않는 방법에 배치 test그래서 unittest그들을로드되지 않습니다.

import unittest

class CommonTests(unittest.TestCase):
      def common_assertion(self, foo, bar, baz):
          # whatever common code
          self.assertEqual(foo(bar), baz)

class BaseTest(CommonTests):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)

class SubTest2(CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

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

1
귀하의 제안에 따라 서브 클래스를 테스트 할 때 common_assertion ()이 여전히 자동으로 실행됩니까?
스튜어트

@Stewart 아니요. 기본 설정은 "test"로 시작하는 메소드 만 실행하는 것입니다.
CS

6

매튜의 대답은 내가 2.5 살 때부터 사용해야했던 것입니다. 그러나 2.7부터 건너 뛰려는 모든 테스트 메소드에서 @ unittest.skip () 데코레이터를 사용할 수 있습니다.

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

기본 유형을 확인하려면 자체 건너 뛰기 데코레이터를 구현해야합니다. 이 기능을 전에 사용하지는 않았지만 머리 꼭대기에서 BaseTest를 마커 유형으로 사용하여 건너 뛰기를 조정할 수 있습니다 .

def skipBaseTest(obj):
    if type(obj) is BaseTest:
        return unittest.skip("BaseTest tests skipped")
    return lambda func: func

6

내가 이것을 해결하려고 생각한 방법은 기본 클래스가 사용되는 경우 테스트 메소드를 숨기는 것입니다. 이렇게하면 테스트를 건너 뛰지 않으므로 많은 테스트보고 도구에서 테스트 결과가 노란색 대신 녹색이 될 수 있습니다.

PyCharm과 같은 ide는 mixin 메소드와 비교할 때 단위 테스트 메소드가 기본 클래스에서 누락되었다고 불평하지 않습니다.

기본 클래스가이 클래스에서 상속되면 setUpClassand tearDownClass메소드 를 대체해야 합니다.

class BaseTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls._test_methods = []
        if cls is BaseTest:
            for name in dir(cls):
                if name.startswith('test') and callable(getattr(cls, name)):
                    cls._test_methods.append((name, getattr(cls, name)))
                    setattr(cls, name, lambda self: None)

    @classmethod
    def tearDownClass(cls):
        if cls is BaseTest:
            for name, method in cls._test_methods:
                setattr(cls, name, method)
            cls._test_methods = []

5

__test_ = FalseBaseTest 클래스에 추가 할 수 있지만 추가하면 __test__ = True테스트를 실행할 수 있도록 파생 클래스를 추가해야합니다 .

import unittest

class BaseTest(unittest.TestCase):
    __test__ = False

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):
    __test__ = True

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    __test__ = True

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

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

이 솔루션은 unittest의 자체 테스트 검색 / 테스트 실행기와 함께 작동하지 않습니다. (코와 같은 대체 테스트 러너를 사용해야한다고 생각합니다.)
medmunds

4

다른 옵션은 실행하지 않는 것입니다

unittest.main()

그 대신에 당신은 사용할 수 있습니다

suite = unittest.TestLoader().loadTestsFromTestCase(TestClass)
unittest.TextTestRunner(verbosity=2).run(suite)

따라서 클래스에서만 테스트를 실행하십시오. TestClass


가장 해키 해법입니다. unittest.main()기본 제품군으로 수집되는 내용을 수정하는 대신 명시 적 제품군을 구성하고 테스트를 실행합니다.
zgoda

1

@Vladim P. ( https://stackoverflow.com/a/25695512/2451329 ) 와 거의 동일 하지만 약간 수정되었습니다.

import unittest2


from some_module import func1, func2


def make_base_class(func):

    class Base(unittest2.TestCase):

        def test_common1(self):
            print("in test_common1")
            self.assertTrue(func())

        def test_common2(self):
            print("in test_common1")
            self.assertFalse(func(42))

    return Base



class A(make_base_class(func1)):
    pass


class B(make_base_class(func2)):

    def test_func2_with_no_arg_return_bar(self):
        self.assertEqual("bar", func2())

그리고 우리는 간다.


1

Python 3.2부터 test_loader 함수를 모듈에 추가 하여 테스트 발견 메커니즘에 의해 발견되는 테스트 (있는 경우)를 제어 할 수 있습니다 .

예를 들어, 다음은 원본 포스터 SubTest1SubTest2테스트 케이스 만 무시하고 로드합니다 Base.

def load_tests(loader, standard_tests, pattern):
    suite = TestSuite()
    suite.addTests([SubTest1, SubTest2])
    return suite

standard_tests( TestSuite기본 로더가 찾은 테스트를 포함하는) 반복 Base하고 suite대신 모든 것을 복사하는 것이 가능 해야 하지만 중첩 된 특성으로 TestSuite.__iter__인해 훨씬 ​​복잡합니다.


0

testCommon 메소드의 이름을 다른 것으로 바꾸십시오. Unittest는 (보통) 'test'가없는 것을 건너 뜁니다.

빠르고 간단한

  import unittest

  class BaseTest(unittest.TestCase):

   def methodCommon(self):
       print 'Calling BaseTest:testCommon'
       value = 5
       self.assertEquals(value, 5)

  class SubTest1(BaseTest):

      def testSub1(self):
          print 'Calling SubTest1:testSub1'
          sub = 3
          self.assertEquals(sub, 3)


  class SubTest2(BaseTest):

      def testSub2(self):
          print 'Calling SubTest2:testSub2'
          sub = 4
          self.assertEquals(sub, 4)

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

2
하위 테스트 중 하나에서 methodCommon 테스트를 실행하지 않은 결과가 발생합니다.
Pepper Lebeck-Jobe

0

그래서 이것은 일종의 오래된 스레드이지만 오늘이 문제를 발견하고 내 자신의 해킹을 생각했습니다. 기본 클래스를 통해 액세스 할 때 함수의 값을 None으로 만드는 데코레이터를 사용합니다. 기본 클래스에 테스트가 없으면 실행되지 않으므로 설정 및 설정 클래스에 대해 걱정할 필요가 없습니다.

import types
import unittest


class FunctionValueOverride(object):
    def __init__(self, cls, default, override=None):
        self.cls = cls
        self.default = default
        self.override = override

    def __get__(self, obj, klass):
        if klass == self.cls:
            return self.override
        else:
            if obj:
                return types.MethodType(self.default, obj)
            else:
                return self.default


def fixture(cls):
    for t in vars(cls):
        if not callable(getattr(cls, t)) or t[:4] != "test":
            continue
        setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t)))
    return cls


@fixture
class BaseTest(unittest.TestCase):
    def testCommon(self):
        print('Calling BaseTest:testCommon')
        value = 5
        self.assertEqual(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

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

0

다음은 문서화 된 단위 테스트 기능 만 사용하고 테스트 결과에서 "건너 뛰기"상태를 피하는 솔루션입니다.

class BaseTest(unittest.TestCase):

    def __init__(self, methodName='runTest'):
        if self.__class__ is BaseTest:
            # don't run these tests in the abstract base implementation
            methodName = 'runNoTestsInBaseClass'
        super().__init__(methodName)

    def runNoTestsInBaseClass(self):
        pass

    def testCommon(self):
        # everything else as in the original question

작동 방식 : unittest.TestCase설명서에 따라 따라 "TestCase의 각 인스턴스는 단일 기본 메소드 (methodName이라는 메소드)를 실행합니다." 기본 "runTests"는 클래스에서 모든 test * 메소드를 실행합니다. 이것이 TestCase 인스턴스가 정상적으로 작동하는 방식입니다. 그러나 추상 기본 클래스 자체에서 실행할 때는 아무 것도 수행하지 않는 메서드로 해당 동작을 재정의 할 수 있습니다.

부작용은 테스트 수가 1 씩 증가한다는 것입니다. runNoTestsInBaseClass "test"는 BaseClass에서 실행될 때 성공적인 테스트로 계산됩니다.

(여전히 파이썬 2.7에서도 작동합니다.로 변경 super()하십시오 super(BaseTest, self).)


-2

BaseTest 메소드 이름을 setUp으로 변경하십시오.

class BaseTest(unittest.TestCase):
    def setUp(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

산출:

0.000 초 안에 2 번 테스트 실행

Calling BaseTest : testCommon 호출
SubTest1 : testSub1 호출
BaseTest : testCommon 호출
SubTest2 : testSub2

로부터 문서 :

TestCase.setUp ()
테스트 픽스처를 준비하기 위해 호출되는 메소드. 테스트 메소드를 호출하기 직전에 호출됩니다. 이 방법으로 발생한 예외는 테스트 실패가 아닌 오류로 간주됩니다. 기본 구현은 아무 것도 수행하지 않습니다.


그것은 작동합니다, 만약 내가 testCommon을 가지고 있다면, 그것들을 모두 아래에 두어야 setUp합니까?
Thierry Lam

1
예, 실제 테스트 사례가 아닌 모든 코드를 설정해야합니다.
Brian R. Bondy

그러나 서브 클래스에 둘 이상의 test...메소드 가있는 경우 해당 메소드 setUp마다 한 번씩 반복해서 실행됩니다. 따라서 테스트를하는 것은 좋지 않습니다!
Alex Martelli

좀 더 복잡한 시나리오에서 실행될 때 OP가 무엇을 원했는지 확실하지 않습니다.
Brian R. Bondy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.