我们都会为我们的代码编写测试,不是吗?毫无疑问,我知道这个问题的答案可能会从 “当然,但你知道怎样才能避免写测试吗?” 到 “必须的!我爱测试”都有。接下来我会给你几个小建议,它们可以让你编写测试变得更容易。那会帮助你减少脆弱的测试,并保证应用程序更加健壮。
与此同时,如果你的答案是 “不,我不编写测试。”,那么我希望这些简单但有效的技术可以让你了解编写测试带来的好处。你也会看到,编写一个复杂、没有价值的测试集(test suit)并没有你认为的那么难。
如何编写测试、有哪些用于管理测试集合的最佳实践这些主题并不新鲜。我们在过去已经就这个问题的某些方面讨论了很多次。从 “在构建过程中使用集成测试的正确方式” 到谈论“在单元测试中恰当地模拟环境”, 再到“ 代码覆盖率以及如何找到哪些是你真正需要测试的代码”。
但是,今天我想和你谈论一系列小建议,这些建议可以帮助你在头脑中理清测试自下而上是如何运作的。从如何构造一个简单的单元测试到对 mock(模拟) 和 spy(监视) 以及复制粘贴测试代码更高层次的理解。那我们开始吧。
AAArrr,像海盗一样说话?
和大部分软件开发一样,模式通常都是一个不错的开始。无论是想要通过工厂来创建对象,或者希望将web应用程序中的关注点分散到Model、View和Controller中,在它们背后通常都会有一个模式,帮助你理解正在发生什么并解决困难。 那么,一个典型的测试看上去应该是怎么样的?
当我们编写测试时,其中一个最有用但却极其简单的模式是计划-执行-断言(Arrange-Act-Assert),简称AAA。
这个模式的前提是所有测试都应该遵循默认布局。测试系统所必需的全部条件和输入都应该在测试方法开始的时候被设置(Arrange)。在计划好所有前置条件后,我们通过触发一个方法或者检查系统的某些状态的方式,在测试系统上运行(Act)。最后,我们需要断言(Assert)测试系统是否已经生成了期望的结果。
让我们来看一个Java JUnit测试的示例,它展示了这种模式:
@Test
public void testAddition() {
// Arrange
Calculator calculator = new Calculator();
// Act
int result = calculator.add(1, 2);
// Assert
assertEquals("Calculator.add returns invalid result", 3, result);
}
看看代码流多么精准!计划-执行-断言模式可以让你快速理解测试的功能。偏离了这个模式后会很容易写出非常糟糕的代码。
牢记迪米特法则
迪米特法则在软件上面应用了最小知识原则,减小了单元的耦合——这一直是在开发软件的设计目标。
迪米特法则可以表述为一系列的规则:
在方法中,一个类的实例可以调用该类的其它方法;
在方法中,实例可以查询自己的数据,但不能查询数据的数据(译者注:即实例的数据比较复杂时,不能进行嵌套查询);
当方法接收参数时,可以调用参数的第一级方法;
当方法创建了一些局部变量的实例后,这个类的实例可以调用这些局部变量的方法;
不要调用全局对象的方法。
那么,就测试而言,这些意味着什么呢?好吧,由于迪米特法则减少了应用程序各部分之间的耦合,这意味着测试应用程序中的各个部分变得更加容易。为了要查看该法则如何为测试提供帮助,我们来看一个定义非常糟糕的类,它违背了迪米特法则:
考虑下面这个我们要测试的类:
public class Foo() {
public Bar doSomething(Baz aParameter) {
Bar bar = null;
if (aParameter.getValue().isValid()) {
aParameter.getThing().increment();
bar = BarManager.getBar(new Thing());
}
return bar;
}
}