使用模擬時間在Docker 中進行測試

在測試應用程序時,定義假系統(tǒng)時鐘以執(zhí)行使用日期和時間的代碼通常很有用。雖然總是可以直接更改系統(tǒng)時鐘,但許多人認為這種風格是不受歡迎的:

  • 它會影響計算機上運行的所有程序,而不僅僅是正在測試的應用程序
  • 反復更改系統(tǒng)時鐘可能既費時又麻煩

    您可以為您的應用定義一個假系統(tǒng)時鐘,而不是更改系統(tǒng)時鐘。 在生產(chǎn)中,假系統(tǒng)時鐘返回正常時間。 在測試過程中,偽造的系統(tǒng)時鐘會在您需要有效測試覆蓋時隨時返回。

為此,您需要定義各種不同的時鐘實現(xiàn),并能夠輕松交換它們。 許多人會選擇使用依賴注入工具,或者使用插件機制。
為此,您必須永遠不要直接引用默認系統(tǒng)時鐘和時區(qū),避免使用以下方法:

  • System.currentTimeMillis()
  • LocalDateTime.now() (或者類似的)
  • Date類的默認構(gòu)造函數(shù)(后者又使用System.currentTimeMillis())

這需要一些規(guī)則,因為許多代碼示例使用默認系統(tǒng)時鐘(和時區(qū)),并且因為調(diào)用上述方法已成為習慣。
假時鐘的可能行為包括:

  • 跳到未來
  • 回到過去
  • 使用固定日期和固定時間
  • 使用固定日期,但仍然讓時間變化
  • 每次看到時鐘時都會增加一秒鐘
  • 通過加速或減慢某個因素來改變時間的流逝率
  • 使用正常的系統(tǒng)時鐘而無需改動

根據(jù)您的需要,您可能必須在部分或全部這些地方使用假系統(tǒng)時鐘:

  • 應用代碼
  • 與數(shù)據(jù)庫交互的代碼
  • 日志輸出
  • 框架類

例子 for Java 8

java.time包的Clock類允許您創(chuàng)建一個假的系統(tǒng)時鐘。 它的固定方法可以讓您快速創(chuàng)建一個常見類型的假時鐘,它只是在給定時區(qū)內(nèi)返回一個固定值。 通常,您需要擴展抽象Clock類,并實現(xiàn)其抽象方法。

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Objects;

/**
 Increment by 1 second each time you look at the clock.
 Starts with the default system clock's instant and time-zone.

 Example output:
  2018-05-26T14:00:12.778Z
  2018-05-26T14:00:13.778Z
  2018-05-26T14:00:14.778Z
  2018-05-26T14:00:15.778Z
  2018-05-26T14:00:16.778Z

 @since Java 8.
*/
public final class ClockTicker extends Clock {

  /** Simple demo of the behaviour of this class. */
  public static void main(String... args) {
    ClockTicker ticker = new ClockTicker();
    log(ticker.instant());
    log(ticker.instant());
    log(ticker.instant());
    log(ticker.instant());
    log(ticker.instant());
  }
  private static void log(Object msg){
    System.out.println(Objects.toString(msg));
  }

  @Override public ZoneId getZone() {
    return DEFAULT_TZONE;
  }

  @Override public Clock withZone(ZoneId zone) {
    return Clock.fixed(WHEN_STARTED, zone);
  }

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

  //PRIVATE
  private final Instant WHEN_STARTED = Instant.now();
  private final ZoneId DEFAULT_TZONE = ZoneId.systemDefault();
  private long count = 0;

  private Instant nextInstant() {
    ++count;
    return WHEN_STARTED.plusSeconds(count);
  }
}

import java.time.Clock;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Objects;

/**
 Set the starting date-time and time-zone, but then
 let the time vary normally.

 Example output:
  2018-12-25T05:00:00Z
  Sleep for 5 seconds...
  2018-12-25T05:00:05.005Z
  Done.

 @since Java 8.
*/
public final class ClockTimeTravel extends Clock {

  /** Simple demo of the behaviour of this class. */
  public static void main(String[] args) throws InterruptedException {
    ClockTimeTravel timeTravel = new ClockTimeTravel(
      LocalDateTime.parse("2018-12-25T00:00:00"), ZoneOffset.of("-05:00")
    );
    log(timeTravel.instant());
    log("Sleep for 5 seconds...");
    Thread.currentThread().sleep(5000);
    log(timeTravel.instant());
    log("Done.");
  }

  private static void log(Object msg){
    System.out.println(Objects.toString(msg));
  }

  public ClockTimeTravel(LocalDateTime t0, ZoneOffset zoneOffset){
    this.zoneOffset = zoneOffset;
    this.t0LocalDateTime = t0;
    this.t0Instant = t0.toInstant(zoneOffset);
    this.whenObjectCreatedInstant = Instant.now();
  }

  @Override public ZoneId getZone() {
    return zoneOffset;
  }

  /** The caller needs to actually pass a ZoneOffset object here. */
  @Override public Clock withZone(ZoneId zone) {
    return new ClockTimeTravel(t0LocalDateTime, (ZoneOffset)zone);
  }

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

  //PRIVATE

  /** t0 is the moment this clock object was created in Java-land. */
  private final Instant t0Instant;
  private final LocalDateTime t0LocalDateTime;

  private final ZoneOffset zoneOffset;
  private final Instant whenObjectCreatedInstant;

  /**
   Figure out how much time has elapsed between the moment this
   object was created, and the moment when this method is being called.
   Then, apply that diff to t0.
  */
  private Instant nextInstant() {
    Instant now = Instant.now();
    long diff = now.toEpochMilli() - whenObjectCreatedInstant.toEpochMilli();
    return t0Instant.plusMillis(diff);
  }
}

例子 小于 Java8

The TimeSource interface allows you to define various implementations of a fake system clock:

public interface TimeSource {

  /** Return the system time. */  
  long currentTimeMillis();

}
This implementation mimics a system clock running one day in advance:
public final class TimeSrc implements TimeSource {

  /** One day in advance of the actual time.*/
  @Override public long currentTimeMillis() {
    return System.currentTimeMillis() + ONE_DAY;
  }

  private static final long ONE_DAY = 24*60*60*1000;

}

使用各種TimeSource實現(xiàn),您可以模擬系統(tǒng)時鐘的任何所需行為。
配置JDK記錄器以使用假系統(tǒng)時鐘很簡單。 一個簡單的自定義Formatter可以使用TimeSource來改變LogRecord的時間:

import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;

public final class SimpleFormatterTimeSource extends SimpleFormatter {

  @Override public String format(LogRecord aLogRecord) {
    aLogRecord.setMillis(fTimeSource.currentTimeMillis());
    return super.format(aLogRecord);
  }

  private TimeSource fTimeSource = BuildImpl.forTimeSource();
}

上面的文章機翻Use a fake system clock

Docker 中修改時間

Docker 是容器技術(shù),不同于虛擬化技術(shù)是獨立的系統(tǒng),Docker是通過NameSpace上NameSpace下CGroup來虛擬的系統(tǒng),可以參考上面的幾篇文章,可以讓你讓你了解為什么修改時間后,Docker會崩潰了(Docker 的時間其實是使用的宿主機時間)。我們一般測試的時候,需要將時間修改成指定的時間,所以只是修改時區(qū)的話,是滿足不了我們的要求的。

所以我們需要其他的解決方法。

解決方案是在容器中偽造它。 這個lib 攔截所有系統(tǒng)調(diào)用程序用于檢索當前時間和日期。
實施很容易。根據(jù)需要為Dockerfile添加功能:

cd WORKDIR /
git clone https://github.com/wolfcw/libfaketime.git
cd  /libfaketime/src
make install

請記住設(shè)置環(huán)境變量 LD_PRELOAD 在運行應用程序之前,您需要應用偽造的時間。

CMD ["/bin/sh", "-c", "LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 FAKETIME_NO_CACHE=1 python /srv/intercept/manage.py runserver 0.0.0.0:3000]
import os
def set_time(request):
    print(datetime.today())
    os.environ["FAKETIME"] = "2020-01-01"  # Note: time of type string must be in the format "YYYY-MM-DD hh:mm:ss" or "+15d"
    print(datetime.today())

上面的文章引用于 http://webmotociclismo.com/questions/277/ru-he-zai-dockerrong-qi-zhong-dong-tai-she-zhi-xi-tong-shi-jian

后面會再單獨寫一篇使用Dockerfile 的詳細示例。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容