单元测试应如何记录?
我正在努力提高我Python项目中测试的数量和质量。随着测试数量的增加,我遇到的一个困难是搞清楚每个测试的作用,以及它们是如何帮助我发现问题的。我知道,跟踪测试的一部分是给单元测试起个好名字(这个问题在其他地方已经讨论过了),但我也想了解文档和单元测试是如何结合在一起的。
单元测试应该如何记录,以便在将来这些测试失败时能更好地帮助我们?具体来说,什么样的单元测试文档字符串才算好?
我希望能得到一些详细的回答和优秀文档的单元测试示例。虽然我只用Python,但我也愿意学习其他语言的做法。
5 个回答
测试方法的名字应该清楚地说明你在测试什么。文档里应该写明是什么原因导致测试失败。
也许问题不在于如何写测试文档,而在于如何编写测试本身?如果能把测试重构得更自我说明,那就能大大提高测试的可读性,而且当代码发生变化时,你的文档也不会过时。
为了让测试更清晰,你可以做几件事:
- 使用清晰且描述性的测试方法名称(之前提到过)
- 测试的主体应该简洁明了(自我说明)
- 把复杂的准备和清理工作放到单独的方法中
- 还有其他吗?
举个例子,如果你有这样的测试:
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
注意,现在你的测试方法是由一系列意图明确的方法调用组成,这就像是为你的测试定制的语言。这样的测试还需要文档吗?
另一个要注意的点是,你的测试方法主要处于一个抽象层次。阅读测试方法的人会看到算法是:
- 创建一个小部件
- 在小部件上调用运行
- 确认代码的表现符合我们的预期
他们对测试方法的理解不会被设置小部件的细节所混淆,这些细节处于比测试方法更低的抽象层次。
测试方法的第一个版本遵循了内联设置模式。第二个版本遵循了创建方法和委托设置模式。
一般来说,我不太赞成使用注释,除非它们能解释代码的“为什么”。阅读了Uncle Bob Martin的《整洁代码》让我相信了这一点。书中有一章专门讲注释,还有一章讲测试。我推荐这本书。
想了解更多自动化测试的最佳实践,可以查看xUnit Patterns。
我在单元测试中大部分时间只用方法名来做文档说明:
testInitializeSetsUpChessBoardCorrectly()
testSuccessfulPromotionAddsCorrectPiece()
对于几乎100%的测试案例,这样做能清楚地说明这个单元测试在验证什么,我就只用这个方法。不过在一些比较复杂的测试案例中,我会在方法中添加一些注释,来解释几行代码的作用。
我之前见过一个工具(我记得是针对Ruby的),它可以通过解析项目中所有测试案例的名称来生成文档文件,但我不记得它的名字了。如果你有一个棋类游戏中的皇后类的测试案例:
testCanMoveStraightUpWhenNotBlocked()
testCanMoveStraightLeftWhenNotBlocked()
这个工具会生成一个HTML文档,内容大概是这样的:
Queen requirements:
- can move straight up when not blocked.
- can move straight left when not blocked.