Mockito
Dan North, the originator of Behavior-Driven Development wrote this back in 2008:
“We decided during the main conference that we should use JUnit 4 and Mockito because we think they are the future of TDD and mocking in Java”
说明
本文章中的示例使用的开发环境为JDK1.8!
Mockito的版本信息如下
1 |
|
文章中的示例部分来自Mockito Dzone Reference Card
Warning:文章中的示例只是为了展示Mockito的语法和特性,在实际的测试编码中,我们可能不会使用示例的用法(测试逻辑)!
官方原文:
Warning: Note that the examples in this Refcardwere created to demonstrate behaviors of Mockito in a specific context. Of course, when writing the test for your codebase, there is no need to ensure that mocks are stubbed correctly.
鄙人在Github上修改了部分示例,并且写了一些其他的示例(实际测试代码中用法)!传送门
简介
Mockito是一个模拟测试框架。在一个被测试的对象(功能)A中,它通常需要与其他的对象(功能)B进行一些交互,我们把A称作被测试对象(tested object),把B称作协作者(collaborators)。那么在测试环境中,这些协作者都需要被创建,以便被测试对象可以使用它们。为了使测试代码简化以及满足上下文执行环境,我们通常使用测试替身(test double)来代替这些协作者,测试替身看上去和原始的协作者一样,但是却不依赖其他对象,而且可以执行预期行为,记录他们的交互行为(interactions,可以理解成方法调用)
核心元素
TDD : Test-Driven Development
BDD : Behavior-Driven Development
Mock : 模拟对象,可以理解成Mockito框架帮我们自动生成的数据。通过mock产生的对象有以下的能力:1.可以通过编程产生预期行为;2.在对象的生命周期内可以校验它的交互行为(方法调用);
原文参考:Mock - an object with the ability to a) have a programmed expected behavior, and b) verify the interactions occurring in its lifetime (this object is usually created with the help of mocking framework)
Stub : 存根?桩?可以理解成通过硬编码的方式预期定义行为(方法),将会产生特定的结果,屏蔽原本行为(方法)的实现。
1 |
|
原文参考:Stub - an object with hardcoded behavior suitable for a given test (or a group of tests)
Spy : mock的代理对象,当方法被stub的时候,调用stub的定义行为(方法实现被忽略);当方法没有被stub时,调用真实对象的行为(调用真实方法的逻辑实现)。此对象一般不由mock生成,而是自己编码new,再通过spy包装成mock
原文参考:Spy - a mock created as a proxy to an existing real object; some methods can be stubbed, while the un-stubbed ones are forwarded to the covered object
Test Doubles:测试替身,包括5个类型stub,mock,spy,fake,dummy。
地心历险(待研究)
揭示底层的原理
实战
1. 概述
编写测试用例,一般分为三个阶段:
Section name | Responsibility |
---|---|
arrange (given) | SUT and mocks initialization and configuration |
act (when) | An operation which is a subject to testing;preferably only one operation on an SUT |
assert (then) | The assertion and verification phase |
arrange-act-assert模式对应的语法为:when(~).thenXXX(~)
given-when-then模式(对应BDD形式)对应的语法为:given(~).willXXX(~)
官方大部分DEMO使用given-when-then模式,而且也推荐使用这种模式
官方原文: given-when-then comments make intentions of tests clearer.
2. 简例
1 |
|
1 |
|
3. 参数匹配
(Argument Matching)
Mockito默认使用equals()来匹配参数。通常我们需要一个宽范围的参数匹配,Mockito的org.mockito.Matchers类中提供了一套内置的匹配器(Matcher)。
代码示例:
1 |
|
如果有一个参数使用了匹配器,则所有的参数必须都使用匹配器,否则将会抛出异常!示例代码中注释掉的部分会抛出异常!
自定义的参数匹配器需要继承org.mockito.ArgumentMatcher抽象类,并且实现matches方法。然后调用argThat(org.hamcrest.Matcher
代码示例:
1 |
|
4. 调用Void方法
Stubbing Void Methods
void Methods 应该使用 willXXX…given 或者 doXXX…when.来进行stubbing
代码示例:
1 |
|
5. 自定义返回器
Stubbing With a Custom Answer
极少的情况会使用自己的处理逻辑来指定预期行为的结果(也就是given…willReturn(Custom Answer)中的Answer).Mockito还是提供了org.mockito.stubbing.Answer<Object>
的接口来实现这个功能,你只需实现该接口中的answer方法。
Warning:如果需要使用Custom Answer,可能预示着被测代码太复杂,需要重构!
官方原文:
Warning: The need to use a custom answer may indicate that tested code is too complicated and should be re-factored.
6. 检验行为
(Verifying Behavior)
在一个mock对象的生命周期内,它会记住本身所有的操作。在被测系统(SUT)中,这些操作应该可以被轻易校验。Mockito中可以使用Mockito.verify(T mock)这个基础形式来进行校验
代码示例:
1 |
|
Mockito提供了一些有意义的校验模式,你也可以创建自定义校验模式
1 |
|
7. 校验调用顺序
(Verifying Call Order)
Mockito可以让你校验调用的顺序,你可以创建一组mocks,然后在组内校验所有的调用顺序。
代码示例:
1 |
|
8. 真实调用
Spying on Real Objects
在Mockito中,你可以使用真实的对象来代替mock,从而使部分方法可以stubbed。通常我们不需要使用spy一个真实的对象,这可能是代码异味(code smell)的信号。但在一些特殊的情况下(比如使用了遗留代码,或者IOC容器),纯mock对象可能不能进行测试。
代码示例:
1 |
|
当使用spy时,必须使用willXXX…given/ doXXX…的形式来stubbing,它可以在stub期间防止真实方法方法被调用
Warning:当使用spy时,Mockito创建了真实对象的一份拷贝,因此所有的交互行为应该被传递到被创建的spy对象上
官方原文:
Warning: While spying, Mockito creates a copy of a real object, and therefore all interactions should be passed using the created spy.
9. 注解
Annotations
Mockito提供三个注解来简化用静态方法创建mock对象的工作 – @Mock, @Spy, @Captor;注解@InjectMocks可以简化mock和spy对象的注入,它可以通过构造器注入,setter方法注入,field赋值注入。
使用注解的功能要调用MockitoAnnotations.initMocks(testClass)(通常在@Before的方法中调用),或者使用MockitoJUnit4Runner来作为junit runner
代码示例:
1 |
|
Annotation | Responsibility |
---|---|
@Mock | Creates a mock of a given type |
@Spy | Creates a spy of a given object |
@Captor | Creates an argument captor of a given type |
@InjectMocks | Creates an object of a given type and injects mocks and spies existing in a test |
10. 修改默认返回值
(Changing the Mock Default Return Value)
Mockito使我们可以选择生成mock对象的默认值
Default Answer | Description |
---|---|
RETURNS_DEFAULTS | Returns a default “empty” value (e.g., null, 0, false, empty collection) - used by default |
RETURNS_SMART_NULLS | Creates a spy of a given object |
RETURNS_MOCKS | Returns a default “empty” value, but a mock instead of null |
RETURNS_DEEP_STUBS | Allows for a simple deep stubbing (e.g., Given(ourMock.getObject().getValue()).willReturn(s)) |
CALLS_REAL_METHODS | Call a real method of spied object |
mock(clazz, Mockito.${Default Answer})
Tips
依照本人的理解,在真正编写测试代码时,我们应该分清当前单元测试的目的,以及它所依赖的方法调用。然后在given阶段,mock各种依赖的对象,并且stub各种依赖对象的预期行为;when阶段进行测试目的的执行,也就是测试对象真实的调用行为;then阶段进行verify和assert。
局限性
引用官方说明:
Limitations
- Mockito has a few limitations worth remembering. They are generally technical restrictions, but Mockito authors believe using hacks to work around them would encourage people to write poorly testable code. Mockito cannot :
- mock final classes
- mock enums
- mock final methods
- mock static methods
- mock private methods
- mock hashCode() and equals()
参考资料
鄙人编写的代码示例(部分来自官网示例,适量修改,并添加额外的示例)
Mockito官网
Mockito On Github
Mockito Dzone Reference Card
Introduction to mockito