星期六, 十月 27, 2007

"Progmatic Unit Testing", 心得和自省

昨天花了一天时间,把《Progmatic Unit Testing》这本书基本上过了一遍。写的很不错,结合之前做项目时写单元测试的经验,有一些心得,也必须做一些反省。

最主要的一个方面是关于测试代码本身的质量。测试代码应该与产品代码有同样的质量,因此要遵循 DRY 和正交性、低耦合的设计原则,而这一点在我之前的测试中做的很不好,以 Python Tree 来说,本来我想测试应该保证逻辑上的尽可能简单,因此为了保证测试覆盖面足够,结果包含了很多重复的语句,尤其是有时侯需要对一个变量(Tree Node)的几个方面做检查的时候,这些检查语句都要反复写,这就很不好。

在书中提到,最好从 TestCase 继承一个类,然后所有的其他所有的测试类都从这个继承基类再继承,这样可以在这个基类里面做 setUp() 和 tearDown(),并且可以自定义 assert/fail 函数。例如,对 Python Tree,我大可以这样定义:
class TestTree(unittest.TestCase):
def assertValidTreeNode(node, value):
self.failUnlessEqual(node(), value)
...

class TestSetAttr(TestTree):
def testNotExisted():
self.root.branch = 1
self.assertValidTreeNode(self.root.branch, 1)
...
在 cutils 的 mirrord/fs_mirror 项目中也存在这个问题。这样导致我在单元测试上花费了太多的时间,特别是如果对产品代码做出改动,在单元测试中就需要改很多地方,也就是说,复用性不好!不够专业。

另一个重要的问题是”独立性“不好。这一点在做 cutils 的 mirrord/fs_mirror 的时候尤其明显。一个方面是对于环境的依赖,在 mirrord/fs_mirror 中,因为并发非常重要,所以在 test_mirrord 中需要测试这种不同的并发情况会产生的不同的结果,之前的做法就是调度一个实际的实例,然后进行一些 sleep/wait 来等待并发的状态变化。但因为并发状态会如何变化是不受控制的,在不同的主机上结果也可能完全不同,所以只能等的更久来保证状态一定会变化(实际上有时侯也难以完全保证),结果就是运行的时间很长。另外,这也导致了在编写的时候更难保持 DRY 和正交性、低耦合的原则,对进度很不利。

我当时还在找多线程/并发的单元测试方法,好像对于 Java 还有专门的这样的软件。但现在看来,其实并不需要,只要利用 Mock 对象,模拟出相同的接口,然后在里面可控的设置并发状态,这个问题应该很好解决。

在 caxes Tree 的顺序问题也反映了独立性不够,主要是必须保证以前的节点不会因为后面的操作而受到影响,之前的做法就是逐一检查,当然也就导致了重复,而且后面的操作受前面的影响,没有前面的操作,后面就无从谈起,但完全可以利用 fixture 做多个 TestSuite,每次使用 fixture 和 test case 的 setUp() 重建就好了,然后利用 fixture 和 test suites 重新调用前面的测试就可以了。

在 test_mirrord 也有顺序问题,因为并发状态不同,可能结果不同,那么测试 server thread 的时候和测试 monitor 的时候结果可能不同!我之前的做法显然是错误的,只是因为想不到好的办法,就将前面测试 monitor 的结果记录到文件中,在 测试 server thread 的时候调出来比较,这显然不正确,只不过一般机器的运行调度结果在一般情况下会一样,所以基本上不会出现 fail 而已。

关于测试的“彻底性”和"自动化"方面,应该做得还可以,不过因为复用性不好,比较烦琐。当然,书中提到的对测试覆盖和边界条件的检查的原则还是相当有价值的,我之前也很难说做的很好。

另外,使用数据文件的技巧也很有意思。

要重写了! '')

没有评论: