基类和子类的 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,我需要一种机制来防止这种情况发生。

75777 次浏览

将 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秒内做了两次测试

调用 BaseTest: testCommon 调用
调用
BaseTest: testCommon 调用
SubTest2: testSub2

来自 文件:

SetUp ()
调用的 准备测试夹具,这是 在调用 测试方法; 由 此方法将被视为 错误而不是测试失败 默认实现不执行任何操作。

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

使用多重继承,这样带有常见测试的类本身就不会从 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()

马修的答案是我需要用到的因为我还在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

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

你可以用一个简单的命令来解决这个问题:

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

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

与 Mixin 方法相比,ide 类似 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 = []

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

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

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

另一种选择是不执行

unittest.main()

而不是你可以使用的

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

所以您只能在类 TestClass中执行测试

只要将 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()`

我做的和@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())

好了。

所以这是一个老线程,但我今天遇到了这个问题,并想到了我自己的黑客技术。它使用一个装饰符,当通过基类访问时,该装饰符使函数的值为 Nothing。不需要担心 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()

您可以在 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()

从 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__的嵌套性质使得这种复杂得多。

这里有一个解决方案,它只使用文档化的单元测试特性,并避免在测试结果中出现“跳过”状态:

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 的方法。”默认的“ runTest”运行类上的所有 test * 方法ーー这是 TestCase 实例通常的工作方式。但是,当在抽象基类本身中运行时,您可以简单地用一个什么也不做的方法重写该行为。

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

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