我有一个Python单元测试,其中一些测试测试了相同类型的对象。一个测试班的基本大纲是:
class TestClass(unittest.TestCase):
def setup(self):
...
def checkObjects(self, obj):
for i in [...values...]:
self.assertEqual(starttags(i,obj), endtags(i,obj))
def testOne(self):
#Get object one.
checkObjects(objone)
def testAnother(self):
#Access another object.
checkObjects(another)
... various tests for similar objects.
尽管它是模块化的,但我注意到任何故障都会产生类似AssertionError:number!=的错误另一个数字,以及生成错误的代码行self.assertEqual(starttags(i,obj), endtags(i,obj))
。如果我把测试列出来,而不是放在for循环中,我会得到这样的东西:
self.assertEqual(starttags(value1,obj), endtags(value1,obj))
self.assertEqual(starttags(value2,obj), endtags(value2,obj))
它确切地显示了导致错误的情况,但它是复制粘贴代码,我认为这通常是不推荐的。最近,当一位贡献者重新编写了一个更为干净的单元测试时,我注意到了这个问题,不幸的是,这几乎不会提供关于断言失败的调试信息。那么,在这些情况下,最佳做法是什么呢?类似于元组列表的东西,用assertEquals输入到for循环中是"更干净"的,但在不同行上复制粘贴不同值会提供有用的堆栈跟踪。
如果cleaner指的是更少的代码,那么这种cleaner[/em>代码并不总是更具可读性代码。事实上,它通常可读性较低(尤其是当您回到它时)。你总是可以进行花哨的重构,但你需要知道什么时候停止。从长远来看,使用更明显、更简单的代码总是比试图为人工增益少压缩一行代码要好,而不仅仅是在单元测试方面。
单元测试遵循它们自己的规则。例如,它们通常允许与常规代码标准不同的命名约定,你几乎从不记录它——它们是代码库中的一个特殊部分。此外,重复代码也并不罕见。事实上,有许多相似的小测试是很典型的。
设计测试(代码)时要考虑到简单性
目前,即使在编写测试的阶段,您的测试也会令人困惑——想象一下,3个月后再回到那个代码。想象一下,其中一个测试因其他人在某个其他地方进行某些更改而中断。情况不会好转。
以这样一种方式设计测试,当其中一个测试中断时,您会立即知道为什么会中断以及在哪里中断。不仅如此——以这样一种方式设计它们,你一眨眼就能知道它们在做什么。在测试代码中对循环、ifs以及基本上任何其他类型的控制流机制(或过于广泛的重构)使用,通常会让你想到一个问题——我们在这里做什么这是你不想问的问题。
用比我聪明的人的话来总结这篇冗长的帖子:
任何傻瓜都能写出计算机能理解的代码。优秀的程序员编写人类能够理解的代码。
-Martin Fowler等人,重构:改进现有代码的设计,1999
帮你自己一个忙,遵守那条规则。
使用nosetests,它使您的测试更加干净:
#!/usr/bin/python
def test_one():
for i in [...]:
assert xxx(i) == yyy
def test_two():
...