Java 我可以使用mockito来匹配具有自动更新时间戳的对象吗?

Java 我可以使用mockito来匹配具有自动更新时间戳的对象吗?,java,unit-testing,time,mocking,mockito,Java,Unit Testing,Time,Mocking,Mockito,如果在进行模拟调用之前自动更新时间戳,那么最好的方法是什么 下面是我试图测试的一些虚拟代码: public class ThingWithATimestamp { public Long timestamp; public String name; public ThingWithATimestamp(String name) { this.name = name; } } public class TheClassThatDoesStuff

如果在进行模拟调用之前自动更新时间戳,那么最好的方法是什么

下面是我试图测试的一些虚拟代码:

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范围内,以便我知道它已正确更新,并且已使用对象的正确时间戳调用了
连接器。update

首先:

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

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

import java.util.Date;
public class TimeHelper{
    public long currentTimeMillis(){
        return System.currentTimeMillis();
    }
    public Date makeDate(){
        return new Date();
    }
}
它可能有更多相同类型的方法。现在,任何使用此类函数的类都应该(至少)有两个构造函数——一个是将在应用程序中使用的普通构造函数,另一个是包私有构造函数,其中
TimeHelper
是一个参数。此
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的
when
然后return
,或者
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
        // ... ???
    }
}        

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

为什么有这个时间戳?这其中有什么逻辑?在间隔中检查时间戳对我来说很棘手-你必须选择很小的间隔来检查准确性,但它也应该很宽,以减少错误警报。对我来说,这表明设计中有些东西太复杂,或者不应该进行测试。我们有时间戳,因为我们实际的类是一个save,我们希望在服务器上为最后一次更新时间始终设置“now”。如果我们的“连接器”与SQL db对话,我实际上会选择在SQL中执行时间戳,但我们的连接器与没有执行时间戳功能的服务对话。检查时间戳是否已更改或比以前更晚?是的,这对于我正在做的工作可能已经足够了。好的,然后,如果我还需要验证,请说出名称,我可以使用and()。我想我是在同一个匹配器中同时匹配了这两个。很酷,这对我来说应该行得通,谢谢。直到你在不同的平台上运行你的测试,这会产生不同的时间片。我已经被这种方法弄得焦头烂额——编写在我的windows PC上运行良好的测试,然后在Jenkins环境中Linux机器上运行时失败。如果您正在测试这样的时间关键型东西,那么您真的需要使用时间助手。如果我有时间,我会发布一个详细的答案。好的,完成。另外,请不要在调用
验证
时写入
次(1)
。默认值是
times(1)
,因此在测试中显式地编写它只会使测试变得混乱。我还将编写一个函数,该函数可以很好地读取,并返回
timestininterval
。然后可以编写
verify(连接器,times(1)).update(argThat(timestampIsInInterval())
.Duh,是的,这太明显了,真不敢相信我没有想到会做这样的事情。实际上,由于您提到的时间原因,我打算采用这种解决方案。作为旁注,将此标记为已接受的答案还是另一个更合适?另一个答案更准确地回答了原始问题,但这是唯一的答案问题的实际解决方案。也许这更像是在meta上发表的评论或其他东西。基于我在meta上读到的几件事,我接受了这个答案。似乎大家的共识是接受你使用的答案,即使它不是问题的最直接答案。甚至比TimeHelper更好,你可以使用jodatime来保留你的timJodatime包含一个实用程序类,用于设置它使用的“当前时间”,正是出于此目的。
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
        // ... ???
    }
}