【问题标题】:What is the correct way to report an error in a Python unittest in the setUp method?在setUp方法中报告Python单元测试错误的正确方法是什么?
【发布时间】:2016-05-10 09:24:47
【问题描述】:

我已经阅读了一些关于在 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)

【问题讨论】:

    标签: python unit-testing testing assert arrange-act-assert


    【解决方案1】:

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

    在安排阶段,您: 设置运行测试代码所需的一切。这包括测试运行所需的依赖项、模拟和数据的任何初始化。

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

    如前所述; 如果你不能创建测试前提条件,那么你的测试就被破坏了。 为了避免这种情况,Roy Osherove 写了一本很棒的书,叫做:The Art Of Unit Testing(为了充分披露 Lior Friedman(他是 Roy 的老板) ) 是我的一个朋友,我和他们密切合作了 2 年多,所以我有点偏见...)

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

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

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

    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. 某事/某人编辑了外部资源。2. 您在某些测试中未能加载外部资源)
    3. 更快的执行速度

    缺点:

    1. 代码更复杂

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 那么您应该如何在自动构建环境中突出显示这一点?
      • @JonCage 恕我直言,如果你真的需要测试先决条件,你应该为它们编写一个测试,而不是使用 setUp() 来断言你的先决条件是有效的。所以添加一个像test_load_fixture(self): self.assertIsNotNone(self.content, "Failed to load test data") 这样的测试,这个测试会出错,而使用self.content 的每个测试都会失败。
      【解决方案3】:

      来自Python Standard Library Documentation

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

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

      【讨论】:

        【解决方案4】:

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

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

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

        更好的方法是使用 addCleanup 来确保状态被重置,这里的好处是即使测试失败它仍然可以运行,您还可以让清理更加了解您在测试方法的上下文。

        也没有什么可以阻止您在 unittest 类中定义方法来进行常见检查并在 setUp 或 test_methods 中调用它们,这有助于将复杂性限制在定义和管理的区域中。

        除了简单的测试定义之外,不要试图将 unittest2 子类化,我已经看到人们试图这样做以使测试变得简单并实际上引入了完全意想不到的行为。

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

        【讨论】:

          【解决方案5】:

          您希望避免在setUp() 中使用断言的原因之一。 如果设置失败,您的 tearDown 将不会被执行。

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

          有了这个sn-p:

          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)
          

          【讨论】:

            猜你喜欢
            • 2022-01-06
            • 1970-01-01
            • 2017-05-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-03-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多