制作存根
2003年6月10日
测试增强型设计的一个常见问题是如何在测试模式下创建服务存根,同时让真实的服务在生产环境(以及某些测试中)可用。我的几位同事分享了他们的想法。
Jeremy Stell-Smith 向我展示了一种基于抽象工厂的方法。所有可存根的服务都从一个工厂中提取。此示例展示了这样的持久性类。
public abstract class Persistence...
public static Persistence getInstance() {
return (Persistence)Factories.get(Persistence.class);
}
public abstract void save(Object obj);
除了抽象工厂功能外,测试工厂还具有一个实现堆栈的不错功能——这使得工厂的设置更加容易。
public class FooTest...
public void setUp() {
TestFactories.pushSingleton(Persistence.class,
new MockPersistence());
}
public void tearDown() {
TestFactories.pop(Persistence.class);
}
public void testSave() {
Foo foo = new Foo();
foo.save();
...
}
public class Foo ...
public void save() {
Persistence.getInstance().save(this);
}
在另一个项目中,Kraig Parkinson 展示了一种略有不同的方法。与使用单个抽象工厂不同,那些需要存根的服务使用原型。
public class MyFacade {
private static MyFacade prototype;
/**
* Sets the instance of the facade that will be returned by the getInstance method
* used by all clients of the facade.
*/
public static void setFacade(MyFacade newPrototype) {
prototype = newPrototype;
}
/**
* Returns an instance of the facade, using the prototype if set,
* otherwise an instance of the facade is used during normal operation.
*/
public static MyFacade getInstance() {
if (prototype != null)
return prototype;
else
return new MyFacade();
}
要在测试中使用它,您可以执行以下操作。
public class MyClientTest extends junit.framework.TestCase {
private class Client {
public String speak(String input) {
return MyFacade.getInstance().echo(input);
}
public void dance() {
return MyFacade.getInstance().move();
}
}
public void testSpeak() {
final String expectedInput = "bar";
final String expectedOutput = "foo";
MyFacade.setPrototype(new MyFacade() {
public String echo(String input) {
assertEquals(expectedInput, input);
return expectedOutput;
}
}
//Invoke code that'd invoke the facade, but remember to remove
// the prototype reference once you're done with it....
try {
final String actualOutput = new Client.speak(expectedInput);
assertEquals(expectedOutput, actualOutput);
} finally {
MyFacade.setPrototype(null);
}
}
public void testDance() {
final StringBuffer proof = new StringBuffer();
MyFacade.setPrototype(new MyFacade() {
public void move() {
proof.append("g");
}
}
//Invoke code that'd invoke the facade, but remember to remove
// the prototype reference once you're done with it....
try {
new Client().move();
assertTrue("move was not invoked", proof.length > 0);
} finally {
MyFacade.setPrototype(null);
}
}
在这种情况下,Kraig 在测试方法的 finally 块中清理资源。另一种选择(我承认这是我通常的做法)是将清理代码放在 tearDown 中。
舞蹈案例类似于 Mock Objects 人员对模拟对象设置期望的想法。您可以将其视为一种轻量级的 Mock Objects 方法。

