任何开发人员都使用面向对象的编程语言并为实现的单元测试写作,都可以选择如何为课程提供依赖项。某些语言几乎没有选择,有些语言仅提供了通过构造函数参数的一种方式。
让我们以Java语言为例,在这种情况下,可以通过两种方式提供依赖项,并且在两种情况下,编写单元测试都可以不同。每个人都可以使用哪些优点和缺点。
所以,我们的任务看起来像:
- 必须通过相应实例分配类字段;
- 应该写单位测试。
这是我们要修改的初始代码段。代码段具有一个依赖性,要在运行时填充。
public class Service {
Dependency dependency;
public void action() {
dependency.action();
}
}
通过DI框架将依赖项分配给课堂字段
如果我们使用例如Spring
框架,则可以在这样的东西中更改以下示例。
@Component
public class Service {
@Autowired
private Dependency dependency;
public void action() {
dependency.action();
}
}
看起来很清晰,没有“多余的”。很少有东西困扰我。
第一件事 - Java反射用于分配依赖项。没什么大不了的,但是在背景中以某种方式为私有字段提供价值并不是很简单和干净 - 仅从课程中分配私有字段。
第二件事 - 班级的字段不是最终的。从技术上讲,这意味着可以在班级实例的生命周期中从类内部修改字段。换句话说,班级部分失去了不变性,这是不好的 - 状态的变化和行为也会改变。编写良好和干净的单元测试将很难。
好吧,到目前为止,它看起来还不错,但是让我们尝试为上述实现编写单元测试。
这是单位测试的任务:
- 将依赖项分配给类字段;
- 检查依赖关系的方法是否实际上是调用的。
当前实现不假设依赖关系直接分配给类字段,因此我们将使用DI将依赖项的实例分配给私人类字段。只能仅使用mockito
扩展或mockito + Spring testing
扩展程序来完成此操作。每种方式都有利弊,但结果将分配给所需字段,我们能够编写单元测试以验证我们的实现。
@ExtendWith(MockitoExtension.class)
class ServiceTest {
@Mock
private Dependency dependency;
@InjectMocks
private Service subject;
@Test
public void testAction() {
subject.action();
verify(dependency, times(1)).action();
}
}
我会强调的是,这是非常平稳的方法:初始化和分配是看不见的。唯一的一件事 - 我们需要一个调解人才能嘲笑并将实例分配给课堂字段。我们看不到并控制嘲笑和分配发生的方式和何时发生。这个事实已经使这种方法有些雾gy:看起来我们“看到”了一切,但并不清楚。雾中的形状可能很棘手;)
通过DI框架通过类构造函数将依赖项分配给类字段
让我们检查一下通过类构造函数提供依赖项的另一种方法。样本Java类可以以以下形式进行转换。
@Component
public class Service {
private final Dependency dependency;
public Service(Dependency dependency) {
this.dependency = dependency;
}
public void action() {
dependency.action();
}
}
所有依赖性都是通过构造函数明确提供的,类字段标记为final
,并且在类实例的整个生命周期中都是不变的。也许构造函数在类定义中会占用更多空间,因为我们将其用于分配依赖项。
让我们编写一个单元测试以实现此类实现,并遵循与通过DI直接到类字段的接线依赖关系相同的任务:
- 将依赖项分配给类字段;
- 检查依赖关系的方法是否实际调用。
class ServiceTest {
private final Dependency dependency = mock(Dependency.class);
private final Service subject = new Service(dependency);
@Test
public void testAction() {
subject.action();
verify(dependency, times(1)).action();
}
}
由于我们通过构造函数将依赖项传递给了一个类,因此我们可以在下面使用以下方法。测试实现不需要任何特殊的扩展即可运行测试:依赖关系和测试主题创建“手动”发生。这种实现没有隐形的动作,一切都由我们控制。实现以不同的方式看待,可以看到依赖性创建,也可以看到传递依赖关系,代码比以前的实现更长。肯定会更明显的是,这里会发生什么。
为什么final
在这里很重要?
应用于类字段的关键字final
表示不变性。如果类字段没有这样的关键字,则有可能修改类字段的值。这意味着在某些特定情况下,我们的班级在技术上的行为可能会有所不同,而且根本不好 - 这是一种产生生产问题而不是偏爱的方式。由于一类失去不变性,因此单位测试结果变得不可预测,可以描述为flaky
。因此,final
制造了一个对象immutable
,单位测试产生相同的结果,并且在生产中的应用表现与原本。
结论
单元测试的基本部分是简单性和稳定性:)简单性有助于简单地理解实施,
稳定性在多次调用中提供了相同的结果,并且都导致生产中的稳定工作。考虑到上述方法,我个人会选择第二种方法。就我而言,它看起来更简单,干净和直截了当。第二个在测试中没有“魔术” - 所有内容都在桌子上。