1. 单测测什么
1.1 模块接口
- 调用所测模块时输入参数与模块的形式参数在个数、属性、顺序上是否匹配
- 所测模块在调用其他模块时,它输入给其他模块的参数在个数、属性、顺序上是否匹配
- 是否修改了只做输入用的形式参数
- 输出给标准函数的参数在个数、属性、顺序上是否匹配
- 全局变量的定义在各模块中是否一致
- 限制是否通过形式参数来传递
1.2 局部数据结构
- 不正确的或者不一致的数据类型
- 使用未赋值或者未初始化的变量
- 错误的初始值或者错误的默认值
- 变量名拼写错误
1.3 逻辑错误
- 不正确的计算、比较和控制流
1.4 异常处理
- 出错的描述难以理解
- 出错的描述不足以对错误定位和确定出错原因
- 显示的错误与实际错误不符
- 对错误条件的处理不正确
- 在对错误进行处理之前,错误条件已经引起了系统的干预
1.5 边界
- 在循环的第零次,第一次和最后一次是否有错误
- 运算或者判断中最大最小值是否有错误
- 数据流、控制流中刚好大于、小于或等于最大或最小值时是否有错误
2. 单测工具
2.1 JUnit
JUnit是一个开放源代码的Java测试框架,用于编写和运行可重复的测试。它是单元测试框架体系xUnit的一个Java语言的实例。其安装简单、使用方便,并易于集成到代码体系中,进行丰富、可控的代码级别的测试。
2.2 MockMvc
MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。
2.3 Mockito
Mockito是一个强大的用于Java开发的模拟测试框架,通过Mockito可以创建和配置Mock对象,进而简化有外部依赖的类的测试。
3. JUnit详解
3.1 Junit基本概念
3.1.1 被测系统 (SUT)
被测系统 (System under test) 表示正在被测试的单元,目的是测试系统能否正确工作。根据测试类型的不同,SUT指代的内容也不同,例如SUT可以是一个类甚至是一个方法。
3.1.2 测试依赖组件(DOC)
被测单元所依赖的组件,例如对UserService服务进行单元测试时,UserService会依赖UserDao,因此 UserDao就是DOC。
3.1.3 测试替身(TD)
一个实际的系统会依赖多个外部对象,但是在进行单元测试时,会用一些功能较为简单的并且其行为和实际对象类似的假对象来作为SUT的依赖对象,以此来降低单元测试的复杂性和可实现性。在这里,这些假对象就被称为测试替身(Test Double)。
测试替身有如下5种类型:
-
Test stub,为SUT提供数据的假对象。
假设SUT需要从HTTP接口中获取站点信息数据,这个获取数据的接口被封装为getSite方法。在对这个SUT进行测试时,显然不太可能专门开一个HTTP服务来提供此接口,而是提供一个带有getSite方法的假对象,从这个假对象中获取数据。在这个例子中,提供数据的假对象就叫做Test stub。 -
Fake object
实现了简单功能的一个假对象。Fake object和Test stub的主要区别就是Test stub侧重于用于提供数据的假对象,而Fake object没有这层含义。使用Fake object的最主要的原因就是在测试时,某些组件不可用或运行速度太慢,因而使用Fake object来代替它们。 -
Mock object
用于模拟实际的对象,并且能够校验对这个Mock object的方法调用是否符合预期。实际上,Mock object是Test stub或Fake object一种,但是Mock object有Test stub/Fake object没有的特性,Mock object可以很灵活地配置所调用的方法所产生的行为,并且它可以追踪方法调用,例如一个Mock object方法调用时传递了哪些参数,方法调用了几次等。 -
Dummy object
在测试中并不使用,但是为了测试代码能够正常编译/运行而添加的对象。例如调用一个Test Double对象的一个方法,这个方法需要传递几个参数,但是其中某个参数无论是什么值都不会影响测试的结果,那么这个参数就是一个Dummy object。Dummy object可以是一个空引用,一个空对象或者是一个常量等。简单的说,Dummy object就是那些没有使用到的,仅仅是为了填充参数列表的对象。 -
Test spy
可以包装一个真实的Java对象,并返回一个包装后的新对象。若没有特别配置的话,对这个新对象的所有方法调用,都会委派给实际的Java对象。mock和spy的区别是:mock是无中生有地生出一个完全虚拟的对象,它的所有方法都是虚拟的;而spy是在现有类的基础上包装了一个对象,即如果我们没有重写spy的方法,那么其实都是调用的被包装的对象的方法。 -
Test fixture
所谓Test fixture,就是运行测试程序所需要的先决条件(precondition)。即对被测对象进行测试时所需要的一切东西。这个东西不单单指的是数据,同时包括对被测对象的配置,被测对象所需要的依赖对象等。 -
测试用例(Test case)
这里的测试用例指的是代码级别的测试方法,在JUnit 3中,测试方法都必须以test为前缀,且必须是public void 的,JUnit 4之后,就没有这个限制了,方法签名可以是任意的。 -
测试套件
通过TestSuit对象将多个测试用例组装成一个测试套件,这样通过操作测试套件就可以对多个测试用例同时进行批量单元测试。
3.2 Junit生命周期
JUnit4测试用例的完整的生命周期要经历如下几个阶段:
- 类级初始化资源处理
- 方法级初始化资源处理
- 执行测试用例中的方法
- 方法级销毁资源处理
- 类级销毁资源处理
其中,类级初始化和销毁资源处理在每一个测试用例类中仅仅执行一次,方法级初始化和销毁资源处理在执行测试用例的每个测试方法时都会被执行一次。
3.3 Junit注解
- @RunWith 指定一个特殊的运行器,如Suite.class套件运行器。
- @SuiteClasses 将需要进行测试的类列表作为参数传入。通过@RunWith和@SuteClass两个注解, 可以创建一个测试套件。
- @Test (expected = Exception.class) 表示预期会抛出Exception.class的异常。
- @Test(timeout=100) 表示预期方法执行不会超过100毫秒,可以防止死循环。
- @Ignore 含义是“某些方法尚未完成,暂不参与此次测试”。这样的话测试结果就会提示有几个测试被忽略,而不是失败。一旦完成了相应函数,只需要把@Ignore注解删去,就可以进行正常的测试。
- @BeforeClass 表示该方法只执行一次,并且在所有方法之前执行。一般可以使用该方法进行数据库连接操作,注意该注解运用在静态方法上。
- @AfterClass 表示该方法只执行一次,并且在所有方法之后执行。一般可以使用该方法进行数据库连接关闭操作,注意该注解运用在静态方法上。
- @Before 表示该方法在每一个测试方法之前运行,可以使用该方法进行初始化之类的操作。
- @After 表示该方法在每一个测试方法之后运行,可以使用该方法进行释放资源、回收内存之类的操作。
4. 单测实战
4.1 简单方法测试
pom.xml
<dependencies>
<dependency>
<groupId>