60

是否可以创建一个TestCase具有一些 test_* 方法的 abstract ,但这TestCase不会被调用,并且这些方法只会在子类中使用?我想我将TestCase在我的测试套件中拥有一个抽象,它将为单个接口的几个不同实现进行子类化。这就是为什么所有的测试方法都是一些,只有一种,内部方法变化。我怎样才能以优雅的方式做到这一点?

4

12 回答 12

76

我不太明白你打算做什么——经验法则是“不要对测试很聪明”——只要把它们放在那里,写得很清楚。

但是为了实现你想要的,如果你从 unittest.TestCase 继承,每当你调用 unittest.main() 你的“抽象”类都会被执行——我认为这是你想要避免的情况。

只需这样做:创建从“对象”而不是从 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默认覆盖,正如下面的评论中所指出的那样。

于 2010-12-30T23:12:56.273 回答
19

到目前为止,每个人都错过了一种非常简单的方法。与几个答案不同的是,它适用于所有测试驱动程序,而不是在您在它们之间切换的那一刻失败。

像往常一样简单地使用继承,然后添加:

del AbstractTestCase

在模块的末尾。

于 2017-04-11T18:31:14.140 回答
13

多重继承在这里不是一个很好的选择,主要有以下两个原因:

  1. 没有TestCase使用任何方法,super()因此您必须首先列出您的类以获取类似setUp()tearDown()工作的方法。
  2. pylint 将警告基类使用了当时未定义的self.assertEquals()etc。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()
于 2013-08-04T19:15:59.940 回答
11

如果您真的想使用继承而不是 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
于 2018-05-04T13:38:18.937 回答
9

只需投入我的两分钱,尽管它可能违反某些约定,但您可以将抽象测试用例定义为受保护的成员以防止其执行。我在 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)
于 2014-09-05T10:29:26.217 回答
7

加注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'

如果您需要使用定义自己的自定义 TestCasesetUpClass()并且需要调用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
于 2020-01-02T10:36:20.817 回答
4

如果您遵循在 run_unittest 中明确列出所有测试类的约定(参见例如 Python 测试套件以了解该约定的许多用途),那么不列出特定类将是直截了当的。

如果您想继续使用 unittest.main,并且您可以允许使用 unittest2(例如从 Python 2.7 开始),您可以使用它的load_tests协议来指定哪些类包含测试用例)。在早期版本中,您必须继承 TestLoader 并覆盖loadTestsFromModule

于 2010-12-30T23:05:44.387 回答
3

Python unittest 库有load_tests 协议,可以用来实现你想要的:

# 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)
于 2015-06-16T10:31:14.973 回答
3

unittest模块提供了几个跳过测试的选项。

我首选的解决方案是重写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()
于 2020-06-09T23:33:33.713 回答
1

想要做 OP 正在做的事情的另一个原因是创建一个高度参数化的基类,它实现了一组需要在多个环境/场景中重现的核心测试。我所描述的本质上是使用 unittest 创建一个参数化的fixture,一个 la pytest。

假设您(像我一样)决定尽可能快地逃离任何基于多重继承的解决方案,使用 load_tests() 从加载的套件中过滤掉您的基类可能会遇到以下问题:

在标准的 TestLoader 中,load_tests在自动加载类完成后被调用。因为: * 这个自动加载类将尝试使用标准签名init (self, name) 从你的基类构造实例,并且 * 你可能希望这个基类有一个非常不同的 ctor 签名,或者 * 你由于某些其他原因,可能想要跳过基类实例的构造然后删除

..您可能希望完全阻止从基类自动加载测试实例。

编辑:Vadim 在这个其他线程中的解决方案是一种更优雅、简洁和独立的方式来做到这一点。我已经实现了“嵌套类技巧”并确认它可以很好地工作,以防止 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 方法(“fixturey”那些 setUp*/tearDown*/ cleanup*测试方法本身)具有定义该子 TestCase 类要操作的现在非常具体的夹具的所有信息。

对我来说,这是在 unittest 中快速实现一些参数化固定装置的临时解决方案,因为我计划尽快将团队的测试转移到 pytest。

于 2018-10-19T19:08:50.247 回答
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文档,“TestCase 的每个实例都将运行一个基本方法:名为 methodName 的方法。” 默认的“runTests”运行类上的所有 test* 方法——这就是 TestCase 实例正常工作的方式。但是当在抽象基类本身中运行时,您可以简单地用一个什么都不做的方法覆盖该行为。

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

于 2020-07-26T22:18:05.980 回答
-1

我已经按照以下方式完成了,也许它可以启发你:

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 测试。

于 2020-02-23T14:12:03.063 回答