一.前言
关于这篇文章的起源,是第三次思沃大讲堂的作业的题目中,有这样一段话
把前两问做的类集成起来,写一个集成的单元测试,写一个集成测试。
问题来了,集成的单元测试和集成测试有什么区别呢?
集成测试(Integration Testing):是在单元测试的基础上,将所有模块按照概要设计要求组装成为一个子系统或者系统,进行集成测试。一些模块虽然能够单独工作,但并不能保证连接起来也能正常的工作,程序在某些局部反映不出来的问题,在全局上很可能暴漏出来,因此集成测试十分必要。
集成的单元测试:按字面意思的理解,就是对该集成类进行单元测试。单元测试就是对已经实现的软件最小单元进行测试以保证构成软件系统的各个单元的质量。这么说来,在此处集成的单元测试和集成测试并无区别吗?
No~ 区别还是有的,集成的单元测试,首先是个单元测试,然后是对集成类进行单元测试,也就是说只测试该类的逻辑,而不用关注他所依赖的类是否正确实现。如何不关注依赖类的是否正确实现呢?这就是接下来我要介绍的Mock啦,这得感谢我的Buddy,让我对Mock有了直白的认识,然后才关注Mock,从而进一步了解。
二.为什么需要mock
我们在做测试的时候,往往会发现我们要测试的类或方法会引用很多外部依赖的对象,而我们没法控制这些外部依赖的对象,为了解决这个问题,我们需要用到Mock来模拟这些外部依赖的对象,从而控制它们。举个例子,service调用dao,即service依赖dao,我们可以用mock来模拟真实的dao调用,从而达到测试service的目的。
模拟对象(Mock Object)可以取代真实对象的位置,用于测试一些与真实对象进行交互或依赖于真实对象的功能,模拟对象背后的目的就是创建一个轻量级的,可以控制的对象来代替测试中需要的真实对象,模拟真实对象的行为和功能。
mock对象使用范畴
1.真实对象具有不可确定的行为,产生不可预测的效果。
2.真实对象很难被创建的。
3.真实对象的某些行为很难被触发。
4.真实对象实际上还不存在的。
三.常见的mock框架
- jmock:通过mock对象来模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得测试变得简单。缺点:在执行前记录期望行为,显得很繁琐。
- Mockito:Mockito通过在执行后校验哪些函数已经被调用,消除了对期望行为的需要,API非常简洁。缺点:对于静态函数、构造函数、私有函数等还是无能为力。
- powermock:PowerMock是在Mockito的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 实现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。缺点:会对字节码篡改,即测试时的字节码与平时编译出来的字节码是不一样的,而很多统计单元测试覆盖率的插件是以字节码来统计的,所以PowerMock编写的测试程序不能被统计进覆盖率。
推荐Mockito和powermock
四.Mockito简单介绍
一般使用Mockito需要执行以下步骤:
1.模拟并替换测试代码中外部依赖。
2.执行测试代码。
3.验证测试代码是否被正确的执行
使用注解(@Mock、@InjectMocks等)的话,必须实例化mock对象,有两种方式实例化mock对象:
- @RunWith(MockitoJUnitRunner.class)
- MockitoAnnotations.initMocks(this)
当我们需要配置某个方法的返回值时,Mockito提供了链式的API供我们方便的调用:
- when(mockObject.someMethod()).thenReturn(...)
用来定义当条件满足时函数的返回值。 - when(mockObject.someMethod()).thenReturn(...).thenReturn(...)
用来定义多个返回值的情况。 - doReturn(...).when(mockObject.someMethod())
- when(mockObject.someMethod()).thenThrow(new Runtime
Exception())
执行某方法时抛出异常。 - doThrow(new RuntimeException()).when(mockObject.someMethod())
- 对void方法进行预期设定
1.doNothing().when(mock.someMethod())
2.doThrow(new RuntimeException()).when(mock.someMethod())
3.doNothing().doThrow(new RuntimeException()).when(mock.someMethod()) - Mockito会自动记录自己的交互行为,可以用verify(…).methodXxx(…)语法来验证Xxx()方法是否按照预期进行了调用
1.验证调用次数:verify(mockObject,times(n)).someMethod(argument);//n为被调用的次数
2.验证超时:verify(mockObject, timeout(100)).someMethod();
3.既验证调用次数,又验证是否超时:verify(mockObject, timeout(100).times(1)).someMethod();
需注意:
- 对于final、static方法,Mockito 无法对其 when(…).thenReturn(…) 操作。
五.Mockito使用实例
以第三次思沃大讲堂的作业为例。
问题描述:游戏开始后,系统会随机给出一个四位,每位都不重复的数字作为答案。由用户输入自己猜测的 四个数字。 系统会将两个数字进行对比,并给形出xAxB的提示, 比如”2A1B”。 如果数字猜对而且位置也对,就是一个A。 如果数字猜对但位置不对,就是一个B。 例如:系统给出”1234”,用户输入”1234” 返回”4A0B” 系统给出”1234”,用户输入”4321” 返回”0A4B”。
CompareNumber类:实现比较。只有一个函数,该函数接受两个参数,一个是答案,一个是用户输 入的四位数。返回值是xAxB的字符串 。
AnswerGenerator类:生成随机的四位无重复位数字。只有一个函数,返回一个四位,每位都不重复随机数。
Guess类:只有一个函数,只有一个参数。把前两问做的类集成起来。
GuessUnitTest类:对Guess类写单元测试(即文首所说的集成的单元测试)。
public class Guess {
private CompareNumber compareNumber = new CompareNumber();
private AnswerGenerator answerGenerator = new AnswerGenerator();
private int answer=answerGenerator.generatorFourDigits();
public String guessTheDigit(int guessDigit){
String result = compareNumber.compareToAnswer(answer,guessDigit);
return result;
}
}
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.lang.reflect.Field;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class GuessUnitTest {
@InjectMocks
private Guess guess = new Guess();
@Mock
private CompareNumber mockCompareNumber = new CompareNumber();
@Mock
private AnswerGenerator mockAnswerGenerator = new AnswerGenerator();
@Before
public void init() throws NoSuchFieldException, IllegalAccessException {
/*返回guess已声明字段answer*/
Field f = guess.getClass().getDeclaredField("answer");
/*值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查*/
f.setAccessible(true);
/*将指f对象表示的字段answer设置为指定的值,即1234。*/
f.set(guess, 1234);
}
@Test
public void test_4A0B() {
when(mockCompareNumber.compareToAnswer(1234, 1234)).thenReturn("4A0B");
String result = guess.guessTheDigit(1234);
assertTrue("4A0B".equals(result));
}
@Test
public void test_0A4B() {
when(mockCompareNumber.compareToAnswer(1234, 4321)).thenReturn("0A4B");
String result = guess.guessTheDigit(4321);
assertTrue("0A4B".equals(result));
}
@Test
public void test_2A2B() {
when(mockCompareNumber.compareToAnswer(1234, 1432)).thenReturn("2A2B");
String result = guess.guessTheDigit(1432);
assertTrue("2A2B".equals(result));
}
}
说明
@Mock:创建一个mock对象(模拟对象)。
@InjectMock:创建一个实例,@Mock注解创建的模拟对象将被注入到该实例中。
@Before:Junit注解,在每个测试执行之前必须执行的代码。
@Test:Junit注解,标明是一个测试方法。
Junit4常用注解
- @Before:初始化方法,在任何一个测试执行之前必须执行的代码。
- @After:释放资源,在任何测试执行之后需要进行的收尾工作。
- @Test:测试方法,表明这是一个测试方法。在Junit中将会自动被执行。
- @Ignore:忽略的测试方法,含义为“某些方法尚未完成,暂不参与此次测试”。测试结果提示你有几个测试被忽略,而不是失败。
- @BeforeClass:针对所有测试,在所有测试方法执行前执行一次。
- @AfterClass:针对所有测试,在所有测试方法执行结束后执行一次。
在Junit4中,单元测试用例的执行顺序:
每个测试方法的执行顺序:
六.Mockito学习资料
Mockito官网:http://site.mockito.org/
Mockito官方文档:https://static.javadoc.io/org.mockito/mockito-core/2.13.0/org/mockito/Mockito.html
Mockito中文文档:http://blog.csdn.net/bboyfeiyu/article/details/52127551
Mockito Github:https://github.com/mockito/mockito