使用基类和子类进行Python单元测试

时间:2009-08-24 16:39:42

标签: python unit-testing testing

我目前有一些单元测试,它们共享一组共同的测试。这是一个例子:

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个测试,而是希望它只运行4个测试,2个来自SubTest1,另外2个来自SubTest2。似乎Python unittest自己运行原始的BaseTest,我需要一种机制来防止这种情况发生。

15 个答案:

答案 0 :(得分:141)

使用多重继承,因此具有常见测试的类本身不会继承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 :(得分:110)

不要使用多重继承,它会咬你later

相反,您可以将基类移动到单独的模块中,或者将其与空白类包装在一起:

import unittest

class BaseTestCases:

    class BaseTest(unittest.TestCase):

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


class SubTest1(BaseTestCases.BaseTest):

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


class SubTest2(BaseTestCases.BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(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

答案 2 :(得分:30)

您可以使用一个命令解决此问题:

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 :(得分:20)

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

答案 4 :(得分:8)

你想要达到什么目的?如果你有通用的测试代码(断言,模板测试等),那么将它们放在没有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()

答案 5 :(得分: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 :(得分:4)

我想到解决这个问题的方法是在使用基类时隐藏测试方法。这样就不会跳过测试,因此在许多测试报告工具中测试结果可以是绿色而不是黄色。

与mixin方法相比,像PyCharm这样的思想不会抱怨基类中缺少单元测试方法。

如果基类继承自此类,则需要覆盖setUpClasstearDownClass方法。

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 = []

答案 7 :(得分:4)

另一个选择是不执行

unittest.main()

而不是你可以使用

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

所以你只在类TestClass

中执行测试

答案 8 :(得分: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())

然后我们去了。

答案 9 :(得分: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()`

答案 10 :(得分:0)

所以这是一个老线程,但我今天遇到了这个问题并想到了我自己的黑客。它使用一个装饰器,当通过基类进行访问时,它使函数的值为None。不需要担心setup和setupclass,因为如果基类没有测试,它们就不会运行。

compression resistance

答案 11 :(得分:0)

您可以在BaseTest类中添加__test_ = False,但是如果添加它,请注意必须在派生类中添加__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()

答案 12 :(得分:0)

从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__的嵌套性质使事情变得更加复杂。

答案 13 :(得分: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 documentation,“每个TestCase实例将运行一个基本方法:名为methodName的方法。”默认的“ runTests”在类上运行所有test *方法,这就是TestCase实例正常工作的方式。但是,当在抽象基类本身中运行时,您可以使用不执行任何操作的方法来简单地覆盖该行为。

副作用是您的测试计数将增加一:runNoTestsInBaseClass“ test”在BaseClass上运行时被视为成功测试。

(如果您仍然在Python 2.7中也可以使用。只需将super()更改为super(BaseTest, self)。)

答案 14 :(得分:-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次测试

     

调用BaseTest:testCommon调用
  SubTest1:testSub1调用
  BaseTest:testCommon调用
  SubTest2:testSub2

来自documentation

  

TestCase.setUp()
  方法叫做   准备测试夹具。这是   在呼叫之前立即呼叫   测试方法;提出的任何例外   这种方法将被认为是一种   错误而不是测试失败。该   默认实现什么都不做。