Thời gian chế nhạo trong API java.time của Java 8


Câu trả lời:


72

Cái gần nhất là Clockđồ vật. Bạn có thể tạo đối tượng Đồng hồ bằng cách sử dụng bất kỳ lúc nào bạn muốn (hoặc từ thời gian hiện tại của Hệ thống). Tất cả các đối tượng date.time đều có nowcác phương thức nạp chồng lấy một đối tượng đồng hồ thay thế cho thời gian hiện tại. Vì vậy, bạn có thể sử dụng phương pháp tiêm phụ thuộc để tiêm Đồng hồ với thời gian cụ thể:

public class MyBean {
    private Clock clock;  // dependency inject
    ...
    public void process(LocalDate eventDate) {
      if (eventDate.isBefore(LocalDate.now(clock)) {
        ...
      }
    }
  }

Xem Clock JavaDoc để biết thêm chi tiết


11
Đúng. Đặc biệt, Clock.fixedrất hữu ích trong thử nghiệm, trong khi Clock.systemhoặc Clock.systemUTCcó thể được sử dụng trong ứng dụng.
Matt Johnson-Pint,

8
Thật xấu hổ vì không có đồng hồ có thể thay đổi cho phép tôi đặt nó thành thời gian không tích tắc, nhưng hãy sửa đổi thời gian đó sau (bạn có thể làm điều này với joda). Điều này sẽ hữu ích cho việc kiểm tra mã nhạy cảm với thời gian, ví dụ như một bộ nhớ cache với các thời gian hết hạn dựa trên thời gian hoặc một lớp lên lịch các sự kiện trong tương lai.
bacar

2
@bacar Clock là lớp trừu tượng, bạn có thể tạo riêng thực hiện kiểm tra đồng hồ của bạn
Bjarne Bostrom

Tôi tin rằng đó là những gì chúng tôi đã làm.
bacar

23

Tôi đã sử dụng một lớp mới để ẩn việc Clock.fixedtạo và đơn giản hóa các bài kiểm tra:

public class TimeMachine {

    private static Clock clock = Clock.systemDefaultZone();
    private static ZoneId zoneId = ZoneId.systemDefault();

    public static LocalDateTime now() {
        return LocalDateTime.now(getClock());
    }

    public static void useFixedClockAt(LocalDateTime date){
        clock = Clock.fixed(date.atZone(zoneId).toInstant(), zoneId);
    }

    public static void useSystemDefaultZoneClock(){
        clock = Clock.systemDefaultZone();
    }

    private static Clock getClock() {
        return clock ;
    }
}
public class MyClass {

    public void doSomethingWithTime() {
        LocalDateTime now = TimeMachine.now();
        ...
    }
}
@Test
public void test() {
    LocalDateTime twoWeeksAgo = LocalDateTime.now().minusWeeks(2);

    MyClass myClass = new MyClass();

    TimeMachine.useFixedClockAt(twoWeeksAgo);
    myClass.doSomethingWithTime();

    TimeMachine.useSystemDefaultZoneClock();
    myClass.doSomethingWithTime();

    ...
}

4
Điều gì về an toàn luồng nếu một số thử nghiệm được chạy song song và thay đổi đồng hồ TimeMachine?
youri

Bạn phải chuyển đồng hồ cho đối tượng được kiểm tra và sử dụng nó khi gọi các phương thức liên quan đến thời gian. Và bạn có thể loại bỏ getClock()phương thức và sử dụng trường trực tiếp. Phương pháp này không thêm gì ngoài một vài dòng mã.
phó tế

1
Banktime hay TimeMachine?
Emanuele

11

Tôi đã sử dụng một lĩnh vực

private Clock clock;

và sau đó

LocalDate.now(clock);

trong mã sản xuất của tôi. Sau đó, tôi sử dụng Mockito trong các bài kiểm tra đơn vị của mình để giả lập Đồng hồ bằng Clock.fixed ():

@Mock
private Clock clock;
private Clock fixedClock;

Chế giễu:

fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();

Quả quyết:

assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));

9

Tôi thấy việc sử dụng Clockmã sản xuất của bạn làm lộn xộn.

Bạn có thể sử dụng JMockit hoặc PowerMock để giả lập các lệnh gọi phương thức tĩnh trong mã thử nghiệm của mình. Ví dụ với JMockit:

@Test
public void testSth() {
  LocalDate today = LocalDate.of(2000, 6, 1);

  new Expectations(LocalDate.class) {{
      LocalDate.now(); result = today;
  }};

  Assert.assertEquals(LocalDate.now(), today);
}

CHỈNH SỬA : Sau khi đọc các bình luận về câu trả lời của Jon Skeet cho một câu hỏi tương tự ở đây trên VẬY, tôi không đồng ý với quá khứ của mình. Hơn bất cứ điều gì khác, lập luận thuyết phục tôi rằng bạn không thể làm song song các bài kiểm tra khi bạn mô phỏng các phương thức tĩnh.

Tuy nhiên, bạn có thể / vẫn phải sử dụng tính năng mô phỏng tĩnh nếu bạn phải xử lý mã kế thừa.


1
+1 cho nhận xét "sử dụng chế độ giả tĩnh cho mã kế thừa". Vì vậy, đối với mã mới, được khuyến khích dựa vào Dependency Injection và đưa vào Clock (Đồng hồ cố định cho các thử nghiệm, Đồng hồ hệ thống cho thời gian chạy sản xuất).
David Groomes

1

Tôi cần LocalDateví dụ thay vì LocalDateTime.
Với lý do như vậy, tôi đã tạo lớp tiện ích sau:

public final class Clock {
    private static long time;

    private Clock() {
    }

    public static void setCurrentDate(LocalDate date) {
        Clock.time = date.toEpochDay();
    }

    public static LocalDate getCurrentDate() {
        return LocalDate.ofEpochDay(getDateMillis());
    }

    public static void resetDate() {
        Clock.time = 0;
    }

    private static long getDateMillis() {
        return (time == 0 ? LocalDate.now().toEpochDay() : time);
    }
}

Và cách sử dụng nó giống như:

class ClockDemo {
    public static void main(String[] args) {
        System.out.println(Clock.getCurrentDate());

        Clock.setCurrentDate(LocalDate.of(1998, 12, 12));
        System.out.println(Clock.getCurrentDate());

        Clock.resetDate();
        System.out.println(Clock.getCurrentDate());
    }
}

Đầu ra:

2019-01-03
1998-12-12
2019-01-03

Thay thế mọi tạo vật LocalDate.now()đến Clock.getCurrentDate()trong dự án.

Bởi vì nó là ứng dụng khởi động mùa xuân . Trước khi testthực hiện hồ sơ, chỉ cần đặt một ngày được xác định trước cho tất cả các thử nghiệm:

public class TestProfileConfigurer implements ApplicationListener<ApplicationPreparedEvent> {
    private static final LocalDate TEST_DATE_MOCK = LocalDate.of(...);

    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
        if (environment.acceptsProfiles(Profiles.of("test"))) {
            Clock.setCurrentDate(TEST_DATE_MOCK);
        }
    }
}

Và thêm vào spring.factories :

org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer


1

Đây là một cách làm việc để ghi đè thời gian hệ thống hiện tại thành một ngày cụ thể cho mục đích kiểm tra JUnit trong ứng dụng web Java 8 với EasyMock

Joda Time chắc chắn rất hay (cảm ơn Stephen, Brian, bạn đã làm cho thế giới của chúng ta trở nên tốt đẹp hơn) nhưng tôi không được phép sử dụng nó.

Sau một số thử nghiệm, cuối cùng tôi đã nghĩ ra một cách để mô phỏng thời gian đến một ngày cụ thể trong API java.time của Java 8 với EasyMock

  • Không có API thời gian Joda
  • Không có PowerMock.

Đây là những gì cần phải làm:

Những việc cần làm trong lớp học đã kiểm tra

Bước 1

Thêm java.time.Clockthuộc tính mới vào lớp đã thử nghiệm MyServicevà đảm bảo thuộc tính mới sẽ được khởi tạo đúng cách ở các giá trị mặc định bằng khối khởi tạo hoặc phương thức khởi tạo:

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  private Clock clock;
  public Clock getClock() { return clock; }
  public void setClock(Clock newClock) { clock = newClock; }

  public void initDefaultClock() {
    setClock(
      Clock.system(
        Clock.systemDefaultZone().getZone() 
        // You can just as well use
        // java.util.TimeZone.getDefault().toZoneId() instead
      )
    );
  }
  { initDefaultClock(); } // initialisation in an instantiation block, but 
                          // it can be done in a constructor just as well
  // (...)
}

Bước 2

Đưa thuộc tính mới clockvào phương thức gọi ngày-giờ hiện tại. Ví dụ: trong trường hợp của tôi, tôi phải thực hiện kiểm tra xem ngày được lưu trữ trong cơ sở dữ liệu có xảy ra trước LocalDateTime.now()đó hay không, ngày mà tôi đã thay thế bằng LocalDateTime.now(clock), như sau:

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  protected void doExecute() {
    LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
    while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
      someOtherLogic();
    }
  }
  // (...) 
}

Những việc cần làm trong lớp thử nghiệm

Bước 3

Trong lớp thử nghiệm, hãy tạo một đối tượng đồng hồ giả và đưa nó vào cá thể của lớp được thử nghiệm ngay trước khi bạn gọi phương thức được thử nghiệm doExecute(), sau đó đặt lại nó ngay sau đó, như sau:

import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;

public class MyServiceTest {
  // (...)
  private int year = 2017;  // Be this a specific 
  private int month = 2;    // date we need 
  private int day = 3;      // to simulate.

  @Test
  public void doExecuteTest() throws Exception {
    // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot
 
    MyService myService = new MyService();
    Clock mockClock =
      Clock.fixed(
        LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
        Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
      );
    myService.setClock(mockClock); // set it before calling the tested method
 
    myService.doExecute(); // calling tested method 

    myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method

    // (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
    }
  }

Kiểm tra nó ở chế độ gỡ lỗi và bạn sẽ thấy ngày 3 tháng 2 năm 2017 đã được nhập chính xác vào myServiceví dụ và được sử dụng trong hướng dẫn so sánh, và sau đó đã được đặt lại đúng cách về ngày hiện tại với initDefaultClock().


0

Ví dụ này thậm chí còn cho thấy cách kết hợp Instant và LocalTime ( giải thích chi tiết về các vấn đề với chuyển đổi )

Một lớp học đang được kiểm tra

import java.time.Clock;
import java.time.LocalTime;

public class TimeMachine {

    private LocalTime from = LocalTime.MIDNIGHT;

    private LocalTime until = LocalTime.of(6, 0);

    private Clock clock = Clock.systemDefaultZone();

    public boolean isInInterval() {

        LocalTime now = LocalTime.now(clock);

        return now.isAfter(from) && now.isBefore(until);
    }

}

Một bài kiểm tra Groovy

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

import java.time.Clock
import java.time.Instant

import static java.time.ZoneOffset.UTC
import static org.junit.runners.Parameterized.Parameters

@RunWith(Parameterized)
class TimeMachineTest {

    @Parameters(name = "{0} - {2}")
    static data() {
        [
            ["01:22:00", true,  "in interval"],
            ["23:59:59", false, "before"],
            ["06:01:00", false, "after"],
        ]*.toArray()
    }

    String time
    boolean expected

    TimeMachineTest(String time, boolean expected, String testName) {
        this.time = time
        this.expected = expected
    }

    @Test
    void test() {
        TimeMachine timeMachine = new TimeMachine()
        timeMachine.clock = Clock.fixed(Instant.parse("2010-01-01T${time}Z"), UTC)
        def result = timeMachine.isInInterval()
        assert result == expected
    }

}

0

Với sự trợ giúp của PowerMockito cho bài kiểm tra khởi động mùa xuân, bạn có thể thử ZonedDateTime. Bạn cần những thứ sau đây.

Chú thích

Trong lớp thử nghiệm, bạn cần chuẩn bị dịch vụ sử dụng ZonedDateTime.

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
  @Autowired
  private EscalationService escalationService;
  //...
}

Trường hợp thử nghiệm

Trong thử nghiệm, bạn có thể chuẩn bị một thời gian mong muốn và lấy nó để phản hồi lại lời gọi phương thức.

  @Test
  public void escalateOnMondayAt14() throws Exception {
    ZonedDateTime preparedTime = ZonedDateTime.now();
    preparedTime = preparedTime.with(DayOfWeek.MONDAY);
    preparedTime = preparedTime.withHour(14);
    PowerMockito.mockStatic(ZonedDateTime.class);
    PowerMockito.when(ZonedDateTime.now(ArgumentMatchers.any(ZoneId.class))).thenReturn(preparedTime);
    // ... Assertions 
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.