如果读者对上面我的分析过程能够正确地理解,相信对于这份代码的bug也是不难找到的:问题出在条件判断上面。
这个条件判断貌似少点什么!没错,分支只判断c是否等于k,而根本没有考虑(k-c)的奇偶问题;换句话说,代码少考虑情况了!那么Challenge就变得很容易了,只要把代码中缺少的部分提炼出来即可,例如示例中的第三组测试数据originalWord="aaa", finalWord="bab",k=4,按照这份代码的结果会是"IMPOSSIBLE",而实际上(k-c)是偶数,应该返回"POSSIBLE",这样我们的Challenge又成功了!不过这次与上一次的Challenge是有所不同的,因为这份代码并非所有测试用例都通过不了,例如示例中的第二个测试用例,当originalWord与finalWord相同的时候,代码是可以通过的。因此我们称这种仅能通过部分测试用例的情况为对需求的片面分析或需求项分析不足。
我们的Challenge过程很好地阐述了如何针对这种情况设计测试用例:将需求项分析不足的地方提取出来,专门设计这方面的用例。当然,真实应用中,我们会设计大量测试用例,但需求项的覆盖问题依然是重中之重,因为如果测试人员在解读需求的时候不能将其完全覆盖,设计的测试用例必然有所遗漏,可能会造成测试失败。所以,测试用例对需求的完全覆盖,即恰当地进行逻辑测试是测试人员必须加以重视的内容。
正确的代码总是相同的,而错误的代码则各有各的问题,我们再来看另一份代码:
判断部分似乎真的没什么问题,只是形式和我的例程有所差异,那这份代码是怎么被成功Challenge的呢?细心的读者可能一眼就能发现问题所在,前面的循环有问题!结束条件居然是i
显然这段代码不能完全实现题目中的需求,我们只需设计一个测试用例使得最后一个字符左右了程序的输出结果即可。例如,originalWord="aaa", finalWord="aab",k=0,如果没有最后一个字符,显然应该返回"POSSIBLE",然而最后一个字符的存在使得结果变成了"IMPOSSIBLE",而这一切对于上面的代码而言就好像在比较originalWord= finalWord ="aa"一样,所以该测试用例成功Challenge了该代码!
对于这份代码的问题,我们通常叫做边界问题处理不当。边界可能的含义有很多,例如输入变量的范围,例如系统的吞吐量极限等等,在这个问题中,边界的意义就是循环的初始和结束条件。很显然,被我们成功Challenge 的代码初始条件做的没有问题,但结束条件却是错的,因为最后一个字符没有被处理!而我们设计测试用例的时候便顺水推舟,将最后一个字符起决定作用的测试用例设计出来,软件的bug就暴露了!所以任何测试一定不能放过边界问题的处理,对边界条件设置测试用例势在必行。
看来Challenge不是一件多难的事,那么来看下面一位选手的代码吧:
相信一看到这份代码,大家会十分吃惊,居然代码只有两行,这能实现要求的功能吗?话说回来,我当初看到这份代码的时候也觉得看来这两行儿戏般的代码凶多吉少了,于是将之作为重点Challenge对象。不过细细读来,你和我都会发现,没那么简单,这是因为这份代码对需求的实现没有问题,囊括了所有需求项,边界处理也很得当,任何用例都不可能Challenge成功,这份代码是正确的。因此,只要我们的测试遵循原则,尊重需求,没有遗漏,考虑边界,不仅能够找到软件的bug,也可以证明软件实现需求的正确性。
说了这么多,相信读者对设计有效的测试用例应该有了一定的了解,不过真实系统中遇到的问题远比算法设计题目要复杂得多,设计的测试用例也要涵盖更多内容,例如除了功能测试、逻辑测试和边界测试外,还可能有余量测试、接口测试、强度测试、容错性测试等。不过,万变不离其宗,任何测试都是基于对需求的准确、完整的分析;而测试人员在测试过程中也应该坚持心思缜密的作风,这样软件bug将无处遁形,被我们一网打尽!
原文转自:http://www.uml.org.cn/Test/201212274.asp