Mock介绍
基本介绍
为了提高代码质量,除了做静态代码测试,动态的单元测试也少不了。而在单元测试过程中,Mock
是少不了的技术。
Mock
是允许用模拟对象替换测试中的系统部件,并断言它们是如何被使用的一项技术。
Mock的作用如下
- 解决依赖问题:当测试一个接口或者功能模块的时候,如果这个接口或者功能模块依赖其他接口或其他模块,那么如果所依赖的接口或功能模块未开发完毕,那么我们就可以使用Mock模拟被依赖接口,完成目标接口的测试。
- 单元测试:如果某个功能未开发完成,又要进行测试用例的代码编写,也可以先模拟这个功能进行测试。
- 模拟复杂业务的接口:实际工作中如果我们在测试一个接口功能时,如果这个接口依赖一个非常复杂的接口业务或者来源于第三方接口(如第三方支付接口),那么我们完全可以使用Mock来模拟这个复杂的业务接口,其实这个和解决接口依赖是一样的原理。
- 前后端联调:进行前后端分离编程时,如果进行一个前端页面开发,需要根据后合返回的状态展示不同的页面,那么就需要调用后合的接口,但是后合接口还未开发完成,完全可以借助mock来模拟后台这个接口返回想要的数据。
Mock与Stub(桩)
桩代码(Stub)
:用来代替真实代码的临时代码,主要作用是使被测代码能够独立编译、 链接,并独立运行Mock代码
:也是用来代替真实代码的临时代码,起到隔离和补齐的作用,但是它还可深入的模拟对象之间的交互方式,可以对结果进行验证
Mockito测试框架
Mockito是Java单元测试中使用率最高的Mock框架之一,同时也是SpringBoot默认引入的mock框架(spring-boot-starter-test包括JUnit, Hamcrest 和 Mockito)
Maven依赖
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
验证某些行为是否被调用
Mockito可以验证某些行为:一旦mock对象被创建了,mock对象会记住所有的交互
使用方法verify()
判断方法是否被调用过
@Test
void verifyTest(){
Person mockPerson = Mockito.mock(Person.class);
mockPerson.setId(1);
Mockito.verify(mockPerson).setId(1);
Mockito.verify(mockPerson).setName("cc"); // 此方法没有调用过,所以会报错
}
测试桩Stub
Mockito可以做一些测试桩(Stub
),做测试桩的目的是为了在某些方法还没有开发出来,但我们测试时却需要调用它的时候,给这个方法模拟一个假的返回值供我们测试用。
默认情况下,所有函数都有返回值。mock函数默认返回的是null,一个空的集合或者一个被对象类型包装的内置类型。
当我们要指定某的函数的返回值时,可以使用when()
和 ThenReturn()
、ThenThrow()
方法
之后调用这些函数时,就会返回我们上面指定的值(注意返回类型也要正确)
@Test
void stubTest(){
Person mockPerson = Mockito.mock(Person.class);
Mockito.when(mockPerson.getId()).thenReturn(1);
Mockito.when(mockPerson.getName()).thenThrow(new NoSuchMethodError());
System.out.println(mockPerson.getId()); // 返回1
System.out.println(mockPerson.getName()); // 抛出异常NoSuchMethodError
}
一个方法可能多次调用,想要每次调用都设置对应返回值也是可以的
@Test
void constStubTest(){
Person personMock = mock(Person.class);
when(personMock.getName())
.thenReturn("cc")
.thenReturn("1");
System.out.println(personMock.getName()); // 第一次返回cc
System.out.println(personMock.getName()); // 第二次返回1
}
类型范围
使用anyInt()
、anyString()
、anyList()
等方法可以对参数类型进行限制或者放宽范围
@Test
void matchTest(){
Person mockPerson = Mockito.mock(Person.class);
when(mockPerson.setKeyById(anyInt())).thenReturn("0001Test1000");
// 表示只要传入int类型就返回"0001Test1000"
System.out.println(mockPerson.setKeyById(10));
verify(mockPerson).setKeyById(10);
}
方法调用次数验证
times()
可以用来验证方法的调用次数,如果不是预期次数则会不通过
@Test
void timesTest(){
Person mockPerson = mock(Person.class);
mockPerson.setId(1);
mockPerson.setName("cc");
mockPerson.setName("cc");
verify(mockPerson).setId(1);
verify(mockPerson, times(2)).setName("cc");
}
方法调用顺序验证
当我们需要验证方法调用的前后顺序是否符合预期,可以InOrder
类以及它的verify()
方法
不仅使用与单个对象,多个对象的方法之间的调研顺序也可以进行判断
@Test
void orderTest() {
Person singeMock = mock(Person.class);
singeMock.setName("cc");
singeMock.setName("qq");
// 单个对象方法的调用顺序
InOrder inOrder = inOrder(singeMock);
inOrder.verify(singeMock).setName("cc");
inOrder.verify(singeMock).setName("qq"); // 顺序正确
Person firstMock = mock(Person.class);
Person secondMock = mock(Person.class);
firstMock.setId(1);
secondMock.setId(2);
// 多个方法的调用顺序
InOrder inOrder1 = inOrder(firstMock, secondMock);
inOrder1.verify(secondMock).setId(2);
inOrder1.verify(firstMock).setId(1); // 顺序错误
}
spy监控对象
spy可以监控或控制一个真正new创建出来的对象,而不是去mock的对象,两者区别在于mock的对象如果不指定返回值就会返回null这样的零值,而spy则介于真正的bean和mock之间,对于被我们指定了返回值的优先按指定的,没有指定的就按对象实际值返回。
下面的例子是修改一个对象中某个属性的返回值,使得其返回值不在是属性的真实值
也可以用@Spy
注解
@Test
void spyTest(){
Person person = new Person(1, "cc", null);
Person spy = spy(person);
when(spy.getName()).thenReturn("qq");
System.out.println(spy.getName()); // 返回qq,而不是cc
System.out.println(spy.getId()); // 返回1,因为没有设置过
}
Spring Boot中的单元测试
Spring Boot的结构上来说,Service层是依赖Dao层的,而Controller层又依赖于Service层
从单元测试的角度,对某个Service和Controller进行单元测试的时候,所有需要依赖的类或方法都应该mock,只针对某一段代码进行运行逻辑测试,测试Service层时,就可以使用Mockito来隔离Dao层;Dao层的单元测试可以用工具随机生成测试数据,然后使用工具测试SQL即可,就不要还启动整个应用,效率比较低。
下面是几种mock的方案
- 在before函数中直接mock实例化对象
@BeforeEach
public void before(){
userService = mock(UserService.class);
}
- 使用
@Mock
注解来Mock对象
@Mock
private UserSerice userService;
@BeforeEach
public void before(){
MockitoAnnotations.initMocks(this); // 此方法已被deprecad
}
- 使用
@RunWith(MockitoJunitRunner.class)
,搭配@Mock就可以直接使用了,不用再初始化(此方法只能用于Junit4)(较为推荐)
@RunWith(MockitoJunitRunner.class)
@SpringBootTest
public class Test {
@Mock
private UserSerice userService;
@Test
public void test(){
// code
}
}
- 使用
MockitoRule
,但是对象的访问级别必须是public(此方法也只能用于Junit4)
@RunWith(JUnit4.class)
@SpringBootTest
public class Test {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
private UserSerice userService;
@Test
public void test(){
// code
}
}
单元测试常用
注解
@Test
标注一个测试方法
@BeforeEach
/ @AfterEach
在每个测试方法之前/后运行
@BeforeAll
/ @AfterAll
在整个测试流程之前/后运行,整个测试过程只会运行一次
@SpringBootTest
初始化Spring上下文来进行测试,当测试中用到类需要用Spring容器管理时加
@MockBean
同Mockito的@Mock
@SpyBean
同Mockito的@Spy
断言
Assert
类
JUnit4提供,使用较为简单,点进去看源码的方法即可。
Assertions
类
JUnit5提供,和Assert类差不多。
assertThat(T actual, Matcher<? super T> matcher);
assertThat(Assert类中)是JUnit 4.4引入的一个全新的断言语法,可以只使用这一个方法,实现所有的断言测试。
其中actual为需要测试的变量值,matcher为使用Hamcrest的匹配符来表达变量actual期望值的声明,如果值与matcher所表达的期望值相符,则测试成功,否则失败。
匹配器有可读性强、满足复杂匹配条件等优点,常用匹配器可以查文档,也不难使用。