在正常运行时,debugging 为false,SystemTimeSynchronizer 调用的是NtpClockWrapper
实例。在单元测试时,可以在测试代码里将debugging 设为true,这样SystemTimeSynchronizer
实际调用的是SystemClock 实例。
/**shannon.demo.unittest is the unit test codes
* package for the demonstration.
*/
package shannon.demo.unittest;
import shannon.demo.SystemTimeSynchronizer;
import shannon.demo.UnitTestFirewall;
import junit.framework.TestCase;
/**SystemTimeSynchronizerTest is the
* unit test class for SystemTimeSynchronizer
* @author Shannon Qian
*/
public class SystemTimeSynchronizerTest extends TestCase {
protected void setUp() throws Exception {
super.setUp();
UnitTestFirewall.setDebugging(true);
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testSyncTime() {
int result = new SystemTimeSynchronizer().syncTime();
assertTrue(result==0||result==1||result==-1);
}
}
另一方面,其实我们也看到了,如果NtpClock 对象后面连着真正的NTP 服务器,那么
它永远只能返回正确的时间,而SystemTimeSynchronizer#syncTime()实际上提供了系统时间
快于、慢于和恰好等于标准时间三种情况的处理逻辑。如果要测全三种场景,修改NTP 服
务器会是件麻烦的事情。或者为此等很长时间,还要看运气。所以目前为止,我们只能在
SystemTimeSynchronizerTest#testSyncTime()里验证SystemTimeSynchronizer
#syncTime()的返回值是{0, 1, -1}中的任何一个。
要方便的测试所有的处理逻辑分支,关键就是要能够随心所欲的控制Clock#getTime()
的返回值。在此定义一个DebugClock 替换原来的SystemClock 类。
package shannon.demo;
/**
* DebugClock is a Clock for debugging.
* It accepts arbitrary value as candidate time for
* the next return of {@link #getTime()}
* @author Shannon Qian
*/
public class DebugClock implements Clock {
private long time=-1L;
/**Sets candidate time for debugging.
* @param t - the candidate time value in millisecond
* @see #getTime()
*/
public void setTime(long t) {
this.time=t;
}
/** Returns the time in millisecond.
* By default, it returns system time if the
* candidate time is not preset. else it will
* return the candidate time after reseting it.
* @return - time in millisecond
* @see #setTime(long)
*/
public long getTime() {
if(time<0L)
return System.currentTimeMillis();
long t=this.time;
this.time=-1L;
return t;
}
}
在UnitTestFirewall 中也要做相应的修改,以允许在测试时,定制Clock 实例。
package shannon.demo;
import thirdparty.any.NtpClock;
/**
* UnitTestFirewall is the facility to
* ease unit test
* @author Shannon Qian
*/
public final class UnitTestFirewall {
private static boolean debugging=false;
/**Returns true if it's in debugging mode, else false.
* @return the debugging
*/
public static boolean isDebugging() {
return debugging;
}
/**Sets Debugging flag as true if it's time to unit test.
* @param on - the debugging to set, true for on and false
* for off
*/
public static void setDebugging(boolean on) {
UnitTestFirewall.debugging = on;
}
private final static NtpClock _ntpClock=new NtpClock();
private static class NtpClockWrapper implements Clock {
public long getTime() {
return _ntpClock.getTime();
}
}
private static NtpClockWrapper ntpClock = null;
private static Clock clock = null;
/**sets the Clock instance for debugging.
* If candidate is not null, {@link #getClock()} will
* return it when debugging is true.
* @param candidate - the Clock instance for debugging
*/
public static void presetClock(Clock candidate) {
clock=candidate;
}
/**Returns the Clock instance for SystemTimeSynchronizer's invocation.
*
* @return - Clock instance
*/
public static Clock getClock() {
if(debugging && clock != null) {
return clock;
} else {
if(ntpClock == null)
ntpClock = new NtpClockWrapper();
return ntpClock;
}
}
}
接着,SystemTimeSynchronizerTests 就可以作很大的改进,通过预设标准时间,测试
SystemTimeSynchronizer#syncTime()的各个处理分支了。
/**shannon.demo.unittest is the unit test codes
* package for the demonstration.
*/
package shannon.demo.unittest;
import shannon.demo.DebugClock;
import shannon.demo.SystemTimeSynchronizer;
import shannon.demo.UnitTestFirewall;
import junit.framework.TestCase;
/**SystemTimeSynchronizerTest is the
* unit test class for SystemTimeSynchronizer
* @author Shannon Qian
*/
public class SystemTimeSynchronizerTest extends TestCase {
protected void setUp() throws Exception {
super.setUp();
UnitTestFirewall.setDebugging(true);
UnitTestFirewall.presetClock(debugClock);
}
protected void tearDown() throws Exception {
super.tearDown();
}
private static DebugClock debugClock=new DebugClock();
public void testSyncTime() {
SystemTimeSynchronizer sync=new SystemTimeSynchronizer();
assertTrue(sync.syncTime()==0);
debugClock.setTime(System.currentTimeMillis()-10L);
assertTrue(sync.syncTime()==1);
debugClock.setTime(System.currentTimeMillis()+10L);
assertTrue(sync.syncTime()==-1);
}
}
再总结一下这种测试桩方法的好处,我体会到的有两个:
1) 免去许多配置和调试外部模块的工作;
2) 可以方便的模拟其他模块的各种行为,提供各种场景测试条件。
当然,实际问题要复杂的多,所以我们可能要比这个例子做得更多。但正因为实际问题复杂,
我们省的力气也要多得多。
另外有同事告诉我JMock 和easyMock 也提供和我上面所讨论的法子类似的功能。我花
了极少的时间试着了解过JMock,但还没入门,所知很少,还望有经验的同行指教。再次感
谢myworkfirst。