SpockFramework教程之Giuce注入

摘要:本文针对 SpockFramework 如何测试使用 Guice 注入的服务进行讲解。

概述

当前 Java 的开发都离不开依赖注入,目前较为流行的轻量级注入框架是 Guice。当使用依赖注入后,进行单元测试时就会面对如何解决注入的依赖的问题。

在进行单元测试的时候,我们往往需要尽可能的将测试单元化,也就是说所有和待测试的内容无关的都要进行模拟,有点类似于做试验中的单一变量原则,为了验证某一个条件对结果的影响,必须在保证其他条件都不变的情况下调整带测变量。而测试也应该尽可能的将待测目标所依赖的内容隔离开,通过模拟这些依赖的输出达到尽可能的测试待测目标的所有可能出现的情况。

这样就要求我们能够在测试中对注入的依赖进行模拟。因此我们总结一下需要在测试中解决的问题

  1. 在测试中实现依赖注入
  2. 对注入的实现进行 Mock 或者 Spy
  3. 在测试中 Mock 或者 Spy 注入的对象的某些行为

本文内容均参考 官方注入文档 以及 Github相应的案例

注入的依赖的外部模拟

我们知道 Guice 的注入方式有很多种,为了能够实现对注入的 Mock, 我们不能使用原有的注入方法。而需要重新注入。

因为我们将要在 Guice 注入的时候进行 Mock,所以我们首先要解决的问题是,如果在 spec 之外的地方进行 MockSpockframework 为我们提供了一个方法 DetachedMockFactory。该工厂方法能够实现外部 Mock。我们通过其 JavaDoc 能够大概了解其用法

This factory allows the creations of mocks outside of a Specification,
e.g., in a Spring configuration.

In order to be usable those Mocks must be manually attached to the
Specification using MockUtil.attachMock(Object, Specification) and
detached afterwards MockUtil.detachMock(Object).

Maven: org.spockframework:spock-core:1.1-groovy-2.4

1
2
3
private DetachedMockFactory factory = new DetachedMockFactory();
factory.Spy(YOUR_OBJECT.class)
factory.Mock(YOUR_OBJECT.class)

通过上述代码,我们就能在任意位置 Mock 需要测试的类了。

使用 Guice Module

在使用 Guice 的时候,我们一般都会使用一个 Module 来统一进行注入,在 Spockframework 中,我仍然使用这种方法,而且 Spockframework 提供一个注解来应用 GuiceModule,该注解为 UserModules

1
2
3
4
5
6
7
8
9
10
@UseModules(value = [MockBinderModule])
class TestSpec extends Specification {
//....
}

public class MockBinderModule extends AbstractModule {
//...bind injection

//我们在这里需要把我们上面所说的 Mock 的实现注入到接口中。
}

我们在 MockBinderModule 把我们上面所说的 Mock 的实现注入到接口中。比如我们有接口 TestService ,而实现是 TestServiceImpl1,我们需要 Mock 实现,那么我就进行如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@UseModules(value = [MockBinderModule])
class TestSpec extends Specification {

@Inject
TestService testService
//....
def setup() {
mockUtil.attachMock(testService, this)
}

def cleanup() {
mockUtil.detachMock(testService)
}
}

public class MockBinderModule extends AbstractModule {
//...bind injection
private DetachedMockFactory factory = new DetachedMockFactory();

@Override
protected void configure() {
bind(TestService.class).toInstance(factory.Spy(TestServiceImpl1.class));
}

}

通过 DetachedMockFactory 的文档,我们知道,虽然我们在外部进行了 Mock,但是实际上我们通过注入使用其实例的时候,并没有真正加载 Mock 的实例,我们需要手动加载 those Mocks must be manually attached to the Specification using MockUtil.attachMock,然后手动卸载。

Spy 待测对象的注入

通常我们会 Mock 待测对象的依赖,然后对于待测对象,如果希望能够 在测试某些方法的时候,Mock 其他方法,我们也可以使用 Spy 监控待测对象,当然待测对象可以注入真实的实例。

私有函数的 Mock

有时候我们有需求在测试某一个 Public 方法的时候,不希望收到 某个 Private 方法影响,这时候最好就是将其 Mock 起来,但是实际上这是做不到。很多然讨论过这个话题,可以参考:

https://stackoverflow.com/questions/34571/how-do-i-test-a-private-function-or-a-class-that-has-private-methods-fields-or?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa

https://zeroturnaround.com/rebellabs/how-to-mock-up-your-unit-test-environment-to-create-alternate-realities/

这里个人采用了如下方法:

  1. 尽量不 Mock 私有方法,私有的方法在共有方法一并测试
  2. 如果有些方法必须要测而且要 Mock,那么该方法加入标签 /* private -> testing */