是否可以创建一个抽象的TestCase
,它将有一些test_ *方法,但是这个TestCase
将不会被调用,那些方法只会在子类中使用?我想我将在我的测试套件中有一个抽象TestCase
,它将被子类化为单个界面的一些不同实现。这就是为什么所有测试方法都是一些,只有一个内部方法发生变化的原因。我怎么能以优雅的方式做到这一点?
答案 0 :(得分:60)
我不太明白你打算做什么 - 经验法则是“不要用测试来聪明” - 只是把它们放在那里,写得很清楚。
但要实现你想要的,如果你继承自unittest.TestCase,每当你调用unittest.main()时,你的“抽象”类都会被执行 - 我认为这是你想要避免的情况。
这样做: 创建继承自“object”的“抽象”类,而不是TestCase。 对于实际的“具体”实现,只需使用多重继承: 继承自unittest.TestCase和您的抽象类。
import unittest
class Abstract(object):
def test_a(self):
print "Running for class", self.__class__
class Test(Abstract, unittest.TestCase):
pass
unittest.main()
更新:首先颠倒继承顺序 - Abstract
,使其默认值不被TestCase
默认值覆盖,并在下面的评论中指出。
答案 1 :(得分:11)
多重继承在这里不是一个很好的选择,主要是出于以下两个原因:
TestCase
中的所有方法均未使用super()
,因此您必须首先列出您的课程,以便setUp()
和tearDown()
等方法发挥作用。self.assertEquals()
等,此时self
未定义。以下是我提出的问题:将run()
转换为基类的无操作。
class TestBase( unittest.TestCase ):
def __init__( self, *args, **kwargs ):
super( TestBase, self ).__init__( *args, **kwargs )
self.helper = None
# Kludge alert: We want this class to carry test cases without being run
# by the unit test framework, so the `run' method is overridden to do
# nothing. But in order for sub-classes to be able to do something when
# run is invoked, the constructor will rebind `run' from TestCase.
if self.__class__ != TestBase:
# Rebind `run' from the parent class.
self.run = unittest.TestCase.run.__get__( self, self.__class__ )
else:
self.run = lambda self, *args, **kwargs: None
def newHelper( self ):
raise NotImplementedError()
def setUp( self ):
print "shared for all subclasses"
self.helper = self.newHelper()
def testFoo( self ):
print "shared for all subclasses"
# test something with self.helper
class Test1( TestBase ):
def newHelper( self ):
return HelperObject1()
class Test2( TestBase ):
def newHelper( self ):
return HelperObject2()
答案 2 :(得分:6)
只是为了加入我的2美分,虽然它可能违反某些约定,但您可以将您的抽象测试用例定义为受保护的成员以防止其执行。我在Django中实现了以下功能,并根据需要工作。见下面的例子。
from django.test import TestCase
class _AbstractTestCase(TestCase):
"""
Abstract test case - should not be instantiated by the test runner.
"""
def test_1(self):
raise NotImplementedError()
def test_2(self):
raise NotImplementedError()
class TestCase1(_AbstractTestCase):
"""
This test case will pass and fail.
"""
def test_1(self):
self.assertEqual(1 + 1, 2)
class TestCase2(_AbstractTestCase):
"""
This test case will pass successfully.
"""
def test_1(self):
self.assertEqual(2 + 2, 4)
def test_2(self):
self.assertEqual(12 * 12, 144)
答案 3 :(得分:5)
到目前为止,每个人都错过了非常的简单方法。与其他几个答案不同,它适用于所有测试驱动程序,而不是在它们之间切换时失败。
像往常一样使用继承,然后添加:
del AbstractTestCase
在模块的最后。
答案 4 :(得分:4)
如果遵循在run_unittest中明确列出所有测试类的约定(参见例如Python测试套件以用于该约定的许多用途),那么它将直接不列出特定的类。
如果你想继续使用unittest.main,并且你可以允许使用unittest2(例如来自Python 2.7),你可以使用它的load_tests协议来指定哪些类包含测试用例。在早期版本中,您必须继承TestLoader,并覆盖loadTestsFromModule。
答案 5 :(得分:4)
unittest.SkipTest
中的setUpClass()
另一种方法是在基础类的unittest.SkipTest
中引发一个setUpClass()
并在子类中覆盖setUpClass()
:
class BaseTestCase(TestCase):
@classmethod
def setUpClass(cls):
"Child classes must override this method and define cls.x and cls.y"
raise unittest.SkipTest
def test_x(self):
self.assertEqual(self.x * 3, self.x)
def test_y(self):
self.assertEqual(self.y * 3, self.y + self.y + self.y)
def test_z(self):
self.assertEqual(self.x + self.y, self.y)
class IntegerTestCase(BaseTestCase):
@classmethod
def setUpClass(cls):
cls.x = 0
cls.y = 2
class StringTestCase(BaseTestCase):
@classmethod
def setUpClass(cls):
cls.x = ''
cls.y = 'zuzuka'
如果您需要使用定义自己的setUpClass()
的自定义TestCase并需要调用super().setUpClass()
,则可以定义自己的方法来“设置数据”并仅在该方法内部引发SkipTest:
class BaseTestCase(ThidPartyTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass() # if ThirdPartyTestCase has own setUpClass()
cls.setUpTestCaseData()
@classmethod
def setUpTestCaseData(cls):
"Override and set up cls.x and cls.y here"
raise unittest.SkipTest
... # tests
class IntegerTestCase(BaseTestCase):
@classmethod
def setUpTestCaseData(cls):
cls.x = 0
cls.y = 2
答案 6 :(得分:2)
Python unittest库有load_tests protocol,可以用来实现你想要的:
# Add this function to module with AbstractTestCase class
def load_tests(loader, tests, _):
result = []
for test_case in tests:
if type(test_case._tests[0]) is AbstractTestCase:
continue
result.append(test_case)
return loader.suiteClass(result)
答案 7 :(得分:1)
如果你真的想使用继承而不是mixins,一个简单的解决方案是将抽象测试嵌套在另一个类中。
它避免了测试运行器发现的问题,您仍然可以从另一个模块导入抽象测试。
import unittest
class AbstractTests(object):
class AbstractTest(unittest.TestCase)
def test_a(self):
print "Running for class", self.__class__
class Test(AbstractTests.AbstractTest):
pass
答案 8 :(得分:1)
unittest
模块为skipping tests提供了多个选项。
我的首选解决方案是覆盖“抽象”基类中的setUpClass
方法,以在需要时引发unittest.SkipTest
异常:
class BaseTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
if cls is BaseTestCase:
raise unittest.SkipTest("%s is an abstract base class" % cls.__name__)
else:
super(BaseTestCase, cls).setUpClass()
答案 9 :(得分:0)
想要执行OP正在执行的操作的另一个原因是创建一个高度参数化的基类,该基类实现了许多核心测试的大部分,这些核心测试需要在几种环境/场景中进行重现。我要描述的实际上是使用unittest创建一个参数化的夹具,一个pytest。
假设您(像我一样)决定从任何基于多重继承的解决方案中尽快逃脱,那么使用load_tests()从加载的套件中过滤基类可能会有以下问题:< / p>
在标准TestLoader中,在完成“自动加载类”之后,将load_tests称为 。因为: *此自动加载自类将尝试使用标准签名 init ((自身,名称))从您的基类构造实例,并且 *您可能希望此基类具有完全不同的ctor签名,或者 *您可能出于某些其他原因而跳过了基类实例的构造-然后-移除
..您可能要完全阻止从基类自动加载测试实例。
编辑:Vadim's solution in this other thread是一种更为优雅,简洁和独立的方法。我已经实现了“嵌套类技巧”,并确认它可以很好地工作,以防止TestLoader“发现”您的TestCase基础。
我最初是通过修改TestLoader.loadTestsFromModule来完成此操作的,以简单地跳过用作模块中任何其他TestCase类基类的所有TestCase类:
for name in dir(module):
obj = getattr(module, name)
# skip TestCase classes:
# 1. without any test methods defined
# 2. that are base classes
# (we don't allow instantiating TestCase base classes, which allows test designers
# to implement actual test methods in highly-parametrized base classes.)
if isinstance(obj, type) and issubclass(obj, unittest.TestCase) and \
self.getTestCaseNames(obj) and not isbase(obj, module):
loaded_suite = self.loadTestsFromTestCase(obj)
# ignore empty suites
if loaded_suite.countTestCases():
tests.append(loaded_suite)
其中:
def isbase(cls, module):
'''Returns True if cls is base class to any classes in module, else False.'''
for name in dir(module):
obj = getattr(module, name)
if obj is not cls and isinstance(obj, type) and issubclass(obj, cls):
return True
return False
我上面提到的参数化是通过让每个子类定义其固定装置详细信息(参数)并将它们传递给基类TestCase ctor来实现的,从而使其所有常见的impl方法(“固定装置”的setUp * / tearDown * / cleanup * 和测试方法本身)具有所有信息,这些信息定义了子TestCase类要在其上运行的非常特定的固定装置。
对我来说,这是一个临时解决方案,用于在单元测试中快速实现一些参数化的固定装置,因为我计划将团队的测试尽快迁移到pytest。
答案 10 :(得分:0)
我已经按照以下方式进行了操作,也许可以激发您的灵感:
class AbstractTest(TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def _test_1(self):
# your test case here
class ConcreteTest(AbstractTest)
def test_1(self):
self._test_1()
尽管这不是最方便的解决方案,但它可以让您摆脱多重继承。此外,Dan Ward建议的解决方案不适用于PyCharm中的Django测试。
答案 11 :(得分:0)
这是一种相对简单的方法,它允许您的通用测试从TestCase继承(因此类型检查和IDE工具保持满意),仅使用已记录的单元测试功能,并且避免了“跳过”测试状态:
import unittest
class CommonTestCases(unittest.TestCase):
def __init__(self, methodName='runTest'):
if self.__class__ is CommonTestCases:
# don't run these tests on the abstract base implementation
methodName = 'runNoTestsInBaseClass'
super().__init__(methodName)
def runNoTestsInBaseClass(self):
print('not running tests in abstract base class')
pass
def test_common(self):
# This will run *only* in subclasses. Presumably, this would
# be a test you need to repeat in several different contexts.
self.assertEqual(2 + 2, 4)
class SomeTests(CommonTestCases):
# inherited test_common *will* be run here
def test_something(self):
self.assertTrue(True)
# Also plays nicely with MRO, if needed:
class SomeOtherTests(CommonTestCases, django.test.SimpleTestCase):
# inherited test_common *will* be run here
def test_something_else(self):
self.client.get('/') # ...
工作原理:根据unittest.TestCase
documentation,“每个TestCase实例将运行一个基本方法:名为methodName的方法。”默认的“ runTests”在类上运行所有test *方法,这就是TestCase实例正常工作的方式。但是,当在抽象基类本身中运行时,您可以使用不执行任何操作的方法来简单地覆盖该行为。
副作用是您的测试计数将增加一:runNoTestsInBaseClass“ test”在CommonTestCases上运行时被视为成功测试。