setUp方法中的Python单元测试报错的正确方法是什么?

What is the correct way to report an error in a Python unittest in the setUp method?

我读过一些关于在 Python 单元测试的 setUp 方法中使用 assert 的相互矛盾的建议。如果测试依赖的先决条件失败,我看不出测试失败有什么害处。

例如:

import unittest

class MyProcessor():
    """
    This is the class under test
    """

    def __init__(self):
        pass

    def ProcessData(self, content):
        return ['some','processed','data','from','content'] # Imagine this could actually pass

class Test_test2(unittest.TestCase):

    def LoadContentFromTestFile(self):
        return None # Imagine this is actually doing something that could pass.

    def setUp(self):
        self.content = self.LoadContentFromTestFile()
        self.assertIsNotNone(self.content, "Failed to load test data")
        self.processor = MyProcessor()

    def test_ProcessData(self):
        results = self.processor.ProcessData(self.content)
        self.assertGreater(results, 0, "No results returned")

if __name__ == '__main__':
    unittest.main()

这对我来说似乎是一件合理的事情,即确保测试能够 运行。当由于设置条件而失败时,我们得到:

F
======================================================================
FAIL: test_ProcessData (__main__.Test_test2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Projects\Experiments\test2.py", line 21, in setUp
    self.assertIsNotNone(self.content, "Failed to load test data")
AssertionError: unexpectedly None : Failed to load test data

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

setUp 不适用于 断言 前提条件,而是 创建 它们。如果您的测试无法创建必要的夹具,则它已损坏,而不是失败。

这里没有正确或错误的答案,这取决于您测试的内容以及设置测试的成本。如果数据不符合预期,有些测试太危险,不允许尝试 运行s,有些测试需要使用该数据。

如果需要在测试之间检查特定条件,可以在 setUp 中使用断言,这有助于减少测试中的重复代码。 然而,也使得在 classes 或文件之间移动测试方法有点棘手,因为它们将依赖于具有等效的设置。对于不太懂代码的测试人员,它还可以突破复杂性的极限。

进行单独检查这些启动条件的测试并先 运行 它会更清晰一些,在每次测试之间可能不需要它们。如果您将其定义为 test_01_check_preconditions,它将在任何其他测试方法之前完成,即使其余的是随机的。 然后,您还可以在某些条件下使用 unittest2.skip 装饰器。

更好的做法是使用addCleanup来确保状态被重置,这里的好处是即使测试失败仍然得到运行,也可以让清理更了解具体情况正如您在测试方法的上下文中定义的那样。

也没有什么可以阻止您在单元测试 class 中定义方法来执行常见检查并在 setUp 或 test_methods 中调用它们,这有助于将复杂性保持在定义和管理的区域中.

也不要试图将 unittest2 子class 超出简单的测试定义,我看到有人试图这样做来简化测试,但实际上引入了完全意想不到的行为。

我想真正的收获是,如果你知道你为什么要使用它并确保你记录你的原因它可能没问题,如果你不确定那么选择最简单最容易理解的选项因为测试不好理解就没用了

来自Python Standard Library Documentation

"如果 setUp() 方法在测试 运行ning 时引发异常, 该框架将认为测试发生了错误,并且 运行Test() 方法将不会被执行。如果 setUp() 成功,则 tearDown() 方法将 运行 无论 运行Test() 是否成功。这样的 测试代码的工作环境称为夹具。"

setUp() 方法中的断言异常将被单元测试框架视为错误。测试将不会执行。

setUp 的目的是减少 Boilerplate code,它在安排阶段的测试 class 中的测试之间创建。

在安排阶段,您:设置 运行测试代码所需的一切。这包括对 运行.

测试所需的依赖项、模拟和数据的任何初始化

根据以上段落,您不应在 setUp 方法中断言任何内容。

如前所述; 如果您不能创建测试先决条件,那么您的测试就失败了。为了避免这种情况,Roy Osherove 写了一本很棒的书,书名是:The Art Of Unit Testing(Lior Friedman 的完整披露(他是罗伊的老板)是我的朋友,我和他们一起工作了两年多,所以我有点偏见...)

基本上只有几个原因在安排阶段与外部资源进行交互(或与可能导致异常的事物),其中大多数(如果不是全部)与集成测试相关。

回到你的例子;有一种模式来构建测试,其中 您需要加载外部资源(其中 all/most 个)。只是旁注;在决定应用此模式之前,请确保您不能将此内容作为 UT class 中的静态资源,如果其他测试 classes 需要使用此资源,请将此资源提取到模块中.

以下模式降低了失败的可能性,因为您对外部资源的调用较少:

class TestClass(unittest.TestCase):

    def setUpClass(self):
        # since external resources such as other servers can provide a bad content
        # you can verify that the content is valid
        # then prevent from the tests to run  
        # however, in most cases you shouldn't.
        self.externalResourceContent = loadContentFromExternalResource()


    def setUp(self):
        self.content = self.copyContentForTest()

优点:

  1. 更少的失败机会
  2. 防止不一致行为(1.something/one已编辑外部资源。2.您在某些测试中未能加载外部资源)
  3. 执行速度更快

缺点:

  1. 代码比较复杂

您想避免在 setUp() 中进行断言的原因之一。 如果 setUp 失败,您的 tearDown 将不会被执行。

如果您设置了一组数据库记录,例如您的拆卸删除了这些记录,那么这些记录将不会被删除。

使用此代码段:

import unittest

class Test_test2(unittest.TestCase):

    def setUp(self):
        print 'setup'
        assert False

    def test_ProcessData(self):
        print 'testing'

    def tearDown(self):
        print 'teardown'

if __name__ == '__main__':
    unittest.main()

你运行只有setUp():

$ python t.py 
setup
E
======================================================================
ERROR: test_ProcessData (__main__.Test_test2)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "t.py", line 7, in setUp
    assert False
AssertionError

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)