制作存根
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 方法。