单元测试的目的是在不涉及依赖的情况下测试代码(隔离)。一个设计良好的系统需要遵循 SOLID 原则。
- (S) Single responsibility principle 单一职责
- (O) Open/closed principle 开闭原则,对修改关闭,对扩展开放
- (L) Liskov substitution principle 里氏替换原则,子类和父类表现一致
- (I) Interface segregation principle 接口隔离原则,不要依赖不要使用的方法,或者在设计时尽量将大接口拆开
- (D) Dependency inversion 依赖反转,将类和其他类隔离开,尽量依赖抽象,而不依赖具体实现,比如当修改了外部外部依赖具体实现,而不需要大规模的修改原始代码
Target and challenge of unit testing
Unit test 目标是针对一个模块或者一段代码隔离测试,应该消除其他类,或者系统依赖带来的副作用。
测试替身(Test Doubles)用来消除这类副作用,test Doubles 可以分为:
- dummy object 传入不使用
- fake object 有基本实现,但是非常简单,比如内存数据库
- stub class 带着特殊目的的接口或者类的部分实现
- mock object 接口或者类的虚假实现,可以用来定义固定的输出,mock object 在测试中用来执行特定的行为
通常情况下可以通过手工代码来 mock objects 或者使用 mock framework 来模拟类的行为。Mockito 是一个非常流行的 Mock framework,使用 Mockito 的三段论:
- Mock 外部依赖,插入 mock 代码
- 执行 unit test
- 验证输出
Maven
在 http://search.maven.org 中搜索 a:"mockito-core"
或者 g:"org.mockito"
比如说增加:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
mockito-all
mockito-all 是一个包含了所有依赖 (hamcrest 和 objenesis) 的单一 jar 包。
mockito-core
mockito-core 不包含 hamcrest 和 objenesis 依赖,可以自己来控制依赖的版本。
启用注解
第一种方法,使用 JUnit 的 @RunWith
@RunWith(MockitoJUnitRunner.class)
public class MockitoAnnotationTest {
...
}
或者用代码启用
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
@Mock 注解
最常用的注解就是 @Mock
注解,使用 @Mock
注解来创建和插入 mocked 实例,这样就省去了手动调用 Mockito.mock()
方法。
@Spy 注解
@Spy
注解可以 mock 真实对象的方法,让真实对象方法被调用时,就像 mock object 一样可以被配置。
@Captor 注解
参数捕获器,用于捕获 mock 方法参数,进行验证使用
@InjectMocks 注解
@InjectMocks
注解会自动将 mock fields 注入到被测试的 object 中。
需要注意的是,在 JUnit 4 中必须使用 @RunWith(MockitoJUnitRunner.class)
或 MockitoAnnotations.initMocks(this)
来初始化 mocks 并注入。
在 JUnit 5 中必须使用 @ExtendWith(MockitoExtension.class)
@RunWith(MockitoJUnitRunner.class) // JUnit 4
// @ExtendWith(MockitoExtension.class) for JUnit 5
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
// tests...
}
when thenReturn
通过 when().thenReturn()
方法链可以用来指定一个方法调用预先定义好的返回值。这个方法也可以指定抛出一个异常
Properties properties = mock(Properties.class);
when(properties.get(”Anddroid”)).thenThrow(new IllegalArgumentException(...));
try {
properties.get(”Anddroid”);
fail(”Anddroid is misspelled”);
} catch (IllegalArgumentException ex) {
// good!
}
thenReturn vs thenAnswer 区别
当 mock 方法是知道确定的返回值,那么可以使用 thenReturn
或者 doReturn
,方法会 mock 一个确定的返回值。
thenReturn(T value) Sets a return value to be returned when the method is called.
Answer
当需要根据不同条件来 mock 方法并且返回不同返回值时需要 Answer
,比如需要根据方法传入参数来返回对应的返回值的情况。
Mock 静态方法
Mockito 暂时还不支持 Mock 静态方法,所以需要借助 jmockit 完成静态方法的 Mock:
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.30</version>
<scope>test</scope>
</dependency>
new MockUp<StaticClass>() {
@mockit.Mock
public boolean isXXX() {
return true;
}
};
Other
《Mockito Cookbook》参考代码:https://github.com/marcingrzejszczak/mockito-cookbook