Python Unittest模块性与可读性



我有一个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():
    ...

最新更新