这是大牛的网址01 02
mockito的官方文档:
关于Junit测试业务逻辑层中出现的【方法的输入输出没有规范、测试高度依赖spring boot上下文环境、测试用例不完整等】这些问题,我们使用更完整的测试方法来解决。
针对最近遇到的问题:在SpringBoot框架下,如何脱离Spring的环境进行service层的单元测试,同时面临着【方法的输入输出没有规范、测试高度依赖spring boot上下文环境、测试用例不完整等】这些问题,查了很多资料之后,发现mockito可以很好的解决我当前遇到的问题。因为在这个过程中,查询的资料由于使用的mockito的版本不一致,还有写代码使用的框架不一致,导致在学习和实践测试的过程中走了很多弯路。现在总结如下,提供大家使用,少走弯路。 当前我的代码框架是:SpringBoot下,采用SpringMVC三层架构模式,使用SpringDataJPA处理简化DAO层的编写,语言为kotlin。
单元测试的思路是在不涉及依赖关系的情况下测试代码(隔离性),所以测试代码与其他类或者系统的关系应该尽量被消除。一个可行的消除方法是替换掉依赖类(测试替换),也就是说我们可以使用替身来替换掉真正的依赖对象。 使用Mockito可以明显的简化对外部依赖的测试类的开发。
维基百科对Mock object定义如下:
In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. Mock测试用虚拟的对象来代替真实对象来完成测试工作。为什么要用虚拟对象来代替真实对象?一是因为由于开发分工的问题,导致测试时真实的对象并不存在,二是因为真实对象的行为不可预知,三是可能真实对象难以创建,四是由于真实对象的响应可能很慢。
通常的单元测试仅仅测试方法的结果,Mock测试在此之上能够测试方法的行为,例如某个方法是否被调用或者某方法被调用的次数等。
Mockito是一个用于java程序的Mock测试框架,相对于easymock等Mock框架,采用Mockito框架的测试代码更加简洁,可读性更强。在pom文件中添加依赖既可使用。
需要在 Maven 声明依赖,你可以在 http://search.maven.org 网站中搜索 g:”org.mockito”, a:”mockito-core” 来得到具体的声明方式。 下面是2.0.2版本的mockito:
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>2.0.2-beta</version> <scope>test</scope> </dependency>目前为止,有两种方法可以初始化 fields: (1)Mockito 现在提供一个 JUnit rule。 使用 Mockito 提供的注解比如 @Mock, @Spy, @InjectMocks 等等。 (2)用 @RunWith(@MockitoJUnitRunner.class) 标注 JUnit 测试类 在 @Before 之前调用 MockitoAnnotations.initMocks(Object) 现在你可以选择使用一个 rule:
@RunWith(@MockitoJUnitRunner.class) public class TheTest { @Rule public MockitoRule mockito = MockitoJUnit.rule(); // ... }@mock为一个interface提供一个虚拟的实现。 @InjectMocks将本test类中的mock(或@mock)注入到被标注的对象中去,也就是说被标注的对象中需要使用标注了mock(或@mock)的对象。 mockito遇到使用注解的字段会调用MockitoAnnotations.initMocks(this) 来初始化该 mock 对象。另外也可以通过使用@RunWith(MockitoJUnitRunner.class)来达到相同的效果。 代码见3.4.3
使用测试桩stub来定义在service的实现代码中使用到的Repository代码的设定返回值: 3.4.1 DAO层
interface ProjectRepository : JpaRepository<Project, tag_t> { fun findByProjectId(projectId: String): Project } //JpaRepository是SpringDataJPA提供的一个类,里面有自定义的方法,我们集成之后直接调用即可,这个不重要,可以直接跳过不要看 @NoRepositoryBean public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> { List<T> findAll(); List<T> findAll(Sort var1); List<T> findAllById(Iterable<ID> var1); <S extends T> List<S> saveAll(Iterable<S> var1); void flush(); <S extends T> S saveAndFlush(S var1); void deleteInBatch(Iterable<T> var1); void deleteAllInBatch(); T getOne(ID var1); <S extends T> List<S> findAll(Example<S> var1); <S extends T> List<S> findAll(Example<S> var1, Sort var2); }3.4.2 service层的实现
//service接口 interface AaaService{ fun create(a:Aaa):Aaa } //service层的实现 //注入Aaa类的DAO层 @resource private lateinit var aRepository:AaaRepository class AaaServiceImpl:Aaa{ override fun create(a:Aaa):Aaa{ return aRepository.save(a) } }3.4.3 测试类代码:
@RunWith(MockitoJUnitRunner::class) class AaaServiceImplTest { //用于定义被Mock的组件 @Mock private lateinit var aaaRepository: AaaRepository //mock一个要测试的类对象,同时@Mock注解的会被依赖注入到@InjectMocks注解的类对象中 @InjectMocks private lateinit var aaaService: ProjectServiceImpl private lateinit var aaa:Aaa @Before fun setUp() { //用于初始化@Mock注解修饰的组件 MockitoAnnotations.initMocks(this) //定义类对象 aaa = Aaa() } @Test fun create() { //这是自定义的一个测试桩stub,定义在service层关于dao层语句的返回值定义,使得service代码的测试脱离开dao层。 Mockito.`when`(aaaRepository.save(aaa)).thenReturn(aaa) //运行方法 val result = aaaService.create(aaa) //这里可以多验证几种结果 assertEquals(aaa, result) assertEquals(projectInfo.projectId, result.projectId) //判断某个方法是否被调用(是否发生交互) Mockito.verify(aaaRepository).save(aaa) } }至此,mockito的测试步骤就完了,更多的小的杂乱的知识点,在上面给出的网址中。下面我们来看看在测试类中对测试更全面的介绍。
下面结合Mockito工具的使用来谈谈Mock中涉及一些重要概念。
Stub:
A stub is a controllable replacement for an existing dependency (or collaborator) in the system. By using a stub, you can test your code without dealing with the dependency directly. 我的理解,Stub用于绕开实际依赖或者某些实际方法的执行。可以用Stub去伪造一个方法来绕过数据库访问方法的执行。在Mockito中提供了when语句来实现Stub。例如when(a.func()).thenReturn(1)伪造执行a.func(),当调用a.func()时返回值定义为1。
Behavior verification: Mock测试区别于一般单元测试方法的重要特点是Mock测试可以进行行为验证。在mockito中采用verify(a, times(n)).func()来验证对象a的func()方法是否被调用n次。
Wrap a real object: Mock对象只能调用Stub方法,而不能调用其真实方法,否则会抛出空指针异常。而Mockito提供了spy机制可以用于监控一个真实对象,此时可以调用该对象的真实方法。在Mockito中可以采用@Spy标签或者调用Mockito.spy(T object)方法。
Mock中常用注解有:
@Mock:用于标识mock对象。@InjectMocks:将用@Mock标注的mock对象,注入到被某个被该注解标注的测试的对象中。@Spy:用来标注某个被@Mockito标注的真实对象。Mockito当然也有一定的限制。而下面三种数据类型则不能够被测试
final classes anonymous classes primitive types
doReturn().when()和when().thenReturn()的区别。后者会调用对象的真实api,而前者遇到函数调用直接返回doReturn中设置的值。
更详细的内容最好是参考:mockito的官方文档