相信大家在不少书籍、文章中都看过大佬们强调单元测试如何如何重要,一写大公司也会强调单元测试的重要性。但是如何完成单元测试却鲜有文章提及,这对一写萌新开发就相当不友好了。
这篇文章我就使用Mockito和Junit这两个工具,给大家分享一下。如果有大佬发现哪里有谬误,欢迎指正(#^.^#)。
为什么要使用mockito
上图目录结构,想必大家都不陌生。如果没有单元测试。各位小伙伴,如何完成功能的验证呢?
有的小伙伴可能会想着把项目运行起来,调用Controller 对应的接口,通过debug来查看对应功能是否符合预期。
我只想说并不是所有时候,你都能在本机启动整个项目的。而且,就算你本地运行的起来这个项目,当一个功能同时由多个人共同开发时,功能不符合预期,到底是我们负责的部分的问题,还是其他伙伴负责的功能有问题呢?你打算阅读其他伙伴的功能吗?此外,当功能升级需要向下兼容时,这种方式也无法保证。
好的,现在你也意识到了,我应该只测试我编辑的内容就足够了。但是我的Service有依赖其他bean呀。没有spring帮我们注入,难道我们手动实例化吗?好的,但是依赖的Bean又依赖了其他bean, 甚至是个mapper怎么办?
package org.example.service.impl;
import lombok.Setter;
import org.example.business.DemoBusinessOne;
import org.example.business.DemoBusinessTwo;
import org.example.pojo.bo.DemoBO;
import org.example.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class DemoServiceImpl implements DemoService {
@Setter(onMethod_ = @Autowired)
private DemoBusinessOne demoBusinessOne;
@Setter(onMethod_ = @Autowired)
private DemoBusinessTwo demoBusinessTwo;
@Override
public String getDemo() {
DemoBO demoBO = demoBusinessOne.doBusiness1();
DemoBO demoBO1 = demoBusinessTwo.doBusiness2();
return Optional.ofNullable(demoBO).map(DemoBO::getBusinessMessage).orElse("")
+ Optional.ofNullable(demoBO1).map(DemoBO::getBusinessMessage).orElse("");
}
}
这个时候mockito,就发挥作用了。先看一个简单的示例:
package org.example.service.impl;
import org.example.business.DemoBusinessOne;
import org.example.business.DemoBusinessTwo;
import org.example.pojo.bo.DemoBO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.jupiter.api.Assertions.*;
// 开启 Mockito
@RunWith(MockitoJUnitRunner.class)
public class DemoServiceImplTest {
@InjectMocks // 需要mockito 帮我们注入依赖的对象,也就是我们要测试的对象
private DemoServiceImpl demoService;
@Mock //依赖对象 通常我们不关心他的具体实现逻辑
private DemoBusinessOne demoBusinessOne;
@Mock
private DemoBusinessTwo demoBusinessTwo;
@Test
public void getDemoTest() {
// 当调用mock对象的方法时,我们让他默认返回一个对象
Mockito.when(demoBusinessOne.doBusiness1()).thenReturn(new DemoBO());
Mockito.when(demoBusinessTwo.doBusiness2()).thenReturn(new DemoBO());
assertNotNull(demoService.getDemo());
}
}
导入依赖
这里我使用的junit,版本是 4.12,mockito版本为3.9.0。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
mockito使用案例
关于spy和mock
如果你是第一次接触mockito,很有可能被一个概念搞晕(起码我刚接触的时候是这样的)——什么是spy什么是mock。简单的理解就是我们依赖的那个对象,如刚开始的那个例子中的demoBusinessOne、demoBusinessTwo,这俩的实例是由谁完成的。
如果我没有创建对象,可以使用mock,此时mockito框架会通过帮我们创建一个对象,我叫他mock对象。这个mock对象中的方法被调用时,默认返回空,当然我们可以自定义他调用时候的返回值。就像最开始的返回值。甚至可以自定义一段执行逻辑。如下:
@Test
public void customInstanceTest() {
Mockito.when(demoBusinessOne.doBusiness1()).thenReturn(new DemoBO());
Mockito.when(demoBusinessTwo.doBusiness2()).then((invocation) -> {
// 如果要使用参数可以使用 invocation.getArgument(0) 获取
DemoBO demoBO = new DemoBO();
demoBO.setBusinessMessage("Custom Message");
return demoBO;
});
assertEquals("Custom Message", demoService.getDemo());
}
如果我已经有一个实例对象,需要用把这个实例加入到mockito框架中时,可以使用spy。
这么做,可以在调用实例对象的某个方法时调用实例真实的方法,如下面代码片段所示。
@InjectMocks // 需要mockito 帮我们注入依赖的对象,也就是我们要测试的对象
private DemoServiceImpl demoService;
// @Mock //依赖对象 通常我们不关心他的具体实现逻辑
@Spy
private DemoBusinessOne demoBusinessOne = new DemoBusinessTwoImpl();
@Mock
private DemoBusinessTwo demoBusinessTwo;
@Test
public void spyTest(){
Mockito.when(demoBusinessOne.doBusiness1()).thenCallRealMethod();
Mockito.when(demoBusinessTwo.doBusiness2()).thenReturn(new DemoBO());
assertEquals("realBusiness",demoService.getDemo());
}
分享一个我常用spy的场景,在测试一写业务代码时,总是很烦造一写模拟数据,利用mybatis 加上spy 就可以轻松的使用开发环境数据库中的数据了。
需要特别注意的一个点,使用spy()创建的对象时,如果想要设置调用方法时候返回的之,不要使用thenReturn。如下面代码
List list = new LinkedList();
List spy = spy(list);
//会调用真正的方法,然后抛出数组越界异常
when(spy.get(0)).thenReturn("foo");
//应该这样使用
doReturn("foo").when(spy).get(0);
Mockito 常用方法
这里仅介绍一些常用方法,具体使用还是得看官方文档
- mock()
//手动创建一个mock对象 可以使用 @Mock 注解替代
DemoBusinessOne demoBusinessOne = Mockito.mock(DemoBusinessOne.class);
- spy()
// 窃取一个实际对象作为mock对象,可以使用 @Spy 注解替代
DemoBusinessOne demoBusinessOne = Mockito.spy(new DemoBusinessTwoImpl());
- 以下三组配合使用
定义满足什么条件:when()
定义满足条件后的行为:then()、thenReturn()、thenAnswer()、thenThrow()、thenCallRealMethod()
定义满足条件后的行为:do()、doReturn()、doAnswer()、doThrow()、doCallRealMethod()
- when 扩展
对于有参数的方法,我们希望根据参数进行不同的处理时,可以采用下面的方式:
@Test
public void argTest(){
DemoBusinessThree demoBusinessThree = Mockito.mock(DemoBusinessThree.class);
Mockito.when(demoBusinessThree.hasArgBusiness("test1")).thenReturn(new DemoBO());
Mockito.when(demoBusinessThree.hasArgBusiness("test2")).thenReturn(null);
assertNotNull(demoBusinessThree.hasArgBusiness("test1"));
assertNull(demoBusinessThree.hasArgBusiness("test2"));
}
此外mockito还帮我们提供了更多参数匹配规则。详情请查看ArgumentMatchers类下的方法,很简单这里不做赘述。
- verify()
verify用于验证mock对象是否完成某个行为。
- verify 行为定义(常用)
times(i) 是否执行了i次
atLeastOnce() 至少执行了一次
atLeast(i) 至少执行了i次
atMostOnce() 最多执行了一次
atMost(i) 最多执行了i次
timeout(i) 验证异步方法,如果异步方法在i内没有执行会验证失败。
after(i) 在i时间之后验证方法时候执行
结语
《代码整洁之道》中有一句话我觉着很有道理:糟糕的单元测试不如没有测试。一个好的单元测试最起码应该满足三点:自动化、独立、可重复。当然还有一些其他大佬提出了其他原则比如FIRST,感兴趣的伙伴可以自己了解一下。
通过Junit的Assert工具和Mockito的verify()的api可以,帮助我们完成自动化。通过Mock 和spy 可以帮助我们轻松的完成独立。
以上就是我分享的内容,当然只是一些基础的示例,想要写出优秀的单元测试,能够顺应系统的不断迭代,在我看来是一件相当需要功底的事情,如果有机会再和大家分享吧。