技术开发 频道

谈谈单元测试中的测试桩实践

  在正常运行时,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。

0
相关文章