看看下面的图:
|-----------------------------------------------------------------|
| |
| |---------------------| | |-----------------------------|
| | Tested | <------------------------------------------à | External Mock Object |
| | Object | | |----------------------------|
| |---------------------| |
| /|\ |--------------------| |
| |-------〉 | Internal Mock | |
| | Object | |
| |--------------------| |
| [Your system scope] |
|--------------------------------------------------------- --------|
在测试你的Tested Object时,你可能会与你系统内的某个模块或系统外某个实体交互,而这些模块或实体在你做单元测试的时候可能并不存在,这时:
Ø Internal Mock Object可能是一个你的系统尚未完成的模块的“替身”(replacement);
Ø External Mock Object可能是测试你的Tested Object时需要的外部的环境实体的“替身”(replacement)。
不知道这样给Mock Object分类是否正确J
我们来看看与Real world object交互有什么不足之处:
Ø Real world object的行为具有不确定性,我们难于控制它们的输出or返回结果。
Ø Real world object有些时候是难于被建立的或者说是无法获得的。
Ø Real world object的有些行为难于被触发,如磁盘已满,网络error等。
Ø Real world object可能不存在,比如你的Tested Object需要与你的系统的另一个module交互,而另一个module尚未开发完毕。
当然还不止这些,我们仅仅是列出一部分。
使用Mock Object替代Real world object后我们就会解决上述问题,换句话说当上面的情况出现后,我们就可以使用Mock Object。这也是什么时候该使用Mock Object的answer。
Mock Object是我们自己编写的,我们拥有控制它的绝对的权力,我们可以定制它的行为和输出。
Use Mock Object
使用Mock Object解决上述问题可分三步走:
1. Use an interface to describe the object
2. Implement the interface for production code
3. Implement the interface in a mock object for testing [3]
还有一点就是对于Internal Mock Object早晚你要实现出其Real world object的,因为那是你系统的一部分。
一个改自资料[3]的例子
public interface Environmental {
public long getTime();
// Other methods omitted...
}
对于这样一个接口,我们提供两种实现,
//real world object
public class SystemEnvironment implements Environmental {
public long getTime() {
return System.currentTimeMillis();
}
// other methods ...
}
//mock object
public class MockSystemEnvironment implements Environmental {
public long getTime() {
return currentTime;
}
public void setTime(Time aTime){
this.currentTime = aTime;
}
private Time currentTime;
//others
}
我们可以看到在MockSystemEnvironment中我们提供“setTime”函数是为了提供控制Mock Object的接口。
我们要测试的类
//TestedObject
public class TestedObject{
private Environmental env;
TestedObject(Environmental aEnv){
this.env = aEnv;
}
public boolean isAm(){
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(env.getTime());
int hour = cal.get(Calendar.HOUR_OF_DAY);
if (hour <=12) return true;
return false;
}
}
将要测试的类放入单元测试框架
public class TestTestedObject extends TestCase {
public void testIsAm(){
MockSystemEnvironment env = new MockSystemEnvironment();
// Set up a target test time
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2004);
cal.set(Calendar.MONTH, 10);
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 16);
cal.set(Calendar.MINUTE, 55);
long t1 = cal.getTimeInMillis();
env.setTime(t1);
TestedObject to = new TestedObject(env);
assertFalse(to.isAm());
}
}
在该单元测试中我们使用了Mock Object,并且在使用前我们利用setTime接口,输入了我们需要的值。结果我们会通过测试。如果我们使用Real Object,我们得到的测试结果将是不固定的,后者可不是所期望的。从这个例子中你也应该体会到Mock object的一些好处了。
如果我们总是手动写我们需要的Mock Object,那将是一个很大的工作量。现在业界有了Mock Objects、easy mock等开源框架的支持,是我们编写Mock object变得越来越容易。
参考资料:
1、《Test-Driven Development – A practical guide》
2、《JUnit in action》
3、《Pragmatic Unit Testing》