Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/visual-studio-2008/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用Java8时钟对类进行单元测试_Java_Unit Testing_Java 8_Java Time - Fatal编程技术网

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

使用Java8时钟对类进行单元测试,java,unit-testing,java-8,java-time,Java,Unit Testing,Java 8,Java Time,Java8引入了Java.time.Clock,它可以作为许多其他Java.time对象的参数,允许您将真实或虚假的时钟注入到这些对象中。例如,我知道您可以创建一个Clock.fixed(),然后调用Instant.now(Clock),它将返回您提供的固定Instant。这听起来非常适合单元测试 然而,我很难想出如何最好地使用它。我有一个类,类似于以下内容: public class MyClass { private Clock clock = Clock.systemUTC();

Java8引入了
Java.time.Clock
,它可以作为许多其他
Java.time
对象的参数,允许您将真实或虚假的时钟注入到这些对象中。例如,我知道您可以创建一个
Clock.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'
    }
}
现在,我想对这段代码进行单元测试。我需要能够设置
时钟
以产生固定的时间,这样我就可以在不同的时间测试
方法()。显然,我可以使用反射将
clock
成员设置为特定值,但如果我不必求助于反射,那就太好了。我可以创建一个public
setClock()
方法,但感觉不对。我不想在方法中添加
Clock
参数,因为真正的代码不应该与传入时钟有关

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

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

我不想在方法中添加时钟参数,因为真正的代码不应该关心传入时钟

不。。。但是,您可能希望将其视为构造函数参数。基本上你是说你的班级需要一个时钟来工作。。。这是一种依赖。将其视为任何其他依赖项,并将其注入构造函数或通过方法。(我个人支持构造函数注入,但YMMV)


一旦你不再认为它是你可以轻松构建自己的东西,而开始认为它“只是另一种依赖”,那么你就可以使用熟悉的技术了。(无可否认,我假设您对依赖注入总体上感到满意。)

让我将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 
    }
}
单元测试:

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

我在这里玩游戏有点晚了,但要补充其他答案,建议使用时钟-这肯定有效,通过使用Mockito的doAnswer,您可以创建一个时钟,您可以随着测试的进行动态调整

假设该类已被修改为在构造函数中使用时钟,并在
Instant.now(Clock)
调用中引用该时钟

public class TimePrinter() {
    private final Clock clock; // init in constructor

    // ...

    public void printTheTime() {
        System.out.println(Instant.now(clock));
    }
}
然后,在测试设置中:

private Instant currentTime;
private TimePrinter timePrinter;

public void setup() {
   currentTime = Instant.EPOCH; // or Instant.now() or whatever

   // create a mock clock which returns currentTime
   final Clock clock = mock(Clock.class);
   when(clock.instant()).doAnswer((invocation) -> currentTime);

   timePrinter = new TimePrinter(clock);
}
稍后在测试中:

@Test
public void myTest() {
    myObjectUnderTest.printTheTime(); // 1970-01-01T00:00:00Z

    // go forward in time a year
    currentTime = currentTime.plus(1, ChronoUnit.YEARS);

    myObjectUnderTest.printTheTime(); // 1971-01-01T00:00:00Z
}
您告诉Mockito在调用instant()时始终运行一个返回currentTime当前值的函数
Instant.now(时钟)
将调用
clock.Instant()
。现在,你可以快进、快退,通常时间旅行比德洛林更好。

创建一个可变时钟,而不是模拟时钟 首先,按照@Jon Skeet的建议,一定要在测试的类中注入一个
时钟。如果您的类只需要一次,那么只需传入
Clock.fixed(…)
值。但是,如果您的类在不同时间的行为不同,例如,它在时间A执行某项操作,然后在时间B执行其他操作,那么请注意,Java创建的时钟是不可变的,因此测试不能更改为一次返回时间A,另一次返回时间B

根据公认的答案,模拟是一种选择,但它将测试与实现紧密结合。例如,正如一位评论者指出的,如果被测试的类调用
LocalDateTime.now(clock)
clock.millis()
而不是
clock.instant()
,该怎么办

另一种比模拟更明确、更容易理解且可能更健壮的方法是创建一个真正的
Clock
实现,它是可变的,以便测试可以注入它并根据需要修改它。这并不难实现,或者这里有几个现成的实现:

  • (这是HT:@Bartek Jablonski)
  • (另请注意:@Wim Deblauwe)
下面是如何在测试中使用类似的东西:

MutableClock c = new MutableClock(Instant.EPOCH, ZoneId.systemDefault());
ClassUnderTest classUnderTest = new ClassUnderTest(c);

classUnderTest.doSomething()
assertTrue(...)

c.instant(Instant.EPOCH.plusSeconds(60))

classUnderTest.doSomething()
assertTrue(...)

正如其他人所指出的,您需要以某种方式对其进行嘲弄-然而,您很容易推出自己的:

    class UnitTestClock extends Clock {
        Instant instant;
        public void setInstant(Instant instant) {
            this.instant = instant;
        }

        @Override
        public Instant instant() {
            return instant;
        }

        @Override
        public ZoneId getZone() {
            return ZoneOffset.UTC;
        }

        @Override
        public Clock withZone(ZoneId zoneId) {
            throw new UnsupportedOperationException();
        }
    }

我面临同样的问题,无法使用现有的简单有效的解决方案,因此我最终遵循代码。当然,它可以更好,有可配置的TZ等,但我需要一些易于在测试中使用的东西,我想检查我的测试下类是如何处理时钟的:

  • 我不想关心时钟的
    方法被称为什么,所以模仿不是一种好办法
  • 我想先给米利斯下定义
  • 注意:这个类是
    Groovy
    类,用于Spock测试,但它很容易翻译成Java

    class FixedTicksClock extends Clock {
        private ZoneId systemDefault = ZoneId.systemDefault()
        private long[] ticks
        private int current = 0
    
        FixedTicksClock(long ... ticks) {
            this.ticks = ticks
        }
    
        @Override
        ZoneId getZone() {
            systemDefault
        }
    
        @Override
        Clock withZone(final ZoneId zone) {
            systemDefault = zone
            this
        }
    
        @Override
        Instant instant() {
            ofEpochMilli(getNextTick())
        }
    
        @Override
        long millis() {
            getNextTick()
        }
    
        private long getNextTick() {
            if (current >= ticks.length) {
                throw new IllegalStateException('No more ticks provided')
            } else {
                ticks[current++]
            }
        }
    }
    

    是的,我考虑过这个。但是,问题是通过构造函数传递时钟可以将其设置为单个固定时钟。我想我没有说清楚,但我需要能够在一次测试中将时钟设置为多个值,而无需构造新对象。@Mike请更新您的示例以明确您的用例。@Mike您应该能够将时钟模拟为参数传递(例如使用Mockito),而不是提供固定的时钟。就像这样,您可以在不同的调用中返回不同的值。@迈克:或者,您可以编写自己的可重用假时钟,而不是模拟时钟,具有您想要的任何行为,例如每次调用递增1秒。然而,在这一点上,您将取决于您在生产代码中对时钟的调用次数,这并不理想