qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

使用单元测试的原因及使用前的准备

使用单元测试的原因及使用前的准备

  1、自傲的编码

  有一次——或许就是上个礼拜二——有两个开发者:Pat 和Dale。他们面临着不异的最后一日,而这一天也越来越近了。Pat 天天都在焦心地编写代码,写完一个类又写一个类,写完一个函数又接着写另一个函数,还经常不得不停下来做一些调整,使得代码能够经由过程编译。

  Pat 一向连结着这种工作体例,直到最后一日的前一天。而这时已经是演示所有代码的时候了。Pat 运行了最上层的轨范,可是一点输出也没有,什么都没有。这时只好用调试器来单步跟踪了。“Hmm,决不成能是这样的”,Pat 想,“此时这个变量绝对不是0 啊”。于是,Pat 只能回过头来看代码,考虑着跟踪一下这个难以琢磨的程序的挪用流程。

  时刻已经越来越晚了,Pat 找到且更正了这个bug;但在这个过程中,Pat 又找到了其他好几个bug;如斯几回事后,bug 仍是存在。而程序输出何处,仍然没有结果。这时,Pat 已经筋疲力尽了,完全搞不清为什么会这样,认为这种(没有输出的)行为是毫无事理的。

  而于此同时,Dale 并没像Pat 那么快地写代码。Dale 在写一个函数的时候,会附带写一个简短的测试程序来测试这个函数。这并没有什么非凡的处所,只是添加了一个简单的测试,来判定函数的功能是否和程序员期望的一致。显然,考虑如何写,然后把测试写出来,是需要占用一定时间的;可是Dale 在未对刚写的函数做出确认之前,是不会接着写新代码的。也就是说,只有等到已知函数都获得确认之后,Dale 才会继续编写下一个函数,然后挪用前面的函数等等。

  在整个过程中,Dale 几乎不使用调试器;而且对Pat 的模样也有些思疑不解:只见他头埋在两手之间,嘀咕着各类难听的话语,诅咒着计较机,充血的眼球同时盯着好几个底时景口。

  最后一日终于到了,Pat 未能完成使命。而Dale 的代码被集成到整个系统中,而且能够很好地运行。之后,在Dale 的模块中,呈现了一个小问题;可是Dale 很快就发现了问题地址,在几分钟之内就解决了问题。

  此刻,是该总结一下这个小故事的时候了:Dale 和Pat 的年数相当,编码能力相当,智力也差不多。唯一的区别就是Dale 很是相信单元测试;对于每个新写的函数,在其他代码使用这个函数并对它形成依靠之前,都要先做单元测试。

  而Pat 则没有这么做,他老是“知道”代码的行为应该和所期望的完全一样,而且等到所有代码都差不多写完的时候,才想起来运行一下代码。然而到了这个时辰,要想定位bug,或者,甚至是确定哪些代码的行为是正确的,哪些代码的行为是错误的,都为时已晚了。

  2、什么是单元测试

  单元测试是开发者编写的一小段代码,用于磨练被测代码的一个很小的、很明晰的功能是否正确。凡是而言,一个单元测试是用于判定某个特定前提(或者场景)下某个特定函数的行为。例如,你可能把一个很年夜的值放入一个有序list 中去,然后确认该值呈现在此刻list 的尾部。或者,你可能会年夜字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。

  执行单元测试,是为了证实某段代码的行为确实和开发者所期望的一致。

  对于客户或最终使用者而言,这种测试需要吗,它与验收测试有关吗?这个问题仍然很难回覆。事实上,我们在此并不关心服个产物确认、验证和正确性等等;甚至此时,我们都不去关心性能方面的问题。我们所要做的一切就是要证实代码的行为和我们的期望一致。所以,我们所要测试的是规模很小的、很是独立的功能片段。经由过程对所有零丁部门的行为成立起抉择信念,确信它们都和我们的期望一致;然后,我们才能起头组装和测试整个系统。

  事实下场,若是我们对手上正在写的代码的行为是否和我们的期望一致都没把握,那么其他形式的测试也都只能是华侈时刻而已。在单元测试之后,你还需要其他形式的测试,有可能是更正规的测试,那一切就都要看情形的需要来抉择了。总之,做测试如同做善事,老是要巨匠(代码最根基的正确性)起头。

  3、为什么要使用单元测试

  单元测试不单会使你的工作完成得更轻松,而且会令你的设计变得更好,甚至削减你花在调试上的时间。

  在我们之前的小故事中,Pat 因为假设底层的代码是正确无误的而卷入麻烦之中,先是高层代码中使用了底层代码;然后这些高层代码又被更高层的代码所使用,如斯往来来往。在对这些代码的行为没有任何抉择信念的前提下,Pat 等于是在假设用竖立卡片堆砌了一间房子——只要将下面卡片轻轻移动,整间房子就会轰然倾塌。

  当根基的底层代码不再靠得住时,那么必需的改动就无法只局限在底层。虽然你可以批改底层的问题,可是这些对换层代码的改削必然会影响到高层代码,于是高层代码也连带地需要修改;以此递推,就很可能会动到更高层的代码。于是,一个对换层代码的批改,可能会导致对几乎所有代码的陆续串改动,如此而使改动越来越多,也越来越复杂。于是,整间由卡片堆成的房子就由此倾塌,从而使整个项目也以失踪失败了却。

  Pat 老是说:“这怎么可能呢?”或者“我其实想不明白为什么会这样”。你发现自己有时候也会有这种想法。那么凡是你对自己的代码还缺乏足够抉择信念的默示——你并不能确认哪些是工作正常的而哪些不是。

  为了获得Dale 所具有的那种对代码的抉择信念,你需要“询问”代码事实做了什么,并搜检所发生的结过是否确实和你所期望的一致。

  这个简单的设法描述了单元测试的焦点内在:这个简单的手艺就是为了令代码变得加倍完美。

 4、我需要做什么

  其实惹人的单元测试是很简单的,因为它自己就布满了乐趣。然而在项目交付的时候,我们给客户和最终用户的仍然是产物代码,而不包含单元测试的代码;所有,我们必需对单元测试的目的有个充实的熟悉。首先也是最主要的,使用单元测试是为了使你的工作——以及你队友的工作——完成得加倍轻松。

  ● 它的行为和我的期望一致吗?

  最根柢的,你需要回覆下面这个问题:“这段代码达到我的目的了吗?”也许代码所做的是错误的工作,但那是另外的问题了。你要的是代码向你证实它所做的就是你所期望的。

  ● 它的行为一向和我的期望一致吗?

  很多开发者说他们只编写一个测试。也就是让所有代码从头至尾跑一次,只测试代码的一条正确执行路径,只要这样走一遍下来没有问题,测试也就算是完成了。

  可是,现实当然不会这么事事顺心,工作也不老是那么顺利:代码会抛出异常,硬盘会没有残剩空间,收集会失踪线,缓冲区会溢出等——而我们写的代码也会呈现bug。这就是软件开发的“工程”部门。就“工程”而言,土木匠工程师在设计一座桥梁的时候,必需考虑桥梁的负载、强风的影响、地震、洪水等等。电子工程师要考虑频率漂移、电压尖峰、噪音,甚至这些同时呈现时所带来的问题。

  你不能这样来测试一座桥梁:在风和日丽的某一天,仅让一辆车顺遂地开过这座桥。显然,这种测试对于桥梁测试来说是远远不够的。相似地,在测试某段代码的行为是否和你的期望一致时,你需要确认:在任何情形下,这段代码是否都和你的期望一致;譬如在参数很可疑、硬盘没有残剩空间、收集失踪线等的时候。

  ● 我可以依靠单元测试吗?

  不能依靠的代码是没有多大用处的。但更糟糕的是,那些你自认为可以相信的代码(可是结果证实这些代码是有bug 的)有时候也会让你花很多时间在跟踪和调试上。显然,几乎没有项目可以许可你在这上面花费太多的时间,是以无论如何,你都要避免这种“前进一步,萎缩后退两步”的开发体例。也就是说,要闪开开发过程连结不变的轨范前进。

  没人能够写出十全十美的代码;可是这并没有关系——只要你知道问题的地址就足够了。很多类型软件项目的失败,诸如只能把坏了的太空船搁浅在遥远的行星,或者在翱翔的途中就爆炸了,都能经由过程确认的限制来避免。例如,Arianne 5 号火箭软件重用了来自于之前一个火箭项目的一个程序库,而这个程序库并不能措置新火箭的翱翔高度(比原本火箭要高),从而在起飞40 秒之后就发生了爆炸,导致5 亿美元的损失踪。

  显然,我们但愿能够依靠于所编写的代码,而且清楚地知道这些代码的功能和约束。

  例如,假设你写了一个反转数值序列的体例。在测试的过程中,你也许会传一个空序列给这个程序——但导致了程序解体。现实上,轨范并没有要求该轨范必需能够领受一个空序列,是以你可以只在体例的注释中声名这个约束:如不美观传递一个空序列给这个体例,那么这个体例将会抛出一个异常。此刻你马上就知道了该代码的约束,年夜而也就不需要用其他很麻烦的体例来解决这个问题(因为在某些地址要解决这个问题并未便利,好比在高空年夜气层中)。

  ● 单元测试声名我的意图了吗?

  对于单元测试而言,一个最让人欢快的意外收成就是它能够辅佐你充实理解代码的用法。简单而言,单元测试就像是能执行的文档,了然在你用各类前提挪用代码时,你所能期望这段代码完成的功能。

  项目成员能够经由过程查看单元测试来找到如何使用你所写代码的例子。如果他偶然发现了一个你没有考虑到的测试用例,那么他也可以很快地知道这个事实:你的代码可能并不支持这个用例。

  显然,在正确性方面,可执行的文档有它的优势。与通俗的文档分歧的是,单元测试不会呈现与代码纷歧导致的情形(当然,除非程序选择不运行这些测试)。

  5、如何进行单元测试

  单元测试原本就是一项简单易学的手艺;可是如果能够遵循一些指导性原则(guideline)和根基规范,那么进修将会变得加倍轻易和有用。

  首先要考虑的是在编写这些测试用例之前,如何测试那些可疑的用例。有了这样一个概略的想法之后,你将可以在编写实现代码的时候,或者之前,编写测试代码。

  下一步,你需要运行测试用例,或者同时运行系统的所有其他测试,甚至运行整个系统的测试,前提是这些测试运行起来相对斗劲快。在此,我们要确保所有的测试都能够经由过程,而不只是新写的测试能够经由过程;这一点长短常主要的。也就是说,在保证不惹人直接bug 的同时,你也要保证不会给其他的测试带来破损。

  在这个测试过程中,我们需要确认这个测试事实是经由过程了还是失败了——但这并不意味着你或者其他晦气的人需要查看每个输出,然后才抉择这些代码是正确的还是错误的。

  在此,你慢慢地就会养成一个习惯:只要进行一次单元测试查看一下测试结果,就可以马上知道所有代码是否都是正确的,或者哪些代码是有问题的。关于这个问题,我们将留在讨论如何使用单元测试框架时来具体讨论。

posted on 2011-12-05 14:35 顺其自然EVO 阅读(164) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航:
 
<2011年12月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜