我该如何在一组继承自Django TestCase的类中添加测试方法?

14

我有一组测试用例,所有的测试都应该进行完全相同的测试,例如“方法x是否返回现有文件的名称?”

我认为最好的方法是创建一个从TestCase派生的基类,让它们都共享这个类,并将测试添加到该类中。不幸的是,测试框架仍然尝试运行基类的测试,这是没有意义的。

class SharedTest(TestCase):
    def x(self):
        ...do test...

class OneTestCase(SharedTest):
    ...my tests are performed, and 'SharedTest.x()'...

我尝试添加一个检查,如果测试是在基类的对象上调用而不是派生类的对象上调用,则简单地跳过该测试:

    class SharedTest(TestCase):
        def x(self):
            if type(self) != type(SharedTest()):
                ...do test...
            else:
                pass

但是遇到了这个错误:

ValueError: no such test method in <class 'tests.SharedTest'>: runTest

首先,我希望有任何优雅的建议来完成这个任务。其次,尽管我不想使用type() hack,但我仍然想了解为什么它不能正常工作。


@jivanamara:有一种方法可以避免超类测试方法被计数,并且可以编写没有if getattr(...)检查的测试方法。请参见我的答案的第二次更新。 - Manoj Govindan
2个回答

32

您可以使用混合技术,利用测试运行器只运行从 unittest.TestCase 继承的测试(Django的TestCase继承自此类)。例如:

class SharedTestMixin(object):
    # This class will not be executed by the test runner (it inherits from object, not unittest.TestCase.
    # If it did, assertEquals would fail , as it is not a method that exists in `object`
    def test_common(self):
         self.assertEquals(1, 1)


class TestOne(TestCase, SharedTestMixin):
    def test_something(self):
         pass

    # test_common is also run

class TestTwo(TestCase, SharedTestMixin):
    def test_another_thing(self):
        pass

    # test_common is also run

要了解更多关于这个的原因,请搜索Python方法解析顺序和多重继承。


谢谢您的回复, 我一开始就是这样做的,但不喜欢有一个类调用它没有的方法。Pylint也会因此而报错,而我经常使用pylint。 - JivanAmara
@jivanamara:在您的“TestOne”和“TestTwo”测试类中,您不需要调用test_common。通过多继承,“test_common”已经"混入"到了类中。 - Sam Dolan
@Manoj,我同意,很好的解决方案。+1 - Davor Lucic
6
我理解,让我感到烦恼的是SharedTestMixin在既没有声明也没有继承self.assertEquals时调用它。 - JivanAmara
当子类运行混合测试时报告的错误并不告诉您引发错误的子类,即它只报告“FAIL [0.1s]:mixin_method(混合类)mixin.py的X行断言错误”。因此,请确保添加手动消息,包括类名,例如self.assertContains(response, f, f"{f} field missing from {self.__class__.__name__}"),以便从错误报告中清楚地了解哪个子类抛出了错误。 - Chris

5

我遇到了类似的问题。我无法阻止基类中的测试方法被执行,但我确保它不会运行任何实际代码。我通过检查属性并立即返回来实现这一点,如果设置了该属性,则仅对基类设置该属性,因此测试在其他地方都运行,但不在基类中运行。

class SharedTest(TestCase):
    def setUp(self):
        self.do_not_run = True

    def test_foo(self):
        if getattr(self, 'do_not_run', False):
            return
        # Rest of the test body.

class OneTestCase(SharedTest):
    def setUp(self):
        super(OneTestCase, self).setUp()
        self.do_not_run = False

这有点像一个hack。可能有更好的方法来做到这一点,但我不确定怎么做。
更新
正如sdolan says,mixin是正确的方式。为什么我以前没看到呢?
第二次更新
(阅读评论后)如果(1)超类方法可以避免hackish if getattr(self, 'do_not_run', False):检查; (2)如果测试数量被准确计数,那就太好了。
有一种可能的方法可以实现这个。Django会选择并执行所有位于tests中的测试类,无论是tests.py还是该名称的包。如果测试超类声明在外部的测试模块中,则不会发生这种情况。它仍然可以被测试类继承。例如,SharedTest可以位于app.utils中,然后由测试用例使用。这将是上述解决方案的更清晰版本。
# module app.utils.test
class SharedTest(TestCase):
    def test_foo(self):
        # Rest of the test body.

# module app.tests
from app.utils import test
class OneTestCase(test.SharedTest):
    ...

1
因为您没有在 SO 上询问它 :) - Sam Dolan
这是我在提问后编写的代码。它有点像黑客,但pylint更喜欢它,而且我认为它并不比mixin差太多。唯一的缺点是它会将一个不存在的测试添加到测试计数中...这不会让我熬夜失眠。 - JivanAmara
由于我对混合(mixins)的哲学不是很熟悉,所以我向朋友请教并确认了我的犹豫;他说混合应该是自包含的。我选择这个解决方案,因为混合方案会使混合依赖于将使用它的类。 - JivanAmara
这个很好用,但我似乎无法控制测试运行的顺序(而且这似乎是违反直觉的)。使用上面的代码,所有 OneTestCase 中的代码都会在 SharedTest 代码运行之前运行。我的 OneTestCase 依赖于 SharedTest。有什么方法可以控制这个吗? - scoopseven
一个建议(尽管我没有尝试过)是在共享测试中添加一个装饰器,检查self.__class__是否等于SharedTest,然后再运行测试。但是混入(mixin)可能是更清晰的方法。 - jacob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接