这篇来学习单元测试中的Stub,翻译过来就是测试桩。测试过程中为什么要打桩,什么是桩,如何写一个简单的测试桩的例子,本篇就来了解这些基本的问题。
1.什么是测试桩
不要去看网上看哪些所谓的定义,在了解桩之前,我们需要了解依赖。
上面这个图,SUT表示被测试的软件,TestCode是我们写的一个测试用例,其中我们需要测试里面一个模块,但是这个模块依赖别的模块,依赖的模块加入是被部门甚至别公司开发,目前还没做好。为了不影响我们单元测试,我们认为这个依赖模块是没有问题的,然后我们自己写一个桩模块来代替这个依赖模块,这样测试用例就能继续执行下去。
所以桩就是模拟依赖模块的一段测试代码或者一个测试模块,一般我们单独写一个类,这个类以实现依赖模块或者接口。我们桩里面实现代码就随便返回一个数据,可以乱写返回数据,这都是可以。
2.代码准备
还是这个前面一篇用到标准工程
打开TrackingService.java文件,对addProtein(int amount)方法进行修改代码。
public void addProtein(int amount){
total += amount;
history.add(new HistoryItem(historyId++, amount,"add", total));
if(total > goal) {
boolean sendResult = notifier.send("goal met");
String historyMessage = "send: goal met";
if(!sendResult) {
historyMessage = "send_error: goal met";
}
history.add(new HistoryItem(historyId++, 0, historyMessage, total));
}
}
if判断这段就是我们准备好的依赖代码,Notifier这个是一个接口,里面有一个具体的send方法由于各种原因没有准备好。这个Notifier就是我们依赖模块,目前由于没有send的具体实现,我们无法测试这段代码。
这个文件完整的代码
package com.anthony.protein;
import java.util.ArrayList;
import java.util.List;
public class TrackingService {
private int total;
private int goal;
private List<HistoryItem> history = new ArrayList<HistoryItem>();
private int historyId = 0;
private Notifier notifier;
public void addProtein(int amount){
total += amount;
history.add(new HistoryItem(historyId++, amount,"add", total));
if(total > goal) {
boolean sendResult = notifier.send("goal met");
String historyMessage = "send: goal met";
if(!sendResult) {
historyMessage = "send_error: goal met";
}
history.add(new HistoryItem(historyId++, 0, historyMessage, total));
}
}
public void removeProtein(int amount){
total -= amount;
if(total < 1){
total = 0;
}
history.add(new HistoryItem(historyId++, amount,"subtract", total));
}
public int getTotal(){
return total;
}
public void setGoal(int value) throws InvalidGoalException{
if(value < 0) {
throw new InvalidGoalException();
}
goal = value;
}
public boolean isGoalMet(){
return total >= goal;
}
public List<HistoryItem> getHistory(){
return history;
}
}
依赖模块接口设计好了,但是没有具体实现方法
package com.anthony.protein;
public interface Notifier {
boolean send(String message);
}
下面我们需要通过创建测试桩来继续我们的单元测试
3.创建测试桩
测试桩一般是依赖模块的名称加上Stub.java来命名,所以我创建这么一个类,并实现Notifier接口。
package com.anthony.protein;
public class NotifierStub implements Notifier {
public boolean send(String message) {
return true;
}
}
这个测试桩里面我们随便写一个符合业务的返回值,我们业务上这个返回值要求是布尔值。这里我就是写一个True好了。
然后给TrackingSerivce.java添加一个带notifier参数的构造方法
public TrackingService(Notifier notifier) {
this.notifier = notifier;
}
如果我们之前写的TracingServiceTest.java中的代码,由于刚才新的构造函数添加而出现报错,这里我们修改下TrackingService初始化的代码,加上这个参数,改成下面这样。
@Before
public void setup() {
System.out.println("Before Method");
ts = new TrackingService(new NotifierStub());
}
4.写一个单元测试用例
在TrackingServiceTest.java添加下面测试用例,来测试我们桩代码。
@Test
public void whenGoalIsMetHistoryIsUpdate() throws InvalidGoalException {
ts.setGoal(5);
ts.addProtein(6);
// 这种条件触发了Notifier 发送消息功能
HistoryItem result = ts.getHistory().get(1); //取第二个数据
assertEquals("send: goal met", result.getOperation());
}
由于我们在NotifierStub.java中返回是true, 所以这里断言就用“send: goal met”, 如果返回false,你这里就改成“send_error: goal met”, 运行一下就能通过。
总结:为了方便测试不被打断,我们有时候需要写桩代码,桩代码返回值只要符合业务就可以,里面代码基本上都是假代码。利用写好的桩代码去跳过原本依赖的业务方法,这个就是单元测试中的桩模块测试。