如何记录单元测试?

时间:2009-11-13 01:43:47

标签: python unit-testing documentation docstring

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

如果将来测试失败,如何记录单元测试以提高其效用?具体来说,什么是一个好的单元测试docstring?

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

5 个答案:

答案 0 :(得分:14)

我使用方法名称专门记录我的单元测试:

testInitializeSetsUpChessBoardCorrectly()
testSuccessfulPromotionAddsCorrectPiece()

对于几乎100%的测试用例,这清楚地解释了单元测试验证的内容以及我使用的所有内容。但是,在一些更复杂的测试用例中,我将在整个方法中添加一些注释来解释几行正在做什么。

之前我见过一个工具(我相信它适用于Ruby)会通过解析项目中所有测试用例的名称来生成文档文件,但我不记得这个名字。如果您有国际象棋女王级的测试用例:

testCanMoveStraightUpWhenNotBlocked()
testCanMoveStraightLeftWhenNotBlocked()

该工具将生成一个包含以下内容的HTML文档:

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

答案 1 :(得分:12)

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

您可以采取一些措施使测试更加清晰:

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

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

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

您可以使用方法调用替换setup语句:

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。这样的测试是否还需要文档?

另一点需要注意的是,您的测试方法主要是在一个抽象层次上。阅读测试方法的人会看到算法是:

  • 创建小部件
  • 调用小部件上的运行
  • 声明代码符合我们的预期

他们对测试方法的理解并没有被设置小部件的细节弄糊涂,这是一个比测试方法更低的抽象级别。

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

一般来说,我反对评论,除非他们解释代码的“原因”。阅读叔叔鲍勃·马丁的Clean Code使我确信这一点。有一章关于评论,有一章关于测试。我推荐它。

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

答案 2 :(得分:4)

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

答案 3 :(得分:1)

您应该在doc字符串中使用描述性方法名称和注释的组合。一个好方法是在doc字符串中包含基本过程和验证步骤。然后,如果从某种自动运行测试和收集结果的测试框架运行这些测试,您可以让框架记录每个测试方法的doc字符串的内容及其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"

通过测试程序可以更容易地弄清楚测试的作用。如果你将docstring包含在测试输出中,它会在以后更容易理解结果时找出问题所在。我之前工作过的地方做过这样的事情,当故障发生时它很好用。我们使用CruiseControl自动运行每次签入的单元测试。

答案 4 :(得分:0)

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

这完全取决于测试类,测试方法和断言消息的命名。当一个测试失败,你无法分辨这三个线索的内容,然后重命名一些东西或拆分一些测试类。

如果fixture的名称是ClassXTests并且测试的名称是TestMethodX并且错误消息是“expect true,return false”,则不会发生这种情况。这是一个草率的测试写作的迹象。

大多数情况下,您不必阅读测试或任何评论以了解发生了什么。