我使用JodaTime#DateTime,我需要模拟它的行为。由于不可能直接模拟JodaTime#DateTime,因此我创建了一个接口(interface)

Clock.java

public interface Clock {
    DateTime getCurrentDateTimeEST();
    DateTime getFourPM_EST();
    DateTime getSevenPM_EST();
}

JodaTime.java
public class JodaTime implements Clock {

    @Override
    public DateTime getCurrentDateTimeEST() {
        return new DateTime(DateTimeZone.forID("EST"));
    }

    @Override
    public DateTime getFourPM_EST() {
        DateTime current = getCurrentDateTimeEST();
        return new DateTime(current.getYear(), current.getMonthOfYear(),
                current.getDayOfMonth(), 16, 0, 0, 0, DateTimeZone.forID("EST"));
    }

    @Override
    public DateTime getSevenPM_EST() {
        DateTime current = getCurrentDateTimeEST();
        return new DateTime(current.getYear(), current.getMonthOfYear(),
                current.getDayOfMonth(), 19, 0, 0, 0, DateTimeZone.forID("EST"));
    }
}

这是我要测试的方法
public class PrintProcessor{

  Clock jodaTime;

  public PrintProcessor(){
      jodaTime = new JodaTime();
  }
  ...
  public String getPrintJobName(Shipper shipper){
    String printJobName = null;
    //Get current EST time
    if(jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getFourPM_EST()) ||
            jodaTime.getCurrentDateTimeEST().isAfter(jodaTime.getSevenPM_EST())){   //Before 4PM EST and after 7PM EST
        switch(shipper){
        case X:
        ...
    }else if(jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getSevenPM_EST())){ //Between 4PM-7PM EST
        switch(shipper){
        case X:
        ...
    }
    return printJobName;
  }
}

如您所见,printJobName取决于相对于时间间隔[4 PM-7PM] EST和托运人名称的一天中的当前时间。由于Shipper将通过参数传递,因此我们可以对其进行单元测试。但是我需要 mock 时间。所以这是我尝试的
@Test
public void testGetPrintJobNameBeforeFourPM(){
    DateTime current = new DateTime(DateTimeZone.forID("EST"));
    Clock clock = mock(Clock.class);
    //Always return 6pm when I try to ask for the current time
    when(clock.getCurrentDateTimeEST()).thenReturn(new DateTime(current.getYear(), current.getMonthOfYear(),
            current.getDayOfMonth(), 18, 0, 0, 0, DateTimeZone.forID("EST")));
    //Test for Fedex
    String printJobName = printProcessor.getPrintJobName(Shipper.X);
    assertEquals("XNCRMNCF", printJobName);
}

自从我在下午6点通过考试以来,测试应该会失败,但是XNCRMNCF是下午4点之前的名称。我是否还需要模拟printProcessor?如果我有错。我该如何解决? 我正在尝试学习编写高级Java代码,请非常批评我的代码。我真的很想学习

最佳答案

这是一个典型的测试案例,显示了设计中的潜在缺陷。您不能模拟JodaTime,因为您在被测类中对这些类有硬性依赖。

查看SOLID principles以了解为什么这可能是个问题(尤其是在本例中的Dependency Inversion Principle)。如果您将JodaTime作为依赖项注入(inject)到某个地方,那么在单元测试中,您将能够适本地用mock, stub or spy替换它的真实实例。

但是: JodaTime是极不可能在生产环境中与其他任何东西一起注入(inject)的,无论它能使用多长时间。相反,在这种情况下,最好将Composed Method Design Pattern服务。在这里,您将提取用于生成printjobName的任何计算/算法,并将其提取给另一种方法(我在这里看不到您的操作方式,因为您的代码段从未为该变量分配值)。然后,您可以监视(部分模拟)您的受测类,以仅模拟该方法并返回固定值,而不管JodaTime传递的实际日期时间如何,例如:

public class PrintProcessor {
    ...
    public String getPrintJobName(Shipper shipper) {
        String printJobName = null;
        String timeHash = this.getTimeHash();
        if (this.isBeforeFourPM()) {
            switch(shipper) {
                printJobName = // Do something with timeHash to generate name
            }
        } else {
            ...
        }
        return printJobName;
    }

    public boolean isBeforeFourPM() {
        return (jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getFourPM_EST()) ||
            jodaTime.getCurrentDateTimeEST().isAfter(jodaTime.getSevenPM_EST()));
    }

    public String getTimeHash() {
        ... // Do something to hash the time value in to a String
    }
}

现在您可以在测试中编写:
@Test
public void testGetPrintJobNameBeforeFourPM() {
    PrintProcessor concretePrintProcessor = new PrintProcessor();
    PrintProcessor printProcessor = spy(concretePrintProcessor);
    doReturn(true).when(printProcessor).isBeforeFourPM();

    String printJobName = printProcessor.getPrintJobName(Shipper.X);

    assertEquals("XNCRMNCF", printJobName);
}

09-11 20:19