3

在进行模拟调用之前自动更新时间戳的最佳方法是什么?

这是我要测试的一些虚拟代码:

public class ThingWithATimestamp {
    public Long timestamp;
    public String name;

    public ThingWithATimestamp(String name) {
        this.name = name;
    }
}

public class TheClassThatDoesStuff {
    private ThingConnector connector;

    public TheClassThatDoesStuff(ThingConnector connector) {
        this.connector = connector;
    }

    public void updateTheThing(MyThingWithATimestamp thing) {
        thing.timestamp = currentTimestamp();
        connector.update(thing);            
    }
}

这是我要测试的内容:

public class TheClassThatDoesStuffTests {
    @Test
    public void canUpdateTheThing() {
        ThingConnector connector = mock(ThingConnector.class);
        TheClassThatDoesStuff doer = new ThisClassThatDoesStuff(connector);

        doer.updateTheThing(new ThingWithATimestamp("the name"));

        verify(connector, times(1)).update(SomeMatcherThatICantFigureOut);
    }

我知道这段代码非常愚蠢,但我认为它准确地描述了我想要验证的内容。我基本上需要一个匹配器来填写测试,以验证时间戳是否在当前时间的 X 范围内,所以我知道它已正确更新,并且connector.update使用对象的正确时间戳调用。

4

3 回答 3

9

我发现处理时间关键代码的最稳健的方法是将所有时间关键函数包装在它们自己的类中。我通常称之为TimeHelper。所以这个类可能如下所示。

import java.util.Date;
public class TimeHelper{
    public long currentTimeMillis(){
        return System.currentTimeMillis();
    }
    public Date makeDate(){
        return new Date();
    }
}

它可能有更多相同类型的方法。现在,任何使用这些函数的类都应该(至少)有两个构造函数——您将在应用程序中使用的普通构造函数,加上一个包私有的构造函数,其中 aTimeHelper是一个参数。这TimeHelper需要存放起来以备后用。

public class ClassThatDoesStuff {
    private ThingConnector connector;
    private TimeHelper timeHelper;

    public ClassThatDoesStuff(ThingConnector connector) {
        this(connector, new TimeHelper());
    }

    ClassThatDoesStuff(ThingConnector connector, TimeHelper timeHelper) {
        this.connector = connector;
        this.timeHelper = timeHelper;
    } 
}

现在,在你的课堂上,而不是写作System.currentTimeMillis(),写作timeHelper.currentTimeMillis()。当然,这将产生完全相同的效果;除了现在,您的课程神奇地变得更加可测试。

当你测试你的课程时,模拟TimeHelper. 配置此模拟(使用 Mockito 的whenandthenReturn或替代doReturn)以返回您喜欢的任何时间值 - 无论您需要什么测试。如果您要currentTimeMillis()在测试过程中多次调用,您甚至可以在此处返回多个值。

现在使用第二个构造函数来创建您要测试的对象,并传入模拟。这使您可以完美控制测试中使用的时间值;并且您可以使您的断言或验证断言已经使用了正确的值。

public class ClassThatDoesStuffTest{
    @Mock private TimeHelper mockTime;
    @Mock private ThingConnector mockConnector;
    private ClassThatDoesStuff toTest;

    @Test
    public void doesSomething(){
        // Arrange
        initMocks(this);
        when(mockTime.currentTimeMillis()).thenReturn(1000L, 2000L, 5000L);
        toTest = new ClassThatDoesStuff(mockConnector, mockTime);

        // Act
        toTest.doSomething();

        // Assert
        // ... ???
    }
}        

如果这样做,您就知道您的测试将始终有效,并且永远不会依赖于操作系统的时间切片策略。您还可以验证时间戳的确切值,而不是断言它们属于某个近似区间。

于 2012-06-15T08:22:13.693 回答
2

第一的:

class TimestampInInterval extends ArgumentMatcher< ThingWithATimestamp > {
      public boolean matches(Object arg) {
          ThingWithATimestamp thing = (ThingWithATimestamp) arg;
          long delta = thing.timestamp - System.currentTimeMillis();
          return delta < INTERVAL;
      }
   }

第二:

verify(connector, times(1)).update(argThat(new TimestampInInterval());
于 2012-06-14T23:00:25.233 回答
0

尽管接受的答案很好,但没有必要创建自定义TimeHelper类。您可以改为在您的类中使用java.time.Clock作为依赖项,如本答案中所述。TheClassThatDoesStuff

java.time.Clock有许多静态工厂方法来创建具有固定时间点、自定义节奏/滴答声和从当前时刻偏移的时钟。这些工厂方法在测试期间很有用。

于 2021-07-15T10:11:55.320 回答