使用python unittest的抽象测试用例

gru*_*czy 47 python unit-testing testcase

是否有可能创建一个抽象TestCase,它将有一些test_*方法,但这TestCase不会被调用,那些方法只会在子类中使用?我想我将TestCase在我的测试套件中有一个摘要,它将被子类化为单个接口的一些不同实现.这就是为什么所有测试方法都是一些,只有一个内部方法发生变化的原因.我怎么能以优雅的方式做到这一点?

jsb*_*eno 64

我不太明白你打算做什么 - 经验法则是"不要用测试来聪明" - 只要把它们放在那里,写得很清楚.

但是要实现你想要的,如果你继承自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()
Run Code Online (Sandbox Code Playgroud)

更新:颠倒继承顺序 - Abstract首先,TestCase默认情况下不会覆盖其定义,并在下面的注释中指出.

  • 注意一个常见的陷阱:如果你在Abstract中覆盖TestCase方法(覆盖"setUp"是常见的情况),你想要颠倒基类的顺序 - `class Test(Abstract,unittest.TestCase)`.否则,unittest.TestCase中的空setUp方法优先于Abstract中实现的方法. (8认同)
  • @Yonatan提出了一个很好的观点,并且颠倒继承顺序似乎是一个更明智的默认.为什么你想要你的自定义基类被`TestCase`覆盖? (3认同)
  • 这就是我想要做到这一点的方式.我想要聪明,因为我正在针对不同的数据库运行我的测试.访问数据库的接口始终是相同的,所以我只需要即时连接到数据库,并始终运行相同的测试.我不知道,如果这对于测试太聪明了,但我只是不喜欢打字很多;-) (2认同)

tsu*_*una 13

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

  1. 在方法都没有TestCasesuper(),所以你不得不先列出你的类类似的方法setUp()tearDown()工作.
  2. pylint将警告基类使用self.assertEquals()self在该点未定义的等.

这是我想出来的kludge:只变成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()
Run Code Online (Sandbox Code Playgroud)

  • (1)并不是一个真正的问题 - 显而易见的事情就是让你的班级先来并使用超级(我的答案反过来 - 我只是纠正了它).(2)你使用的这个词说它 - 你必须使用"kludge"不要使用Python的OOP内置的干净机制,因为第三方linting工具无法正确反省.这是您选择的工具. (3认同)

jro*_*aud 11

如果你真的想使用继承而不是mixin,一个简单的解决方案是将抽象测试嵌套在另一个类中。

它避免了测试运行程序发现的问题,您仍然可以从另一个模块导入抽象测试。

import unittest

class AbstractTests(object):
    class AbstractTest(unittest.TestCase)
        def test_a(self):
            print "Running for class", self.__class__

class Test(AbstractTests.AbstractTest):
    pass
Run Code Online (Sandbox Code Playgroud)


o11*_*11c 8

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

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

del AbstractTestCase
Run Code Online (Sandbox Code Playgroud)

在模块的最后.

  • 如果抽象基类位于其他测试模块想要导入的公共模块中,则此方法不起作用。 (6认同)
  • 在我发现的所有解决方案中,我最喜欢这个。Mixin 要么缺乏对断言的支持,要么如果您使用它们,它们可能在运行时可用,但它们确实会在 IDE 中发出警告。模块末尾的 del 语句可能很容易被忽视,其意图并不那么明显,但我仍然认为这是所有选项中最干净的,因为只有几个变量会发生变化并且所有测试都保持不变。或者您同时想出了更好的解决方案? (3认同)
  • 请扩展这个答案 - 我差点错过了。解释这是如何解决的:`TypeError: Can't instantiate abstract class AbstractTestCase with abstractmethod my_help_method,...`解释在哪里放置`del`(在继承基类的模块中)以及如何命名包含该类的模块基数,因此被忽略。 (2认同)

小智 7

只是为了加入我的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)
Run Code Online (Sandbox Code Playgroud)

  • 这看起来效果很好,它比上面的猴子补丁方法简单得多. (4认同)
  • 也不适用于django.刚刚测试过 (3认同)
  • 它可能适用于django(在我的情况下,使用鼻子),但单元测试发现机制似乎仍然找到这些抽象测试用例... (2认同)

bel*_*kka 6

提高unittest.SkipTestsetUpClass()

只是另一种方法是提高unittest.SkipTestsetUpClass()基类和覆盖的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'
Run Code Online (Sandbox Code Playgroud)

如果您需要使用定义自己的自定义 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
Run Code Online (Sandbox Code Playgroud)


typ*_*cer 5

该模块提供了多个跳过测试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()
Run Code Online (Sandbox Code Playgroud)