问题:具有基类和子类的Python单元测试
我目前有一些单元测试,它们共享一组通用的测试。这是一个例子:
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,我需要一种机制来防止这种情况的发生。
回答 0
使用多重继承,因此具有通用测试的类本身不会继承自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
不要使用多重继承,它会咬你以后。
相反,您可以将您的基类移至单独的模块中,或将其与空白类包装在一起:
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
回答 2
您可以使用单个命令解决此问题:
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
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
您想达到什么目的?如果您有通用的测试代码(断言,模板测试等),则将它们放在没有前缀的方法中,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
Matthew的答案是我需要使用的答案,因为我仍然是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
我想解决此问题的一种方法是通过隐藏测试方法(如果使用了基类)。这样就不会跳过测试,因此在许多测试报告工具中,测试结果可以是绿色而不是黄色。
与mixin方法相比,ide之类的PyCharm不会抱怨基类中缺少单元测试方法。
如果基类从该类继承,则它将需要重写setUpClass
和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 = []
回答 7
您可以添加__test_ = False
BaseTest类,但是如果添加它,请注意必须添加__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()
回答 8
另一种选择是不执行
unittest.main()
除此之外,您可以使用
suite = unittest.TestLoader().loadTestsFromTestCase(TestClass)
unittest.TextTestRunner(verbosity=2).run(suite)
所以你只执行类中的测试 TestClass
回答 9
我所做的与@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())
然后我们去。
回答 10
从Python 3.2开始,您可以将test_loader函数添加到模块中,以控制由测试发现机制找到哪些测试(如果有)。
例如,下面将只加载原来的海报的SubTest1
和SubTest2
测试用例,忽略Base
:
def load_tests(loader, standard_tests, pattern):
suite = TestSuite()
suite.addTests([SubTest1, SubTest2])
return suite
它应该可以遍历standard_tests
(一个TestSuite
包含默认加载器发现测试)并复制,但Base
要suite
代替,但嵌套性质TestSuite.__iter__
品牌是一个复杂得多。
回答 11
只需将testCommon方法重命名为其他名称即可。单元测试(通常)会跳过其中没有“测试”的所有内容。
快速简单
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()`
回答 12
因此,这是一个旧线程,但是我今天遇到了这个问题,并为此想到了自己的技巧。它使用一个装饰器,当通过基类访问时,该装饰器使函数的值变为None。无需担心setup和setupclass,因为如果基类没有测试,它们将不会运行。
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()
回答 13
将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
从文档中:
TestCase.setUp()
用于准备测试治具的方法。在调用测试方法之前立即调用该方法。此方法引发的任何异常都将被视为错误而不是测试失败。默认实现不执行任何操作。