【格言1】若程序的某项功能没有经过自动测试,那该功能基本等于不存在
【格言2】敏捷测试的第一步就是不要敏捷,先一步步把自动化做好,把持续集成做起来,创建更多的测试工具来提高测试效率,把质量反馈系统做起来,代码质量检查做起来等等,这些建立起来后,你就很敏捷了
【格言3】如果在功能测试和单元测试选一个来做的话,那你应该选择功能测试,70%的应用程序错误都可以通过功能测试来找回的。
参考文献
1、《junit in action》 电子工业出版社
2、《软件测试的艺术》 机械工业出版社【力荐】
一、理论知识点
1、什么叫做测试阶段,什么叫做测试方法?
测试阶段:单元测试、集成测试、功能测试、系统测试,这个叫做测试阶段
测试方法:从大的分类讲,白盒测试、黑盒测试、灰盒测试、人工测试,这个叫做测试方法
2、什么样的测试叫做单元测试?单元到底是什么,多小多大的东西才叫单元?
百度百科说法:要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。
《junit in action》说法:单元测试测的是独立的一个工作单元。在java应用程序中,“独立的一个工作单元”常常指的是一个方法。一个工作单元是一项任务,它不依赖于其他任务的完成。
《软件测试的艺术》说法:单元测试是对程序中的单个子程序、子程序或过程进行测试的过程,是对构成程序的较小模块的测试。
各位看官,你们看懂了么?反正我没看懂。上面有说单元是java中的一个类,又说是java代码中的一个函数方法,这到底是闹哪样。以下是我猜测的结论:被测单元的粒度大小是由程序员规定的,“单元”不仅仅是对应一个代码中方法或某个类,应该说一个具有独立任务的功能代码短(软件编程有一个单一原则,一个方法对应一个功能点)。
3、什么样的测试叫做集成测试?
单元A + 单元B = 更大单元C
单元A:保存用户数据信息到数据库
单元B:下发邮件(短信)到用户邮箱(手机)
更大单元C:注册功能
插曲,请回过头来理解这句话:“《junit in action》一个工作单元是一项任务,它不依赖于其他任务的完成”。第一:单元A和单元B互不依赖;第二:输入可以使用其他单元的输出数据
集成测试:各个单元组合测试,完成某一大功能的测试。单元A测试正确之后,把单元B加入测试。注意:尽量使用增量测试,就是说A,B,AB,C,ABC,D,ABCD这样顺序,而不是A,B,C,D,ABCD这样,虽然少了一点,但是错误不好定位。
4、什么样的测试叫做功能测试?
看看上面的注册功能测试C,可以当作功能测试中的一个步骤了。
功能测试:测试软件表现的是不是符合spec上面规定的
5、什么样的测试叫做系统测试?
系统测试:测试软件表现的是不是符合最初目标(用户需求),包含安全性测试、性能测试、容量测试、可靠性测试等。
6、黑盒测试有哪些方法?
黑盒测试:有正确输入和预期的输出,用正确的输入来测试软件,看看输出数据(表现状态)是否和预期的一致。
我们只讲主要的:等价类测试、边界分析值测试、错误猜测、因果图等方法
等价类测试:
有效(正确的)输入作为一类,其他无效的作为另一类,注意了其他无效的应该是分开的
比如用户名8到16个字符,正确的有效的一个类:对于8个小于16个
错误的类需要分开,而不能写成这样作为一个等价类:小于8或者大于16,因为编译器遇到小于8的时候,就不会忽略后面的错误了。
错误等价类:1、小于8 2、大于16 独立性
边界测试:一般是对数量、个数啊、是、否、有、无、相同、不同、最高、最低进行边界数据测试(包含输出的结果反推到输入数据,比如y=sinx函数,他对输入x没有边界,但是对输出y是有边界的,那就是当y=1的时候,x的那个值需要被测试)
原则:
1、有效值边界,最大值、最小值、比最小少一点、比最大大一点,比如用户名是8到16位,那么边界测试用例数据包含:8,16,7,17
2、输入和输出是有序的,注意第一个和最后一个元素
3、数据库最大记录数,最小记录数
因果图测试:一般是两个输入组合作为条件产生输出的。比如买雪碧,需要两个输入条件1:投入钱币数额2、用户按了哪个按钮。
可能用户投入的是1.5元,按了可乐,那自动饮料机就会输出可乐了。。。输出由两个输入组合决定。
错误猜测:靠猜,可能某些容易犯错误。
7、白盒测试有哪些方法?
白盒测试:语句覆盖测试、判定覆盖测试、条件覆盖测试、判定/条件覆盖测试、多重条件覆盖测试等。
亲,在实际项目中比较靠谱的是多重条件覆盖测试,其他的你可以忘掉。
不想看具体说明的,下面可以跳过
-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-
白盒测试:覆盖程序逻辑结构(源代码)的程度,完全的白盒测试是将程序中每条路径都执行到,然而对有带有循环的程序,完成路径测试是不切合实际的。
完全路径测试是测试不出来真正的错误。语句覆盖——每条语句都覆盖过——较弱的逻辑覆盖准则
判定覆盖或分支覆盖——较强的逻辑覆盖准则。要求必须编写足够的测试用例,使得每一个判断都至少有一个为真和为假的输出结果。每一条语句都必须覆盖过
If(b==2 || a > 1) 这种判断覆盖,他只负责if真和假的路径,测试用例可以这样 b=2 a=1或者b=3 a=1 。但是正确的语句应该是if(b==2 || a <1),这样测试用例测试不出来
条件覆盖——要对if中的判断语句每个条件的所有可能都覆盖到,比如IF(A&B) 测试用例可以是:1(A为真,B为假) 或者 2(A为假,B为真),你瞧瞧A的真假都设计到,B的真假都设计到了,他们满足条件覆盖的测试用例,但是if语句中的then没有被执行。于是引入了判定/条件覆盖
判定/条件覆盖准则——较强的测试用例设计准则,每个判断中的每个条件的所有可能的结果至少执行一次,每个判断的所有可能的结果至少执行一次。要求是设计出充足的测试用例。
还是例子IF(A&B) ,1:条件A和B所有可能结果是A为真,A为假,B为真,B为假,
2:判断的所有可能A&B为真 , A&B为假。
测试用例:1(A为真,B为真) 2(A为假,B为假)满足这个准则,想想有问题么?
具体点if(a>0 && b>1) 其中A代表a>0, B代表b>1
测试用例:1(a=2, b=3)真,执行then语句;2(a=-3,b=1)假,不执行then语句。满足上面判定/条件覆盖准则
问题:其实逻辑判断语句b>1是错误的,他应该是b>2 正确的逻辑代码是if(a>0 && b>2)
结论:判断/条件覆盖准则的测试用例发现不了这个问题。
多重条件覆盖准则——,将每个判定中的所有可能的条件结果进行组合,所有的入口点都至少执行一次。注意了,不是多重判定/条件覆盖准则,但是他满足了判定准则、条件覆盖准则(不用说)、判定/条件覆盖准则。IF(A&B) 对于这个,测试用例必须覆盖下面4个组合是:
A | B | 结果 |
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
\
较上面多了2个
他做到了条件组合,但是并没有做到一个方法内部的判定组合:
If(a>1 && b==0) {
X = x /a; 。。。。。。。。。。。。。。。。。。。。。。。。。运行1
}
If(a == 2 || x > 1) {
X = x + 1;。。。。。。。。。。。。。。。。。。。。。。。。。。。运行2
}
8种条件组合
| a | b | x | 结果 |
1 | >1 | =0 |
| 1 |
2 | >1 | <>0 |
| × |
3 | <=1 | =0 |
| × |
4 | <=1 | <>0 |
| × |
5 | =2 |
| >1 | 2 |
6 | =2 |
| <=1 | × |
7 | <>2 |
| >1 | × |
8 | <>2 |
| <=1 | × |
只要测试用例覆盖了以上的组合就可以了
a | b | x | 覆盖了 |
2 | 0 | 4 | 1,5 |
2 | 1 | 1 | 2,6 |
1 | 0 | 2 | 3,7 |
1 | 1 | 1 | 4,8 |
上面给出的4个具体的测试用例,覆盖了所有的条件组合。同样也覆盖了所有的语句,但是某些组合判定没有被覆盖到。比如1,6组合,运行了语句1,其他不运行。
-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-
总结:从上面几个来看,最完整的应该建议使用多重条件覆盖测试准则。
8、哪个测试阶段会使用某些测试方法?
1、单元测试
若你是敏捷开发者,单元测试肯定是黑盒测试方法。(因为先写测试代码)
若不是,单元测试使用白盒测试、黑盒测试多种方法组合
2、集成测试
集成测试是单元测试基础上,如何把单元模块组装起来一起测试,测试方法采用黑盒。组装方式有自顶向下和自下向上两种。
比如之前说的
单元A + 单元B + 单元C = 更大单元D
单元A:保存用户数据信息到远程UCenter数据库
单元B:下发邮件(短信)到用户邮箱(手机)
单元C:保存用户数据信息到本地数据库
更大单元D:注册功能,保存成功之后
对于单元D,要求是单元A成功后,才能运行单元B和C,他们的关系A在顶端,BC在下面
自顶向下:A, B, C,AB,ABC
自下向上:B ,C, A,BC,ABC
原则:关键单元先测,然后在组合其他的
注意:A是调用远程接口,我们在开发的时候远程代码还没有完成,那我们该如何完成上面的集成测试了?使用mock,自己模拟写一个A的功能,此功能不处理任何逻辑。或者用stub,需要你实现逻辑。Ps:为什么计算机中把stub翻译成桩,很难理解!!
9、一般测试的步骤是什么样的?
第一步:单元测试
1、选择测试方法,到底是选择白盒测试、还是黑盒测试
一:一般是使用组合测试方法,但是如果是敏捷开发方式,只能用黑盒测试方法了
二:选择白盒测试中的多重条件测试准则
三:如果接口文档中说输出是由输入条件组合才能得出,请使用黑盒的因果图分析方法
四:任何情况都要使用边界值分析方法
五:其他情况,使用等价类和错误猜测技术增加更多的测试用例进行补充
2、设计测试用例
根据1中列出来的测试方法来列出测试用例,覆盖白盒测试中的条件组合,
表格如下:
测试用例编号 | 输入 | 预期的输出 | 注释 |
1 |
| 没有返回值,表示成功 |
|
2 | 。。。。 | 。。。。 | 。。。 |
原则1:测试用例尽量少
原则2:一个测试用例覆盖尽量覆盖多个组合(其实和原则1相辅相成)
3、起单元测试方法名称
某被测方法或者功能userRegister
原则1:测试名使用驼峰式 xxYyyZzz
原则2:命名规则,由以下组成test+被测方法+预期输出的类型。如上面测试用例1,他的命名testUserRegisterSuccess。
原则3:一个测试方法,只处理一种预期类型(个人理解)。
在《junit in action》书中说道:“一个单元测试等于一个测试方法,不要试图把多个测试塞进一个方法” 什么意思啊?看懂了么?
表格如下:
方法名 | 检查到的测试用例(这里是测试用例表中的序号) |
testAccountExistUserInfoNull | 9 |
testAccountExistUsernameExisted | 3 |
testAccountExistEmailExisted | 4 |
testAccountExistMobileNumExisted | 6 |
testAccountExistUserTrue | 1、2、5 |
4、写测试代码方法内部使用Assert.fail(“未测试”);Why?避免遗漏测试方法,养成好习惯:)
5、最后填充测试方法
原则一:对于assertTrue/assertNotNull/assertNull/assertFalse测试方法,请记住第一个参数sring需要写,便于失败原因。
原则二:把共有初始化行为放在setUp方法中
第二步:集成测试
1、有时候需要写mock或者stub
2、重要功能的单元测试先加进来,然后加上不重要的单元
其他:功能测试、系统测试,这里不讲
二、具体实践
0、已有模块代码:
此模块的测试描述:用户判断用户名、手机号码、邮箱地址是否重复
1、选择测试方法,到底是选择白盒测试、还是黑盒测试
N1:选择白盒测试中的多重条件测试
N2:选择黑盒测试中的边界值分析法
N3:其他补充(错误猜测法)
2、设计测试用例
N1:基于上面多重条件测试方法。
把所有的if和while等判断语句找出来
判定(代码行数) | 条件 | 正确结果 | 错误结果 |
297 | User.getUsername()!=null | 用户名不为空 | 用户名为空 |
299 | USERNAME_EXIST.equals(resultStr) | 用户名已经存在 | 用户名不存在 |
304 | User.getEmail()!=null | 邮箱地址不为空 | 邮箱地址为空 |
306 | EMAIL_EXIST.equals(resultStr) | 邮箱地址已经存在 | 邮箱地址不存在 |
311 | User.getMobile()!=null | 手机不为空 | 手机为空 |
311 | User.getMobile().getMobileNum()!=null | 手机号码不为空 | 手机号码为空 |
313 | MOBILE_EXIST.equals(resultStr) | 手机号码已经存在 | 手机号码不存在 |
1、用户名为空
2、用户名不为空,用户名重复
3、用户名不为空,用户名不重复
4、邮箱地址为空
5、邮箱地址不为空,邮箱地址重复
6、邮箱地址不为空,邮箱地址不重复
7、手机号码为空
8、手机号码不为空,手机号码重复
9、手机号码不为空,手机号码不重复
那么测试用例必须覆盖上面的9个组合,注意了:一个测试用例尽量覆盖多个上面的组合条件
测试用例编号 | 输入 | 预期的输出 | 注释 |
1 | username=”” email=”email”//邮箱地址可以注册 mobileNum=”” | 程序状态不变 Result=0
| 覆盖了条件:1、6、7 |
2 | username=“username” email=”” mobileNum=”” 此用户名不存在数据库中 需要给出数据库预置的内容 | 程序状态不变 Result=0
| 覆盖了条件: 3、4、7 |
3 | username=”existed_username” email=”” mobileNum=”” | 程序返回1 | 覆盖了条件: 2、4、7 |
4 | email =”existed_ email” username=”” mobileNum=”” | 程序返回2 | 覆盖了条件: 1、5、7 |
5 | mobileNum =“123123123123” 此手机号码不存在数据库中 需要给出数据库预置的内容 Username=”” Email=”” | 程序状态不变 Result=0
| 覆盖了:1、4、9 |
6 | mobileNum =”32132132131” 此手机号码重复 Username=”” Email=”” | 程序返回3 | 覆盖了:1、4、8 |
7 | Username=”” Email=”” mobileNum=”” | 程序返回4 | 覆盖了:1、4、7 |
N2:选择黑盒测试中的边界值分析法
还记的上面讲的原则么?
原则:
1、有效值边界,最大值、最小值、比最小少一点、比最大大一点,比如用户名是8到16位,那么边界测试用例数据包含:8,16,7,17
2、输入和输出是有序的,注意第一个和最后一个元素
3、数据库最大记录数,最小记录数
但是我发现这个单元很难找到边界值,单元并没有对输入值范围、大小进行明确的规定。读者,您有好的用例么?帮忙给出。谢谢了。
N3:其他补充(错误猜测法)
User如果为空,上面的方法就是废物,而且运行期会抛出空指针异常。所以有时候设计用例的时候,会反过来发现代码设计的有问题。这部分白盒测试没办法指导。
测试用例编号 | 输入 | 预期的输出 | 注释 |
8 | User=null | 程序返回4 | 返回是无效参数 |
其他的错误猜想,没想到,请各位看官指点一二。
3、起单元测试方法名称
方法 | 检查到的测试用例 |
testAccountExistUserInfoNull | 7,8 |
testAccountExistUsernameExisted | 3 |
testAccountExistEmailExisted | 4 |
testAccountExistMobileNumExisted | 6 |
testAccountExistUserTrue | 1、2、5 |
4、写测试代码方法内部使用Assert.fail(“未测试”);Why?避免遗漏测试方法,养成好习惯:)
5、最后填充测试方法
待续~