测试替身就是通过写一些代码来替代所测试代码会接触到的其他部分代码。首先需要回答的问题是我们为什么需要测试替身。作者列举了以下几点:
- 隔离要测试的代码
- 加快测试的执行
- 让执行具有确定性
- 模拟特殊的条件
- 访问隐藏的信息
下面我们分别来看这几点。
隔离要测试的代码:这意味着我们要分清楚测试的代码和与测试代码所交互的代码。原始代码写得不好或者一开始就不是面向测试的话,其实很难隔离,现实中这会导致测试代码往往会由于依赖代码的错误,而不是自己代码的错误而通不过。
加快测试的执行:这个比较好理解,通过替身我们实际上不需要去实际执行所依赖的代码的逻辑(其中可能包含复杂的算法,访问其他网络服务,费时的IO访问等等),既然没有实际执行这些代码,测试自然就变快了。
让执行具有确定性:最典型的例子是依赖当前系统时间的代码。需要把取时间这部分代码做成替身。
模拟特殊的条件:比如断网的条件,或者某个外部服务意外报错的情况。这里作者搞笑了一把,说如果通过代码远程遥控乐高机器人把现实中的网线拔了当然是更牛逼。
访问隐藏的信息:一般说来,如果测试代码需要访问类内部实现的话,多半是在类设计上了问题。对java来说意味着要访问一些私有变量之类。但是如果在测试中真发现需要为类添加仅仅为了测试的方法的时候,就该考虑用一个测试替身作为子类继承原来的类,把这些方法加到测试替身这个子类上。
下面谈谈测试替身的类型, 作者分为了四种:
- Test Stub
- Fake Object
- Test Spy
- Mock Object
我也不知道每个准确的中文翻译是什么,我觉得大部分人英文应该能看懂,但是很可能除了清楚Test Spy(测试间谍)功能比较不同外,大部分人不清楚其他三者的分别。下面就来解释一下。
Test Stub:Stub这个词是指短短的一截东西,比如木桩,烟蒂。所以Test Stub的特点就是短。作者举了个很好的例子,比如我们的代码用到了一个打Log的库,Log的实际实现可能很复杂,需要把Log推送到某个远程服务器或者存到数据库,我们手头的测试并不关心Log的输出,只求测试代码跑到需要打Log的地方不出错即可,我们就可以通过Test Stub把Log的接口实现了,每个接口只需要留空就行。见下面的例子,log方法留空即可。
public class LoggerStub implements Logger {
public void log(LogLevel level, String message) {
// still a no-op
}
public LogLevel getLogLevel() {
return LogLevel.WARN; // hard-coded return value
}
}
如果Test Stub对于方法的简单实现(通常只有一行return代码)不能满足需求,就要用到下面的Fake Object了
Fake Object:假对象。假对象的典型例子是模拟数据访问层。通过模拟数据访问层我们可以不访问数据库,而实现简单的增删改操作,把对象存在内存中。比如大家可以思考怎么通过维护一个内部数组来模拟下面的接口。
public interface UserRepository {
void save(User user);
User findById(long id);
User findByUsername(String username);
}
Test Spy:大部分测试的代码,我们可以通过对这段代码的输入输出来判断代码的正确性。但在某些情况下这是无法实现的。比如说,我们要测试的东西并不能通过输出来判断因为执行的代码根本没有任何输出,我们要测试的是在某种情况下代码有没有调用某个特定接口之类。这时候需要为这些接口上建立Test Spy来记录接口方法是否被执行,传入接口的参数是否正确。
比如下面的对于Log的Test Spy,里面的received方法就是用来在事后检测特定的log是否被write方法记录下来了。
private class SpyTarget implements DLogTarget {
private List<String> log = new ArrayList<String>();
@Override
public void write(Level level, String message) {
log.add(concatenated(level, message));
}
boolean received(Level level, String message) {
return log.contains(concatenated(level, message));
}
private String concatenated(Level level, String message) {
return level.getName() + ": " + message;
}
}
Mock Object:这是一种加强版的Stub,在所替代的方法上,规定当接收到特定的参数时候表现出指定的行为。同时又可以说是一种简化版的test spy,因为基本只是记录方法的入参和调用的次数,并不记录其他内部状态。这时候我们可以选择库而不是自己实现,对Java而言,如JMock,Mockito,EasyMock。实际上以上四种替身类型,除了Fake Object可能不太合适外,基本都可以用mock的库来实现。
下一篇文章将介绍如何用好测试替身。
网友评论