白盒测试实例之一——需求说明三角形的问题在很多软件测试的书籍中都出现过,问题虽小,五脏俱全,是个很不错的软件测试的教学例子。
本文借助这个例子结合教学经验,从更高的视角来探讨需求分析、软件设计、软件开发与软件测试之间的关系与作用。
题目:根据下面给出的三角形的需求完成程序并完成测试:一、输入条件:1、条件1:a+b>c2、条件2:a+c>b3、条件3:b+c>a4、条件4:0<a<2005、条件5:0<b<2006、条件6:0<c<2007、条件7:a==b8、条件8:a==c9、条件9:b==c10、条件10:a2+b2==c211、条件11:a2+ c2== b212、条件12:c2+b2== a2二、输出结果:1、不能组成三角形2、等边三角形3、等腰三角形4、直角三角形5、一般三角形6、某些边不满足限制白盒测试实例之二——答案很多初学者一看到这个需求(详见白盒测试实例之一——需求说明收藏),都觉得很简单,然后立刻就开始动手写代码了,这并不是一个很好的习惯。
如果你的第一直觉也是这样的,不妨耐心看到文章的最后。
大部分人的思路:1、首先建立一个main函数,main函数第一件事是提示用户输入三角形的三边,然后获取用户的输入(假设用户的输入都是整数的情况),用C语言来写,这一步基本上不是问题(printf和scanf),但是要求用java来写的话,很多学生就马上遇到问题了,java5.0及之前的版本不容易获取用户的输入。
11. if(a==b && b==c && a==c) //这里可以省掉一个判断12. {13. printf("1是等边三角形");14. }15. else16. {17. if(a==b || b==c || a==c)18. {19. printf("2是等腰三角形");20. }21. else22. {23. if(a*a+b*b==c*c || a*a+c*c==b*b ||b*b+c*c==a*a)24. {25. printf("3是直角三角形");26. }27. else28. {29. printf("4是一般三角形");30. }31. }32. }33. }34. else35. {36. printf("5不能组成三角形");37. }38. }39. else40. {41. printf("6某些边不满足限制");42. }43. }点评:这样的思路做出来的程序只能通过手工方式来测试所有业务逻辑,而且这个程序只能是DOS界面版本了,要是想使用web或图形化界面来做输入,就得全部写过代码。
需求分析是后续工作的基石,如果分析思路有问题,后续工作可能就会走向不正确的方向,比如:代码重用性差、难于测试、难于扩展和难于维护等。
反而,如果需求分析做的好,对设计、开发和测试来说,都可能是很大的帮助。
看到题目给出的条件达12个之多,粗粗一看,好像很复杂,但仔细分析之后,发现可以把它们分成4组来讨论:1、条件1:a+b>c;条件2:a+c>b;条件3:b+c>a这三个表达式有什么特点呢?实际上它们的逻辑是一样的:两个数之和大于第三个数。
那么,前面程序的写法就存在逻辑重复的地方,应该把这个逻辑提取到一个函数中。
2、条件4:0<a<200;条件5:0<b<200;条件6:0<c<200这三个表达式也是同一个逻辑:判断一个数的范围是否在(0, 200)区间内,也应该把这个逻辑提取到一个函数中,去掉重复的逻辑,提高代码的可重用性。
可重用性的好处:比如,现在用户的需求改为了三条边的取值范围要改为[100,400],那么,按前面的思路来说,需要改3个地方,而现在只需要在一个函数里改1个地方,这就是代码重用的好处。
3、条件7:a==b;条件8:a==c;条件9:b==c这三个表达式的逻辑:判断两个数是否相等。
也应该把它提取到一个函数中。
我们进一步来分析一下判断是否是等边三角形或等腰三角形的条件:(1)前面程序的判断是从最直观的方式(a==b && b==c && a==c)(实际上只需要两个表达式成立即可)三条边都相等来判定是等边三角形;(a==b || b==c || a==c)只有两条边相等来判定是等腰三角形。
(2)转变一下思路:给定三个整数,然后用一个函数来判断这三个整数有几个相等,返回相等的个数,如果返回值等于3,那么它是等边三角形,如果返回值是2,那么它是等腰三角形,否则,它是一般三角形(如果不是直角三角形的话)。
4、条件10:a2+b2==c2 条件11:a2+ c2== b2 条件12:c2+b2== a2这三个条件的处理方式有两种:(1)跟前面三组分析一样,把相同的逻辑提取到一个函数中,然后三次调用。
(2)根据直角三角形的特点:斜边是最长的,所以我们可以事先写一个函数来找到最长的边,然后把它赋值给c,这样处理之后,只需要一次调用判定(a2+b2==c2)的函数了。
程序设计对于软件的质量和软件实施过程的难易程度起着至关重要的作用。
好的设计,即使聘用没什么经验的开发人员都很容易产生出高质量的代码出来;而差的设计,即使是经验很丰富的开发人员也很容易产生缺陷,特别是可重用性、可测试性、可维护性、可扩展性等方面的缺陷。
经过以上的分析,下面来看一下如何设计。
在下图中,每个方框都使用一个函数来实现,为了跟用户界面分开,最顶上的函数不要写在main函数中。
把思路用流程图的方式表达出来,不用停留在脑袋里:具体的函数的调用关系图:复杂模块triangleType的流程图:白盒测试实例之五——编码7.8. #include<stdio.h>9. #include<String.h>10.11. /*12. * 判断一个整数是否在(0, 200)区间内13. * 返回值:true-否;false-是14. */15. bool isOutOfRange(int i);16.17. /*18. * 判断三条边是否合法(即:判断三条边都在合法的范围内)19. * 返回值:true-是;false-否20. */21. bool isLegal(int a, int b, int c);22.23. /*24. * 判断两条边之和是否大于第三边25. * 返回值:true-是;false-否26. */27. bool isSumBiger(int a, int b, int c);28.29. /*30. * 判断三条边是否能够组成三角形31. * 返回值:true-是;false-否32. */33. bool isTriangle(int a, int b, int c);34.35. /*36. * 判断两条边是否相等37. * 返回值:true-是;false-否38. */39. bool isEquals(int a, int b);40.41. /*42. * 求三角形有几条边相等43. * 返回值:相等边的数量44. */45. int howManyEquals(int a, int b, int c);46.47. /*48. * 判断是否满足两边平方之和是否等于第三边的平方49. *50. */51. bool isPowerSumEquals(int a, int b, int c);52.53. /*54. * 判断第一个数是否比第二个数大55. */56. bool isGreaterThan(int a, int b);57.58. /*59. * 判断是否是直角三角形60. *61. */62. bool isRightRriangle(int a, int b, int c);63.64. /*白盒测试实例之六——单元测试的步骤所以,单元测试主要是关注本单元的内部逻辑,而不用关注整个业务的逻辑,因为会有别的模块去完成相关的功能白盒测试实例之七——单元测试的尝试6. */7. #include "Triangle.h"8. /*9. * 测试isOutOfRange函数,使用边界值的方法(0,1,5,199,200)10. *11. */12. void testIsOutOfRange_try()13. {14. if(isOutOfRange(0) == true)15. {16. printf("pass!\n");17. }18. else19. {20. printf("fail!\n");21. }22.23. if(isOutOfRange(1) == false)24. {25. printf("pass!\n");26. }27. else28. {29. printf("fail!\n");30. }31.32. }33.小知识:做单元测试的时候,一般不直接在main函数中写所有的测试代码,否则的话,main函数将会非常庞大。
正确的做法:针对每个函数分别创建一个或若干个(函数比较复杂时)测试函数,测试函数的名称习惯以test开头。
写到这里发现重复的代码太多了,而且如果测试用例数量很多的话,对于测试结果的检查也将是很大的工作量。
在测试有错误的时候,这样的单元测试结果也很难获得更多关于错误的信息。
解决问题的途径可以采用cppUnit单元测试框架。
不过这里为了让学生能够对单元测试和单元测试框架有进一步的理解,我决定自己写一个类似cppUnit的简单的测试框架。
白盒测试实例之八——构建自己的单元测试框架(上)4. void assertTrue(char *msg, bool actual)5. {6. if(actual)7. {8. printf(".");9. }10. else11. {12. printf("F");13. }14. }15.16. /*17. * 判断预期结果和实际结果是否相符18. */19. void assertEquals(char *msg, int expect, int actual)20. {21. if(expect == actual)22. {23. printf(".");24. }25. else26. {27. printf("F");28. }29. }小知识:XUnit系列的框架的习惯使用assert*的命名来定义判断函数,对于通过的测试习惯打印一个“.”号,而对于失败的测试习惯打印一个“F”。