使用 Java 8 时钟对类进行单元测试

2022-08-31 12:43:27

引入了Java 8,它可以用作许多其他对象的参数,允许您向它们注入真实或虚假的时钟。例如,我知道您可以创建一个然后调用,它将返回您提供的固定。这听起来非常适合单元测试!java.time.Clockjava.timeClock.fixed()Instant.now(clock)Instant

但是,我很难弄清楚如何最好地使用它。我有一个类,类似于以下内容:

public class MyClass {
    private Clock clock = Clock.systemUTC();

    public void method1() {
        Instant now = Instant.now(clock);
        // Do something with 'now'
    }
}

现在,我想对这段代码进行单元测试。我需要能够设置生成固定时间,以便我可以在不同的时间进行测试。显然,我可以使用反射将成员设置为特定值,但是如果我不必诉诸反射,那就太好了。我可以创建一个公共方法,但这感觉不对劲。我不想向该方法添加参数,因为实际代码不应该关心传入时钟。clockmethod()clocksetClock()Clock

处理此问题的最佳方法是什么?这是新代码,所以我可以重新组织类。

编辑:为了澄清,我需要能够构造一个对象,但能够让一个对象看到两个不同的时钟值(就好像它是一个规则的系统时钟滴答作响)。因此,我无法将固定时钟传递到构造函数中。MyClass


答案 1

我不想向该方法添加时钟参数,因为实际代码不应该关心传入时钟。

不。。。但您可能希望将其视为构造函数参数。基本上,你是说你的班级需要一个时钟来工作......所以这是一种依赖关系。像对待任何其他依赖项一样对待它,并在构造函数中或通过方法注入它。(我个人更喜欢构造函数注入,但YMMV。

一旦你不再把它看作是你可以很容易地构建自己的东西,并开始把它看作是“另一种依赖关系”,那么你就可以使用熟悉的技术。(诚然,我假设你对依赖注入总体上感到满意。


答案 2

让我把Jon Skeet的答案和注释放到代码中:

被测类:

public class Foo {
    private final Clock clock;
    public Foo(Clock clock) {
        this.clock = clock;
    }

    public void someMethod() {
        Instant now = clock.instant();   // this is changed to make test easier
        System.out.println(now);   // Do something with 'now'
    }
}

单元测试:

public class FooTest() {

    private Foo foo;
    private Clock mock;

    @Before
    public void setUp() {
        mock = mock(Clock.class);
        foo = new Foo(mock);
    }

    @Test
    public void ensureDifferentValuesWhenMockIsCalled() {
        Instant first = Instant.now();                  // e.g. 12:00:00
        Instant second = first.plusSeconds(1);          // 12:00:01
        Instant thirdAndAfter = second.plusSeconds(1);  // 12:00:02

        when(mock.instant()).thenReturn(first, second, thirdAndAfter);

        foo.someMethod();   // string of first
        foo.someMethod();   // string of second
        foo.someMethod();   // string of thirdAndAfter 
        foo.someMethod();   // string of thirdAndAfter 
    }
}

推荐