引入单元测试是很简单的,因为它本身就充满了乐趣。然而在项目交付的时候,我们给客户和最终用户的仍然是产品代码,而不包含单元测试的代码;因此,我们必须对单元测试的目的有个充分的认识。首先也是最重要的,使用单元测试是为了使你的工作——以及你队友的工作——完成得更加轻松。
n 它的行为和我的期望一致吗?
最根本的,你需要回答下面这个问题:“这段代码达到我的目的了吗?”也许就需求而言,代码所做的是错误的事情,但那是另外一个问题了。你要的是代码向你证明它所做的就是你所期望的。
n 它的行为一直和我的期望一致吗?
许多开发者说他们只编写一个测试。也就是让所有代码从头到尾跑一次,只测试代码的一条正确执行路径,只要这样走一遍下来没有问题,测试也就算是完成了。
但是,现实生活当然不会这么事事顺心,事情也不总是那么美好:代码会抛出异常,硬盘会没有剩余空间,网络会掉线,缓冲区会溢出等——而我们写的代码也会出现bug。这就是软件开发的“工程”部分。就“工程”而言,土木工程师在设计一座桥梁的时候,必须考虑桥梁的负载、强风的影响、地震、洪水等等。电子工程师要考虑频率漂移、电压尖峰、噪音,甚至这些同时出现时所带来的问题。
你不能这样来测试一座桥梁:在风和日丽的某一天,仅让一辆车顺利地开过这座桥。显然,这种测试对于桥梁测试来说是远远不够的。类似地,在测试某段代码的行为是否和你的期望一致时,你需要确认:在任何情况下,这段代码是否都和你的期望一致;譬如在风很大、参数很可疑、硬盘没有剩余空间、网络掉线的时候。
n 我可以依赖单元测试吗?
不能依赖的代码是没有多大用处的。但更糟糕的是,那些你自认为可以信赖的代码(但是结果证明这些代码是有bug 的)有时候也会让你花很多时间在跟踪和调试上面。显然,几乎没有项目可以允许你在这上面浪费太多的时间,因此无论如何,你都要避免这种“前进一步,后退两步”的开发方法。也就是说,要让开发过程保持稳定的步伐前进。
没人能够写出完美无缺的代码;但是这并没有关系——只要你知道问题的所在就足够了。许多大型软件项目的失败,诸如只能把坏了的太空船搁浅在遥远的行星,或者在飞行的途中就爆炸了,都能通过认知软件的限制来避免。例如,Arianne 5 号火箭软件重用了来自于之前一个火箭项目的一个程序库,而这个程序库并不能处理新火箭的飞行高度(比原来火箭要高)(引入单元测试是很简单的,因为它本身就充满了乐趣。然而在项目交付的时候,我们给客户和最终用户的仍然是产品代码,而不包含单元测试的代码;因此,我们必须对单元测试的目的有个充分的认识。首先也是最重要的,使用单元测试是为了使你的工作——以及你队友的工作——完成得更加轻松。) ,从而在起飞40 秒之后就发生了爆炸,导致5 亿美元的损失。
显然,我们希望能够依赖于所编写的代码,并且清楚地知道这些代码的功能和约束。
例如,假设你写了一个反转数值序列的方法。在测试的过程中,你也许会传一个空序列给这个程序——但导致了程序崩溃。实际上,程序并没有要求该程序必须能够接收一个空序列,因此你可以只在方法的注释中说明这个约束:如果传递一个空序列给这个方法,那么这个方法将会抛出一个异常。现在你马上就知道了该代码的约束,从而也就不需要用其他很麻烦的方法来解决这个问题(因为在某些地点要解决这个问题并不方便,比如在高空大气层中)。