Mockito:如何模拟 JodaTime 的界面

发布于 2024-11-08 07:18:17 字数 2620 浏览 4 评论 0原文

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

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< /code> 取决于当天相对于时间间隔 [4PM-7PM] EST 的当前时间和发件人名称。由于 Shipper 将通过参数传递,因此我们可以对其进行单元测试,没有问题。但我需要嘲笑时间。所以这是我尝试的

@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代码,请对我的代码进行批评。我真的很想学

I use JodaTime#DateTime, and I need to mock its behavior. Since it is not possible to directly mock JodaTime#DateTime, I create an interface of it

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")); 
    }   
}

Here is the method that I want to test

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;
  }
}

As you can see the printJobName depend on the current time of the day relative to the time interval [4PM-7PM] EST and the Shipper name. Since Shipper will be pass via parameter, we can unit test it no problem. But I need to mock the time. So here is what I try

@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);
}

The test should fail since I pass in 6PM, but XNCRMNCF is the name for before 4PM. Do I need to mock printProcessor as well. If what I have is wrong. How should I fix it? I am trying to learn writing high level java code, please be very criticized about my code. I really want to learn

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

怕倦 2024-11-15 07:18:17

这是一个典型的测试案例,显示了设计中的潜在缺陷。您无法模拟 JodaTime,因为您对被测类中的这些类具有硬连线依赖关系。

查看 SOLID 原则,了解为什么这可能是一个问题(特别是在这种情况下依赖倒置原则)。如果您在某处注入了 JodaTime 作为依赖项,那么在单元测试中您将能够用 模拟、存根或间谍(视情况而定)。

但是: JodaTime 是极不可能在生产环境中与其他任何东西一起注入的东西,无论它存在多久。相反,在这种情况下,您可能会更好地使用 组合方法设计模式。在这里,您可以将用于生成 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);
}

This is a classic case of testing showing up a potential flaw in design. You cannot mock JodaTime because you have a hard-wired dependency to these classes in your class-under-test.

Have a look at the SOLID principles to understand why this could be a problem (especially in this case the Dependency Inversion Principle). If you injected JodaTime somewhere as a dependency, then in your unit test you would be able to replace a real instace of it with a mock, stub or spy as appropriate.

However: JodaTime is something that is highly unlikely to be injected with anything else in the production environment, no matter how long it is live for. Instead, in this case you would probably be better served with the Composed Method Design Pattern. Here, you would extract whatever calculation/algorithm you use to generate the printjobName to another method (I can't see how you do it here because your code snippet never assigns a value to that variable). Then you can spy (partial mock) your class under test to only mock that method and return a fixed value, regardless of the real date time that JodaTime is delivering, for instance:

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
    }
}

Now you can write in your test:

@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);
}
伊面 2024-11-15 07:18:17

您永远不会向 PrintProcessor 提供您的模拟。对对象进行模拟与将模拟赋予对象不同。因此,当您调用 PrintProcessor 上的方法时,它是在 JodaTime 的真实实例上进行操作。有几种方法可以为 PrintProcessor 提供模拟:

  1. 使用 PowerMockito (确保您使用 PowerMock-mockito jar 而不是 PowerMock-easymock jar)并模拟 JodaTime 构造函数以返回模拟的 Clock 对象 whenNew(JodaTime.class).withNoArguments().thenReturn (mockJodaTime); 这将在使用 JodaTime 的无参数构造函数的地方插入您的模拟。注意:这需要您使用 JodaTime 类的模拟。
  2. Clock jodaTime 字段添加一个 setter 方法(如果仅在构造函数中定义,该方法可能应该是 final)。
  3. 为您的 Clock 类使用工厂,并在测试期间简单地返回模拟(您可以使用 PowerMockito 来模拟静态方法)。
  4. 创建一个带有 Clock 参数的构造函数并传入您的模拟。

You are never giving the PrintProcessor your mock. Making a mock of the object is not the same as giving your mock to an object. So, when you call methods on PrintProcessor, it is operating on a real instance of JodaTime. There are couple of ways to give the PrintProcessor your mock:

  1. Use PowerMockito (make sure you use PowerMock-mockito jar and not PowerMock-easymock jar) and mock the JodaTime constructor to return your mocked Clock object whenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime); This will insert your mock wherever a no-arg constructor for JodaTime is used. Note: This will require you to use a mock of the JodaTime class.
  2. Add a setter method for the Clock jodaTime field (which, if only defined in the constructor should probably be final).
  3. Use a Factory for your Clock class and simply return the mock during tests (you can use PowerMockito to mock static methods).
  4. Create a constructor with a Clock parameter and pass in your mock.
小草泠泠 2024-11-15 07:18:17

我认为你绝对走在正确的道路上。创建用于模拟的时钟接口绝对是一个好主意。

我在您的代码中没有看到一件事:将模拟时钟注入 printProcessor 中。创建模拟后,我认为您需要以下内容:(

printProcessor.setClock(clock)

这在调用 getPrintJobName 之前进行。此设置器应在 PrintProcessor 类中设置 jodaTime 属性)

我习惯了 EasyMock,所以我可能是错的,但我我非常确定您还需要设置 getFourPM_EST 和 getSevenPM_EST 调用的期望。

I think you're definitely on the right path. Creating the Clock interface for mocking is definitely a good idea.

One thing I don't see in your code: injecting the mocked clock into the printProcessor. After creating the mock, I think you need something along the lines of:

printProcessor.setClock(clock)

(this goes before you call getPrintJobName. This setter should set the jodaTime property in your PrintProcessor class)

I'm used to EasyMock, so I might be wrong, but I'm pretty sure you will also need to set expectations for the getFourPM_EST and getSevenPM_EST calls.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文