您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
单元测试工具-Mockito学习心得
 
   次浏览      
 2019-8-15
 
编辑推荐:
本文来自csdn,本文简单介绍了Mockito是什么,以及Mockito在单元测试中的使用,希望能对您有所帮助。

一、Mockito的介绍

1. 什么是mock测试?什么是mock对象?

先来看看下面这个示例:

从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。

 

一种替代方案就是使用mocks

从图中可以清晰的看出

mock对象就是在调试期间用来作为真实对象的替代品。

mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。

知道什么是mock测试后,那么我们就来认识一下mock框架---Mockito

2. 什么是Mockito?

除了有一个好记的名字外,Mockito尝试用不一样的方法做mocking测试,是简单轻量级能够替代EasyMock的框架。使用简单,测试代码可读性高,丰富的文档包含在javadoc中,直接在IDE中可查看文档,实例,说明。更多信息:http://code.google.com/p/mockito/

3. stub和mock

相同点:Stub和Mock对象都是用来模拟外部依赖,使我们能控制。

不同点:而stub完全是模拟一个外部依赖,用来提供测试时所需要的测试数据。而mock对象用来判断测试是否能通过,也就是用来验证测试中依赖对象间的交互能否达到预期。在mocking框架中mock对象可以同时作为stub和mock对象使用,两者并没有严格区别。 更多信息:http://martinfowler.com/articles/mocksArentStubs.html

 

二、Mockito在单测中的使用

1. 使用Maven引入Mockito

在pom.xml文件中直接加入以下依赖,就可以使用Mockito进行单元测试了。

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>

2. Mockito的重要注解

2.1 @Mock

很多情况下,测试一个功能需要依赖许多其他类的返回值,如果其他类没有好,这个类的功能就不能测试。

使用Mockito的功能,我们可以让单元测试脱离对其他类的依赖,如果我们需要其他类返回一个期望的返回值,我们可以直接mock这个返回值,让测试可以很顺利的继续下去,这样测试就能专注于单元内。

@Mock注解能让你仿造一个依赖,然后通过Mockito.when(class.funciton()).thenReturn(Object)这样的格式来模拟返回值。

根据一个例子我们来尝试一下,先新建一个springboot的project,然后建三个类:

UserDO

public class UserDO {
private String name;
private Integer age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

UserManager

import org.springframework.stereotype.Service;

@Service
public class UserManager {
public UserDO getUserDO() {
UserDO user = new UserDO();
user.setName("real");
user.setAge(20);
return user;
}


}

UserInfoManager

import org.springframework.beans.factory.annotation .Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserInfoManager {
@Autowired
private UserManager userManager;

public String getUserName() {
return userManager.getUserDO().getName();
}

public Integer getUserAge() {
return userManager.getUserDO().getAge();
}

}

根据上面的代码我们可以看出UserDO是一个实体类,UserManager和UserInfoManager都是处理业务的service类,而其中UserInfoManager依赖了UserManager。

此时我们想要对UserInfoManager进行单元测试可以,在src/test包里写测试类,首先我们要保证依赖注入,所以我们要对测试类添加注解:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { JavaLearn04MockitoApplicationTests.class })

并把JavaLearn04MockitoApplicationTests.class中的注解@SpringBootTest替换为@SpringBootApplication,让springboot真的跑起来,把上面的类都加载进来,不然会报这个错:

Error creating bean with name 'com.mrh.java.mockito.UserInfoManagerTest': Unsatisfied dependency expressed through field 'userInfoManager';

为了方便我们把应该注解在测试类上的注解,注解到一个BaseTest上,这样之后对所有类的测试类都可以继承这个基类,这样就不用麻烦的一个一个加注解了。

@RunWith(SpringRunner.class)
@SpringBootApplication
public class JavaLearn04MockitoApplicationTests {

@Test
public void contextLoads() {
}

}

import org.junit.runner.RunWith;
import org.springframework.boot.test.context .SpringBootTest;
import org.springframework.context.annotation .PropertySource;
import org.springframework.test.context.junit4 .SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { JavaLearn04MockitoApplicationTests .class })
@PropertySource(value = { "classpath:application.properties" })
public class BaseTest {

}

到此为止,我们可以开始认真的写测试类了:

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory .annotation.Autowired;

public class UserInfoManagerTest extends BaseTest {

@Autowired
private UserInfoManager userInfoManager;


@Test
public void getUserAge() {
Assert.assertTrue(userInfoManager.getUserAge() .equals(20));
}
}

我们预测获取的age数值为20,不然就是错的。

而运行结果,果然为20.

但是如果我们不想过多的依赖userManager的完成度(比如userManager的某个方法还没完成,或者目前还没调通),而给userManger的方法一个伪造的预期结果,这样这个单元测试就可以独立的测试UserInfoManager的逻辑,而不会受到userManager的影响了,应该怎么办呢?

这就是@Mock方法可以大显身手的时候了。

从上面的userInfoManager代码中看出,我们返回的UserDO的age是20,我们在这段代码里使用@Mock注解注入了UserInfoManager,这样我们就可以通过上面提到过的方法来mock一个预期的返回值。

public class UserInfoManagerTest extends BaseTest {

@Mock
private UserInfoManager userInfoManager;

@Test
public void getUserAger() {
Mockito.when(userInfoManager.getUserAge()) .thenReturn(80);
Assert.assertTrue(userInfoManager.getUserAge() .equals(80));
}

}

这段代码只有当getUserAge()方法返回80的时候才会通过,如果不是80,说明我们的mock 没有成功。

结果返回确实是80,通过这样的方法mock数据是可行的。(必须注意的是在跑mock方法之前

那么又不得不提到另外一种场景了,我们不是想要mock UserInfoManager的返回值,而是想要mock UserManager的返回值,这样userInfoManager的代码还照常跑,这样才能验证UserInfoManager中代码的逻辑。

那么我们需要用到下一个很重要的注解@InjectMocks。

2.2 @InjectMocks

这个注解的作用是创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。

使用这个注解可以创建一个实例,并且将你用@Mock注解的对象注入到这个实例中。

用这个注解我们就可以是使用@Mock创建mock的UserManager对象,并用@InjectMocks注解创建一个UserInfoManager的实例将UserManager对象注入其中。

必须提到的是使用这个注解,我们在单测方法最前面一定要执行以下语句:

MockitoAnnotations.initMocks(this);

否则无法将Mock注入。

public class UserInfoManagerTest extends BaseTest {

@Mock
private UserManager userManager;

@InjectMocks
private UserInfoManager userInfoManager;

@Before
public void before() {
MockitoAnnotations.initMocks(this);
}

@After
public void after() {
Mockito.reset(userManager);
}

@Test
public void getUserName() {
UserDO user = new UserDO();
user.setName("mock");
Mockito.when(userManager.getUserDO()).thenReturn(user);
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
}
}

从之前的代码我们可以看出来如果我们真的调用UserManager的getUserDO,返回的对象里的name应该是“real”,而我们这里预期mock成功后,返回的应该是我们的mock数据“mock”。

代码通过,说明mock的依赖正确注入了。

可以看到我们通过@Mock和@InjectMocks,我们可以对返回值和依赖的返回值进行mock,但是下面的代码却出了一点问题:

public class UserInfoManagerTest extends BaseTest {

@Mock
private UserManager userManager;

@InjectMocks
private UserInfoManager userInfoManager;

// @Mock
// private UserInfoManager userInfoManager;

@Before
public void before() {
MockitoAnnotations.initMocks(this);
}

@After
public void after() {
Mockito.reset(userManager);
}

@Test
public void getUserAge() {
Assert.assertTrue(userInfoManager.getUserAge() .equals(20));
}

@Test
public void getUserName() {
UserDO user = new UserDO();
user.setName("mock");
Mockito.when(userManager.getUserDO()).thenReturn(user);
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
}
}

当我想在getUserAge的Test里面使用UserManager真方法,在getUserName的Test里使用mock的返回值时就遇到了问题,虽然我在getUserAge的Test里面并没有对UserManager进行mock,但是他也终究不是一个真的依赖,不能返回真的结果了, 只能返回null。运行结果如下图:

如果我们想对依赖做局部mock,想要的时候就mock它,不想要的时候就运行真的方法应该如何呢?

接下来就要介绍能让这个构想实现的下一个注解:@Spy。

2.3 @Spy

Mockito中的Mock和Spy都可用于拦截那些尚未实现或不期望被真实调用的对象和方法,并为其设置自定义行为。二者的区别在于:

1、Mock声明的对象,对函数的调用均执行mock(即虚假函数),不执行真正部分。

2、Spy声明的对象,对函数的调用均执行真正部分。

于是我们对自己的代码做一下调整:

public class UserInfoManagerTest extends BaseTest {

@Spy
private UserManager userManager;

@InjectMocks
private UserInfoManager userInfoManager;

// @Mock
// private UserInfoManager userInfoManager;

@Before
public void before() {
MockitoAnnotations.initMocks(this);
}

@After
public void after() {
Mockito.reset(userManager);
}

@Test
public void getUserAge() {
Assert.assertTrue(userInfoManager.getUserAge() .equals(20));
}

@Test
public void getUserName() {
UserDO user = new UserDO();
user.setName("mock");
Mockito.when(userManager.getUserDO()) .thenReturn(user);
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
}
}

用@Spy注解后的方法,可以在想mock的使用mock,想用真实方法的时候使用真实方法。

结果代码通过了。

这个时候我新加一个类OrgManager。

import org.springframework.stereotype.Service;

@Service
public class OrgManager {
public String getOrgName() {
return "abc company";
}
}

然后修改UserInfoManager增加一个依赖OrgManager的方法:

@Service
public class UserInfoManager {
@Autowired
private UserManager userManager;

@Autowired
private OrgManager orgManager;

public String getUserName() {
return userManager.getUserDO().getName();
}

public Integer getUserAge() {
return userManager.getUserDO().getAge();
}

public String getOrgName() {
return orgManager.getOrgName();
}

}

这个时候我们在测试类里对这个新方法进行测试:

原因正是因为我们使用@InjectMocks注入了UserManager,但是OrgManager并没有被注入,所以实际上进入方法的时候UserInfoManager的这个依赖是空的。

难道我们要在外面@Spy一个OrgManager然后和UserManager一起注入吗?但是我们并不想对这个类进行mock,如果在依赖多的情况下这样做非常繁琐,那我们能不能也增加@Spy注解UserInfoManager这样,当我们注入@Spy对象的时候使用@Spy对象,否则使用真对象呢?(不确定@Spy有这个功能)。

@InjectMocks
@Spy
private UserInfoManager userInfoManager;

测试后发现@Spy对象能执行真实代码,但是并不能为对象注入依赖。

而@InjectMocks是可以和@Autowired一起使用的,我们可以两个个注解叠加使用,让@Autowired注解为对象注入真依赖。

@InjectMocks
@Autowired
private UserInfoManager userInfoManager;

下图可见这样的方法是可以通过的。

现在还有第三种场景,如果我的依赖的对象还有依赖(这在大多数的项目中是非常常见的),根据上面的说法用@Spy的对象是不会自动注入依赖的,此时怎么处理呢?

我们可以叠加@Spy和@Autowired使用。这个就不举例说明了。

3. 常用方法

3.1 Mockito.when(classObject.function()) .thenReturn(Obj)

用来mock返回值上面已经提过了。

3.2 Mockito.doReturn(obj).when(classObject) .function()

同样是用来模拟数据,但是和3.1 不同的是,3.1会真的调用方法,而3.2不会。

修改之前的测试类来验证一下:

public class UserInfoManagerTest extends BaseTest {

@Spy
private UserManager userManager;

@InjectMocks
@Autowired
private UserInfoManager userInfoManager;


@Before
public void before() {
MockitoAnnotations.initMocks(this);
}

@After
public void after() {
Mockito.reset(userManager);
}

@Test
public void getUserName() {
System.out.println("run getUserName");
UserDO user = new UserDO();
user.setName("mock");
Mockito.when(userManager.getUserDO()) .thenReturn(user);
System.out.println("start getUserName test");
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
Mockito.verify(userManager, times(2)).getUserDO();
System.out.println("end getUserName test");
}


@Test
public void getUserName2() {
System.out.println("run getUserName2");
UserDO user = new UserDO();
user.setName("mock");
Mockito.doReturn(user).when(userManager).getUserDO();
System.out.println("start getUserName2 test");
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
Mockito.verify(userManager, times(2)).getUserDO();
System.out.println("end getUserName2 test");
}
}

如图可见:在进行when().thenReturn()的时候,我们调用了真的方法。但是在doReturn().when()方法的时候,我们并没有真的执行getUserDO(),而是直接创建了返回值。

3.3 Mockito.doNothing().when(classObject).function()

用于模拟无返回值的方法调用。比如我们的function实际跑是跑不通的(报错或没完成),用这个方法就可以让代码直接模拟他成功执行。

比如我们调整之前的代码:

UserManager:


@Service
public class UserManager {
public UserDO getUserDO() {
UserDO user = new UserDO();
user.setName("real");
user.setAge(20);

System.out.println("run getUserDO");
return user;
}


public void unfinishedFunciotn() throws Exception {
throw new Exception();
}

}

UserInfoManager:


@Service
public class UserInfoManager {
@Autowired
private UserManager userManager;

@Autowired
private OrgManager orgManager;

public String getUserName() {
return userManager.getUserDO().getName();
}

public Integer getUserAge() {
return userManager.getUserDO().getAge();
}

public String getOrgName() {
return orgManager.getOrgName();
}


public boolean unfinishedFunciotn() throws Exception {
userManager.unfinishedFunciotn();
return true;
}

}

可以看出新加的方法是一定会抛出异常的,但是如果我们想让unfinishedFunciotn()返回true,应该如何mock呢?

@Test
public void unfinishedFunciotn() throws Exception {
Mockito.doNothing().when(userManager) .unfinishedFunciotn();
Assert.assertTrue(userInfoManager.unfinishedFunciotn());
}

如图所示,只要用这个方法mock了,就不会执行方法里面的代码,也就不会抛出异常了。

3.4 verify()

verify方法用于验证方法总共被调用了几次,且只能验证mock的方法。


@Test
public void getUserName2() {
System.out.println("run getUserName2");
UserDO user = new UserDO();
user.setName("mock");
Mockito.doReturn(user).when(userManager).getUserDO();
System.out.println("start getUserName2 test");
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
Assert.assertTrue(userInfoManager.getUserName() .equals("mock"));
Mockito.verify(userManager, times(2)).getUserDO();
System.out.println("end getUserName2 test");
}

可以验证被注入userInfoManager的userManager里的getUserDO()方法被调用了两次。

 
   
次浏览       
相关文章

微服务测试之单元测试
一篇图文带你了解白盒测试用例设计方法
全面的质量保障体系之回归测试策略
人工智能自动化测试探索
相关文档

自动化接口测试实践之路
jenkins持续集成测试
性能测试诊断分析与优化
性能测试实例
相关课程

持续集成测试最佳实践
自动化测试体系建设与最佳实践
测试架构的构建与应用实践
DevOps时代的测试技术与最佳实践