【问题标题】:How should unit tests be documented? [closed]应该如何记录单元测试? [关闭]
【发布时间】:2009-11-13 01:43:46
【问题描述】:

我正在努力提高我的 Python 项目中测试的数量和质量。随着测试数量的增加,我遇到的困难之一是知道每个测试的作用以及它应该如何帮助发现问题。我知道跟踪测试的一部分是更好的单元测试名称(已解决 elsewhere),但我也有兴趣了解文档和单元测试如何结合在一起。

当单元测试将来失败时,如何记录单元测试以提高其实用性?具体来说,什么是好的单元测试文档字符串?

我很欣赏描述性答案和带有优秀文档的单元测试示例。虽然我只使用 Python,但我对其他语言的实践持开放态度。

【问题讨论】:

    标签: python unit-testing documentation docstring


    【解决方案1】:

    我在单元测试中记录的最多的是方法名称:

    testInitializeSetsUpChessBoardCorrectly()
    testSuccessfulPromotionAddsCorrectPiece()
    

    对于我几乎 100% 的测试用例,这清楚地解释了单元测试正在验证什么,这就是我使用的全部内容。不过,在一些比较复杂的测试用例中,我会在整个方法中添加几个 cmets 来解释几行代码的作用。

    我以前见过一个工具(我相信它是用于 Ruby 的),它可以通过解析项目中所有测试用例的名称来生成文档文件,但我不记得这个名称了。如果您有国际象棋皇后课程的测试用例:

    testCanMoveStraightUpWhenNotBlocked()
    testCanMoveStraightLeftWhenNotBlocked()
    

    该工具会生成一个 HTML 文档,其内容如下:

    Queen requirements:
     - can move straight up when not blocked.
     - can move straight left when not blocked.
    

    【讨论】:

    • 你的函数名怎么了?我假设您将测试命名为“testFunctionName”,这没关系,但是您真的有一个名为 InitializeSetsUpChessBoardCorrectly 的函数?我认为“setUpChessboard”会很好。
    • 不,方法名称准确地解释了它正在测试什么——该测试用例验证 initalize() 正确设置了棋盘。繁荣,自动文档。
    • 哈哈是的,开头的“测试”只是JUnit 的旧时代,我的大脑还停留在那个年代。我可以将它命名为 initalizeSetsUpChessBoardCorrectly() 并使用@Test 注释。
    • @Kaleb:我仍然这样做。 :) "setUpChessboard" 应该是被测类中的一个方法。
    • python 单元测试框架本质上是JUnit 的python 版本,这也是为什么我们仍然使用“test”前缀作为测试方法的原因。但即使它没有意义,我可能仍然会这样做:)
    【解决方案2】:

    也许问题不在于如何最好地编写测试文档字符串,而是如何编写测试本身?以自我记录的方式重构测试可以有很长的路要走,并且您的文档字符串在代码更改时不会过时。

    您可以做一些事情来使测试更清晰:

    • 清晰且描述性的测试方法名称(已提及)
    • 测试主体应该清晰简洁(自我记录)
    • 抽象出方法中复杂的设置/拆卸等
    • 更多?

    例如,如果您有这样的测试:

    def test_widget_run_returns_0():
        widget = Widget(param1, param2, "another param")
        widget.set_option(true)
        widget.set_temp_dir("/tmp/widget_tmp")
        widget.destination_ip = "10.10.10.99"
    
        return_value = widget.run()
    
        assert return_value == 0
        assert widget.response == "My expected response"
        assert widget.errors == None
    

    您可以将设置语句替换为方法调用:

    def test_widget_run_returns_0():
        widget = create_basic_widget()
        return_value = widget.run()
        assert return_value == 0
        assert_basic_widget(widget)
    
    def create_basic_widget():
        widget = Widget(param1, param2, "another param")
        widget.set_option(true)
        widget.set_temp_dir("/tmp/widget_tmp")
        widget.destination_ip = "10.10.10.99"
        return widget
    
    def assert_basic_widget():
        assert widget.response == "My expected response"
        assert widget.errors == None
    

    请注意,您的测试方法现在由一系列具有意图揭示名称的方法调用组成,这是一种特定于您的测试的 DSL。这样的测试还需要文档吗?

    另外需要注意的是,您的测试方法主要处于一个抽象级别。阅读测试方法的人会看到算法是:

    • 创建小部件
    • 在小部件上调用 run
    • 断言代码符合我们的预期

    他们对测试方法的理解并没有被设置小部件的细节所混淆,这是比测试方法低一级的抽象。

    测试方法的第一个版本遵循Inline Setup 模式。第二个版本遵循Creation MethodDelegated Setup 模式。

    通常我反对 cme​​ts,除非他们解释代码的“原因”。阅读 Bob Martin 叔叔的Clean Code 让我相信了这一点。有一章是关于 cmets 的,还有一章是关于测试的。我推荐它。

    有关自动化测试最佳实践的更多信息,请查看xUnit Patterns

    【讨论】:

    • 感谢您提供额外资源并帮助我了解如何简化测试本身。我肯定会在这个话题上做更多的阅读。再次感谢!
    【解决方案3】:

    测试方法的名称应该准确描述您正在测试的内容。文档应该说明是什么导致测试失败。

    【讨论】:

      【解决方案4】:

      您应该在文档字符串中使用描述性方法名称和 cmets 的组合。一个好的方法是在文档字符串中包含基本程序和验证步骤。然后,如果您从某种自动运行测试并收集结果的测试框架中运行这些测试,您可以让框架记录每个测试方法的文档字符串的内容及其 stdout+stderr。

      这是一个基本示例:

      class SimpelTestCase(unittest.TestCase):
          def testSomething(self):
              """ Procedure:
                  1. Print something
                  2. Print something else
                  ---------
                  Verification:
                  3. Verify no errors occurred
              """
              print "something"
              print "something else"
      

      通过测试过程可以更容易地弄清楚测试在做什么。而且,如果您在测试输出中包含文档字符串,那么以后查看结果时找出问题所在会变得更加容易。我以前工作的地方做过类似的事情,并且在发生故障时效果很好。我们使用 CruiseControl 在每次签到时自动运行单元测试。

      【解决方案5】:

      当测试失败时(应该在它通过之前),您应该会看到错误消息并且能够知道发生了什么。只有当你这样计划时才会发生这种情况。

      这完全是测试类、测试方法和断言消息的命名问题。当一个测试失败时,你无法从这三个线索中判断出发生了什么,然后重命名一些东西或分解一些测试类。

      如果夹具的名称是 ClassXTests 并且测试的名称是 TestMethodX 并且错误消息是“预期为真,返回假”,则不会发生这种情况。这是测试写作草率的表现。

      大多数情况下,您不必阅读测试或任何 cmets 即可知道发生了什么。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多