一、什么是mock测试,什么是mock对象?
先来看看下面这个示例:
从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。
一种替代方案就是使用mocks
从图中可以清晰的看出
mock对象就是在调试期间用来作为真实对象的替代品。
mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。
二、什么是Mockito
Mockito是一个针对Java的mocking框架。它与EasyMock和jMock很相似,但是通过在执行后校验什么已经被调用,它消除了对期望行为(expectations)的需要。其它的mocking库需要你在执行前记录期望行为(expectations),而这导致了丑陋的初始化代码。
官方网站:http://code.google.com/p/mockito/
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.10.19</version> </dependency>
java类里要引入
三、Stub和Mock
相同点:Stub和Mock对象都是用来模拟外部依赖,使我们能控制。
不同点:而stub完全是模拟一个外部依赖,用来提供测试时所需要的测试数据。而mock对象用来判断测试是否能通过,也就是用来验证测试中依赖对象间的交互能否达到预期。在mocking框架中mock对象可以同时作为stub和mock对象使用,两者并没有严格区别。 更多信息:http://martinfowler.com/articles/mocksArentStubs.html
四、mockito入门实例
1.1、模拟对象
// 模拟LinkedList 的对象 LinkedList mockedList = Mockito.mock(LinkedList.class); // 此时调用get方法,是会返回null,因为还没有对方法调用的返回值做模拟 System.out.println(mockedList.get(999));
1.2、模拟方法调用的返回值
// 模拟获取第一个元素时,返回字符串first Mockito.when(mockedList.get(0)).thenReturn("first"); // 此时打印输出first System.out.println(mockedList.get(0));
1.3、模拟方法调用抛出异常
Mockito.doThrow(new RuntimeException()).when(mockedList).clear();
1.4、模拟方法调用的参数匹配
Matchers类内加你有很多参数匹配器 anyInt、anyString、anyMap.....Mockito类继承于Matchers,Stubbing时使用内建参数匹配器。例如anyint()如下:
1.5、验证方法调用次数
示例二(web中的3层中的针对某一层的测试):
以前没接触过Mock类型的框架,比如说要测试action层,我总是从action层调用service再调用dao访问数据库,这种方式从原则上来说是无疑是非常正确的,在没用mock框架之前我就隐隐约约的感觉到了这种方式有个不足的地方,那就是速度问题,测试action层的时候需要访问下面两层,如果我们下面两层已经经过单元测试证明是ok的,那么如果测试action层的时候再调用下面两层就等于是做了重复的动作,逻辑上没问题,只是有点重复,并且速度很慢,毕竟项目做到靠后期的时候文单元测试非常多,maven在自动测试的时候速度会非常慢。
而mock框架原理就是模拟对象,方法调用,异常抛出等动作,文档上面介绍他主要解决的问题是项目中有时候依赖不存在的情况下来模拟一来,然而我却是因为速度问题用这个框架,造成这个现象的原因是因为我的项目原因,因为项目不大,逻辑不是很复杂,我在开发的时候往往能从dao、service到action一气呵成来做,不存在模拟依赖的问题,而只是想要解决测试的时候性能问题。当然我的情况是个例,不过我认为mock框架的作用就是这两个:
1.模拟依赖
2.解决单元测试的重复测试来提升性能。
Mockito的使用:我们以User类来测试。
1.把包加入到project中来:此处版本为1.9.5。
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.5</version> <scope>test</scope> </dependency>
2.POJO
public class User { private String id; private String name; private Integer age; // getter,setter }
3.UserDao,此处的接口是没有实现的,我们就是为了能模拟接口的实现,感觉上就像Spring为此接口注入了实现一样
public interface UserDao { User insertUser(User user); void deleteUser(User user); }
4.UserService
public interface UserService { User insertUser(User user); void deleteUser(User user); }
5.UserServiceImpl
public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl() {} public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } public User insertUser(User user) { return userDao.insertUser(user); } public void deleteUser(User user) { userDao.deleteUser(user); } }
6.测试
package com.dxz.mockito; import java.util.LinkedList; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; public class UserServiceTest { private UserService userService; @Test public void insertUserTest() { // pojo对象 User user = new User(); user.setId("123"); user.setName("Jay"); user.setAge(100); // mock一个userDao对象,本来是不存在的 UserDao userDao = Mockito.mock(UserDao.class); Mockito.when(userDao.insertUser(user)).thenReturn(user); User u = userDao.insertUser(user); System.out.println(u); // 这里模拟Spring给userService注入userDao userService = new UserServiceImpl(userDao); User us = userService.insertUser(user); System.out.println(us); // 测试结果ok Assert.assertEquals(us, user); // 在开发中有接口UserDao但是没有实现,因此UserService里面的userDao属性也是没有实现的, // 模拟Spring给UserService里面的userDao注入一个实现,实际上没有注入,而仅仅是mock了一个userDao // 在userService调用insertUser(User user)方法的时候就能模拟实现 // 当然这里仅仅mock了方法调用返回,还有mock异常,验证调用次数等 // 从整个来讲,这个框架是十分简单易用功能强大的,这里的简单是指使用简单,框架本身是相当复杂的 } }
web编程中,代码一般分3层,controller层,service层,DAO层,分别的测试方法为:
DAO层:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = com.dxz.common.dao.MySqlAutoConfiguration.class) @EnableAutoConfiguration @MapperScan("com.dxz.risk.mapper") //@ActiveProfiles("sonar") @Ignore public class RiskBacklistMapperTest { private static final Logger LOG = LoggerFactory.getLogger(RiskBacklistMapperTest.class); @Autowired RiskBacklistMapper riskBacklistMapper; RiskBacklist riskBacklist; @Before public void setUp() throws Exception { riskBacklist = new RiskBacklist(); riskBacklist.setMemberId("123"); riskBacklist.setMsisdn("13530030000"); riskBacklist.setUsername("duanxz"); riskBacklist.setState(1); riskBacklist.setPlateform(new Byte("1")); } @Test public void testInsert() { riskBacklistMapper.insert(riskBacklist); } // 事务自动回滚,默认是true。可以不写 @Test @Transactional @Rollback(true) public void testUpdateByPayId() { RiskBacklist riskBacklistTemp = riskBacklistMapper.queryByMsisdn("13530030927"); LOG.info("query from db={}", riskBacklist); if(riskBacklistTemp != null) { riskBacklistTemp.setUsername("duanxz2"); int result = riskBacklistMapper.updateByPrimaryKey(riskBacklistTemp); LOG.info("result={}", result); assertEquals(1, 1); } else { LOG.info("result={}", ""); } } }
service层:
@RunWith(SpringRunner.class) @SpringBootTest @ActiveProfiles("sonar") @Ignore public class BacklistCheckServiceImplMockTest { private static final Logger LOG = LoggerFactory.getLogger(BacklistCheckServiceImplMockTest.class); /** * 被注入mock对象的类,即被测试的类 */ @Autowired @InjectMocks private BacklistCheckService backlistCheckService; BacklistReqDTO reqDTO; /** * 需要mock的DAO */ @Mock private RiskBacklistMapper riskBacklistMapper; @Before public void setUp() throws Exception { reqDTO = new BacklistReqDTO(); reqDTO.setMsisdn("13530030000"); RiskBacklist riskBacklist = new RiskBacklist(); riskBacklist.setMsisdn("13530030000"); riskBacklist.setPlateform(1); riskBacklist.setState(2); riskBacklist.setUsername("duanxz"); Mockito.when(riskBacklistMapper.queryByMsisdn("13530030000")).thenReturn(riskBacklist); } @Test public void isBacklist() { boolean result = backlistCheckService.isBacklist(reqDTO); LOG.info("result={}", result); //断言对比业务结果 assertEquals(true, result); } }
controller层:
@RunWith(SpringRunner.class) @SpringBootTest @ActiveProfiles("sonar") @Ignore public class BacklistCheckControllerMockTest { /** * 模拟MVC对象,通过MockMvcBuilders.webAppContextSetup(this.wac).build()初始化。 */ private MockMvc mockMvc; /** * 被注入mock对象的类,即被测试的类 */ @InjectMocks private BacklistCheckController backlistCheckController; /** * 需要mock的DAO */ @Mock private BacklistCheckService backlistCheckService; private BacklistReqDTO reqDTO; @Before public void setUp() throws Exception { reqDTO = new BacklistReqDTO(); reqDTO.setMsisdn("13530030927"); mockMvc = MockMvcBuilders.standaloneSetup(backlistCheckController).build(); Mockito.when(backlistCheckService.isBacklist(reqDTO)).thenReturn(true); } @Test public void testIsBacklist() throws Exception { MvcResult result = mockMvc .perform(post("/isBacklist").contentType(MediaType.APPLICATION_JSON).content(GsonUtils.toJson(reqDTO))) .andExpect(status().isOk())// 模拟向testRest发送get请求 .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))// 预期返回值的媒体类型text/plain;charset=UTF-8 .andReturn();// 返回执行请求的结果 // 打印结果,给研发看的 System.out.println(result.getResponse().getContentAsString()); RespResult<Boolean> resultObj = GsonUtils.fromJson2Object(result.getResponse().getContentAsString(), RespResult.class); //断言对比返回码 assertEquals(RespSystemCode.SUCCESS.getCode(), resultObj.getRespCode()); //断言对比业务结果 assertTrue(resultObj.getData()); } }