Mengganti Java System.currentTimeMillis untuk menguji kode sensitif waktu

129

Apakah ada cara, baik dalam kode atau dengan argumen JVM, untuk mengesampingkan waktu saat ini, sebagaimana disajikan melalui System.currentTimeMillis, selain secara manual mengubah jam sistem pada mesin host?

Sedikit latar belakang:

Kami memiliki sistem yang menjalankan sejumlah pekerjaan akuntansi yang memutar banyak logika mereka di sekitar tanggal saat ini (yaitu tanggal 1 bulan, tanggal 1 tahun, dll)

Sayangnya, banyak kode lama memanggil fungsi seperti new Date()atau Calendar.getInstance(), yang keduanya akhirnya dihubungi System.currentTimeMillis.

Untuk tujuan pengujian, saat ini, kami terjebak dengan memperbarui jam sistem secara manual untuk memanipulasi waktu dan tanggal kode berpikir bahwa pengujian sedang dijalankan.

Jadi pertanyaan saya adalah:

Apakah ada cara untuk menimpa apa yang dikembalikan oleh System.currentTimeMillis? Misalnya, untuk memberi tahu JVM untuk secara otomatis menambah atau mengurangi beberapa offset sebelum kembali dari metode itu?

Terima kasih sebelumnya!

Mike Clark
sumber
1
Saya tidak tahu apakah itu relevan lagi, tetapi ada metode lain untuk mencapai hal ini dengan AspectJ, lihat jawaban saya di: stackoverflow.com/questions/18239859/…
Nándor Előd Fekete
@ NándorElődFekete solusi dalam tautan itu menarik, namun itu membutuhkan kompilasi ulang kode. Saya bertanya-tanya apakah poster asli memiliki kemampuan untuk mengkompilasi ulang, mengingat fakta bahwa ia mengaku berurusan dengan kode warisan.
cleberz
1
@cleberz Salah satu properti bagus dari AspectJ adalah yang beroperasi pada bytecode secara langsung, sehingga tidak memerlukan kode sumber asli untuk bekerja.
Nándor Előd Fekete
@ NándorElődFekete terima kasih banyak atas petunjuknya. Saya tidak mengetahui instrumentasi level-bytecodeJ (khususnya instrumentasi kelas JDK). Butuh waktu agak lama, tetapi saya bisa mengetahui bahwa saya harus menenun waktu kompilasi rt.jar dan juga menenun waktu kelas non-jdk agar sesuai dengan kebutuhan saya (override System. currentTimeMillis () dan System.nanoTime ()).
cleberz
@ cleberz Saya punya jawaban lain untuk menenun melalui kelas JRE , Anda dapat memeriksanya juga.
Nándor Előd Fekete

Jawaban:

115

Saya sangat merekomendasikan bahwa alih-alih mengacaukan jam sistem, Anda menggigit peluru dan refactor kode warisan untuk menggunakan jam diganti. Idealnya itu harus dilakukan dengan injeksi ketergantungan, tetapi bahkan jika Anda menggunakan singleton yang dapat diganti Anda akan mendapatkan testabilitas.

Ini hampir dapat diotomatisasi dengan pencarian dan ganti untuk versi tunggal:

  • Ganti Calendar.getInstance()dengan Clock.getInstance().getCalendarInstance().
  • Ganti new Date()denganClock.getInstance().newDate()
  • Ganti System.currentTimeMillis()denganClock.getInstance().currentTimeMillis()

(dll sesuai kebutuhan)

Setelah Anda mengambil langkah pertama itu, Anda dapat mengganti singleton dengan DI sedikit demi sedikit.

Jon Skeet
sumber
50
Mengacaukan kode Anda dengan abstrak atau membungkus semua API potensial untuk meningkatkan testabilitas adalah IMHO bukan ide yang sangat bagus. Bahkan jika Anda dapat melakukan refactoring satu kali dengan pencarian & penggantian yang sederhana, kode tersebut menjadi lebih sulit untuk dibaca, dipahami, dan dikelola. Beberapa kerangka kerja tiruan seharusnya dapat memodifikasi perilaku System.currentTimeMillis, jika tidak, menggunakan AOP atau instrumentasi buatan sendiri adalah pilihan yang lebih baik.
jarnbjo
21
@jarnbjo: Ya, Anda tentu saja setuju dengan pendapat Anda - tetapi ini adalah teknik IMO yang cukup mapan, dan saya sudah pasti menggunakannya dengan sangat baik. Tidak hanya meningkatkan testabilitas - itu juga membuat ketergantungan Anda pada waktu sistem menjadi eksplisit, yang dapat berguna saat mendapatkan gambaran umum kode.
Jon Skeet
4
@ virgo47: Saya pikir kita harus setuju untuk tidak setuju. Saya tidak melihat "menyatakan dependensi Anda secara eksplisit" sebagai mencemari kode - saya melihatnya sebagai cara alami untuk membuat kode lebih jelas. Saya juga tidak melihat menyembunyikan ketergantungan itu melalui panggilan statis yang tidak perlu sebagai "dapat diterima".
Jon Skeet
26
UPDATE baru paket java.time dibangun ke Jawa 8 termasuk java.time.Clockkelas "untuk memungkinkan jam alternatif untuk dipasang sebagai dan bila diperlukan".
Basil Bourque
7
Jawaban ini menyiratkan bahwa DI semuanya baik. Secara pribadi, saya belum melihat aplikasi di dunia nyata yang memanfaatkannya. Sebagai gantinya, kita melihat banyaknya antarmuka terpisah tanpa titik, "objek" stateless, "objek" data-saja, dan kelas kohesi rendah. IMO, kesederhanaan dan desain berorientasi objek (dengan objek statefull yang benar) adalah pilihan yang lebih baik. Mengenai staticmetode, tentu saja itu bukan OO, tetapi menyuntikkan dependensi stateless yang instansinya tidak beroperasi pada keadaan instance apa pun tidak benar-benar lebih baik; itu hanya cara mewah untuk menyamarkan apa yang sebenarnya merupakan perilaku "statis".
Rogério
78

tl; dr

Apakah ada cara, baik dalam kode atau dengan argumen JVM, untuk mengesampingkan waktu saat ini, seperti yang disajikan melalui System.currentTimeMillis, selain secara manual mengubah jam sistem pada mesin host?

Iya.

Instant.now( 
    Clock.fixed( 
        Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
    )
)

Clock Di java.time

Kami memiliki solusi baru untuk masalah penggantian jam pluggable untuk memfasilitasi pengujian dengan nilai waktu-tanggal palsu . The paket java.time di Jawa 8 meliputi kelas abstrak java.time.Clock, dengan tujuan yang jelas:

untuk memungkinkan jam alternatif dicolokkan sebagai dan ketika diperlukan

Anda dapat memasang implementasi Anda sendiri Clock, meskipun Anda mungkin dapat menemukan satu sudah dibuat untuk memenuhi kebutuhan Anda. Untuk kenyamanan Anda, java.time termasuk metode statis untuk menghasilkan implementasi khusus. Implementasi alternatif ini dapat bermanfaat selama pengujian.

Irama diubah

Berbagai tick…metode menghasilkan jam yang menambah momen saat ini dengan irama yang berbeda.

Default Clockmelaporkan waktu yang diperbarui sesering milidetik di Java 8 dan di Jawa 9 sebagus nanodetik (tergantung pada perangkat keras Anda). Anda dapat meminta momen aktual saat ini untuk dilaporkan dengan rincian yang berbeda.

Jam palsu

Beberapa jam bisa berbohong, menghasilkan hasil yang berbeda dari jam hardware OS host.

  • fixed - Melaporkan momen tunggal yang tidak berubah (tidak bertambah) sebagai momen saat ini.
  • offset- Melaporkan momen saat ini tetapi digeser oleh Durationargumen yang diteruskan .

Misalnya, kunci pada saat pertama Natal paling awal tahun ini. dengan kata lain, ketika Santa dan rusa kutubnya berhenti pertama kali . Zona waktu paling awal saat ini tampaknya berada Pacific/Kiritimatidi +14:00.

LocalDate ld = LocalDate.now( ZoneId.of( "America/Montreal" ) );
LocalDate xmasThisYear = MonthDay.of( Month.DECEMBER , 25 ).atYear( ld.getYear() );
ZoneId earliestXmasZone = ZoneId.of( "Pacific/Kiritimati" ) ;
ZonedDateTime zdtEarliestXmasThisYear = xmasThisYear.atStartOfDay( earliestXmasZone );
Instant instantEarliestXmasThisYear = zdtEarliestXmasThisYear.toInstant();
Clock clockEarliestXmasThisYear = Clock.fixed( instantEarliestXmasThisYear , earliestXmasZone );

Gunakan jam tetap khusus itu untuk selalu mengembalikan momen yang sama. Kami mendapatkan momen pertama hari Natal di Kiritimati , dengan UTC menunjukkan waktu jam dinding empat belas jam sebelumnya, jam 10 pagi pada tanggal 24 Desember sebelumnya.

Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );

instant.toString (): 2016-12-24T10: 00: 00Z

zdt.toString (): 2016-12-25T00: 00 + 14: 00 [Pasifik / Kiritimati]

Lihat kode langsung di IdeOne.com .

Waktu sebenarnya, zona waktu berbeda

Anda dapat mengontrol zona waktu mana yang ditetapkan oleh Clockimplementasi. Ini mungkin berguna dalam beberapa pengujian. Tapi saya tidak merekomendasikan ini dalam kode produksi, di mana Anda harus selalu menentukan secara eksplisit opsi ZoneIdatau ZoneOffsetargumen.

Anda dapat menentukan UTC sebagai zona default.

ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );

Anda dapat menentukan zona waktu tertentu. Tentukan nama zona waktu yang tepat dalam format continent/region, seperti America/Montreal, Africa/Casablanca, atau Pacific/Auckland. Jangan pernah menggunakan singkatan 3-4 huruf seperti ESTatau ISTkarena mereka bukan zona waktu yang sebenarnya, tidak terstandarisasi, dan bahkan tidak unik (!).

ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );

Anda dapat menentukan zona waktu default JVM saat ini harus menjadi default untuk Clockobjek tertentu .

ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );

Jalankan kode ini untuk membandingkan. Perhatikan bahwa mereka semua melaporkan momen yang sama, titik yang sama pada timeline. Mereka hanya berbeda dalam waktu jam dinding ; dengan kata lain, tiga cara untuk mengatakan hal yang sama, tiga cara untuk menampilkan momen yang sama.

System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );

America/Los_Angeles adalah zona default JVM saat ini di komputer yang menjalankan kode ini.

zdtClockSystemUTC.toString (): 2016-12-31T20: 52: 39.688Z

zdtClockSystem.toString (): 2016-12-31T15: 52: 39.750-05: 00 [Amerika / Montreal]

zdtClockSystemDefaultZone.toString (): 2016-12-31T12: 52: 39.762-08: 00 [America / Los_Angeles]

The Instantkelas selalu dalam UTC dengan definisi. Jadi ketiga Clockpenggunaan terkait zona ini memiliki efek yang persis sama.

Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );

instantClockSystemUTC.toString (): 2016-12-31T20: 52: 39.763Z

instantClockSystem.toString (): 2016-12-31T20: 52: 39.763Z

instantClockSystemDefaultZone.toString (): 2016-12-31T20: 52: 39.763Z

Jam default

Implementasi yang digunakan secara default Instant.nowadalah implementasi yang dikembalikan oleh Clock.systemUTC(). Ini adalah implementasi yang digunakan ketika Anda tidak menentukan Clock. Lihat sendiri di kode sumber Java 9 pra-rilis untukInstant.now .

public static Instant now() {
    return Clock.systemUTC().instant();
}

Default Clockuntuk OffsetDateTime.nowdan ZonedDateTime.nowadalah Clock.systemDefaultZone(). Lihat kode sumber .

public static ZonedDateTime now() {
    return now(Clock.systemDefaultZone());
}

Perilaku implementasi standar berubah antara Java 8 dan Java 9. Di Java 8, momen saat ini ditangkap dengan resolusi hanya dalam milidetik meskipun kemampuan kelas untuk menyimpan resolusi nanodetik . Java 9 menghadirkan implementasi baru yang mampu mengabadikan momen saat ini dengan resolusi nanodetik - tentu saja tergantung pada kemampuan jam perangkat keras komputer Anda.


Tentang java.time

The java.time kerangka dibangun ke Jawa 8 dan kemudian. Kelas-kelas ini menggantikan tua merepotkan warisan kelas tanggal-waktu seperti java.util.Date, Calendar, & SimpleDateFormat.

Untuk mempelajari lebih lanjut, lihat Tutorial Oracle . Dan cari Stack Overflow untuk banyak contoh dan penjelasan. Spesifikasi adalah JSR 310 .

Proyek Joda-Time , sekarang dalam mode pemeliharaan , menyarankan migrasi ke kelas java.time .

Anda dapat bertukar objek java.time secara langsung dengan database Anda. Gunakan driver JDBC yang sesuai dengan JDBC 4.2 atau yang lebih baru. Tidak perlu untuk string, tidak perlu untuk java.sql.*kelas. Hibernate 5 & JPA 2.2 mendukung java.time .

Di mana mendapatkan kelas java.time?

Basil Bourque
sumber
ini jawaban yang tepat
bharal
42

Seperti yang dikatakan oleh Jon Skeet :

"Gunakan Joda Time" hampir selalu merupakan jawaban terbaik untuk pertanyaan apa pun yang melibatkan "bagaimana saya mencapai X dengan java.util.Date/Calendar?"

Jadi begini (menganggap Anda baru saja diganti semua Anda new Date()dengan new DateTime().toDate())

//Change to specific time
DateTimeUtils.setCurrentMillisFixed(millis);
//or set the clock to be a difference from system time
DateTimeUtils.setCurrentMillisOffset(millis);
//Reset to system time
DateTimeUtils.setCurrentMillisSystem();

Jika Anda ingin mengimpor perpustakaan yang memiliki antarmuka (lihat komentar Jon di bawah ini), Anda bisa menggunakan Jam Prevayler , yang akan menyediakan implementasi serta antarmuka standar. Stoples penuh hanya 96kB, sehingga tidak boleh merusak bank ...

Stephen
sumber
13
Tidak, saya tidak akan merekomendasikan ini - ini tidak menggerakkan Anda ke arah jam yang benar-benar dapat diganti; itu membuat Anda menggunakan statika di seluruh. Menggunakan Joda Time tentu saja merupakan ide yang bagus, tetapi saya masih ingin menggunakan antarmuka Jam atau sejenisnya.
Jon Skeet
@ Jon: Benar - untuk pengujian, tidak apa-apa, tetapi lebih baik memiliki antarmuka.
Stephen
5
@ Jon: Ini adalah cara sempurna dan mudah IMHO dan halus jika Anda ingin menguji kode tergantung waktu yang sudah menggunakan JodaTime. Mencoba menemukan kembali roda dengan memperkenalkan lapisan / kerangka abstraksi lain bukanlah cara yang tepat, jika semuanya sudah terpecahkan untuk Anda dengan JodaTime.
Stefan Haberl
2
@ JonSkeet: Ini bukan yang diminta Mike awalnya. Dia ingin alat untuk menguji kode waktu tergantung dan bukan jam diganti penuh dalam kode produksi. Saya lebih suka menjaga arsitektur saya serumit yang diperlukan tetapi sesederhana mungkin. Memperkenalkan lapisan abstraksi tambahan (yaitu antarmuka) di sini sama sekali tidak perlu
Stefan Haberl
2
@StefanHaberl: Ada banyak hal yang tidak sepenuhnya "diperlukan" - tetapi sebenarnya yang saya sarankan adalah membuat ketergantungan yang sudah ada secara eksplisit dan lebih mudah diuji secara terpisah. Memalsukan waktu sistem dengan statika memiliki semua masalah statika yang normal - ini keadaan global tanpa alasan yang kuat . Mike meminta cara untuk menulis kode yang dapat diuji yang menggunakan tanggal dan waktu saat ini. Saya masih percaya saran saya jauh lebih bersih (dan membuat ketergantungan lebih jelas) daripada secara efektif memalsukan statis.
Jon Skeet
15

Meskipun menggunakan beberapa pola DateFactory tampak bagus, itu tidak mencakup perpustakaan yang tidak dapat Anda kendalikan - bayangkan anotasi validasi @Past dengan implementasi yang mengandalkan System.currentTimeMillis (ada yang seperti itu).

Itu sebabnya kami menggunakan jmockit untuk mengejek waktu sistem secara langsung:

import mockit.Mock;
import mockit.MockClass;
...
@MockClass(realClass = System.class)
public static class SystemMock {
    /**
     * Fake current time millis returns value modified by required offset.
     *
     * @return fake "current" millis
     */
    @Mock
    public static long currentTimeMillis() {
        return INIT_MILLIS + offset + millisSinceClassInit();
    }
}

Mockit.setUpMock(SystemMock.class);

Karena tidak mungkin untuk mendapatkan nilai milis asli yang tidak terblokir, kami menggunakan penghitung nano - ini tidak terkait dengan jam dinding, tetapi waktu relatif sudah mencukupi di sini:

// runs before the mock is applied
private static final long INIT_MILLIS = System.currentTimeMillis();
private static final long INIT_NANOS = System.nanoTime();

private static long millisSinceClassInit() {
    return (System.nanoTime() - INIT_NANOS) / 1000000;
}

Ada masalah yang terdokumentasi, bahwa dengan HotSpot waktu kembali normal setelah beberapa panggilan - ini adalah laporan masalah: http://code.google.com/p/jmockit/issues/detail?id=43

Untuk mengatasinya kita harus mengaktifkan satu optimasi HotSpot khusus - jalankan JVM dengan argumen ini -XX:-Inline.

Walaupun ini mungkin tidak sempurna untuk produksi, itu baik untuk tes dan sangat transparan untuk aplikasi, terutama ketika DataFactory tidak masuk akal bisnis dan diperkenalkan hanya karena tes. Akan menyenangkan untuk memiliki opsi JVM bawaan untuk berjalan di waktu yang berbeda, sayang sekali tidak mungkin tanpa peretasan seperti ini.

Kisah lengkap ada di posting blog saya di sini: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/

Kelas lengkap SystemTimeShifter yang praktis disediakan di pos. Kelas dapat digunakan dalam tes Anda, atau dapat digunakan sebagai kelas utama pertama sebelum kelas utama Anda yang sebenarnya dengan sangat mudah untuk menjalankan aplikasi Anda (atau bahkan seluruh server aplikasi) dalam waktu yang berbeda. Tentu saja, ini dimaksudkan untuk tujuan pengujian terutama, bukan untuk lingkungan produksi.

EDIT Juli 2014: JMockit banyak berubah belakangan ini dan Anda pasti akan menggunakan JMockit 1.0 untuk menggunakannya dengan benar (IIRC). Jelas tidak dapat memutakhirkan ke versi terbaru di mana antarmuka sama sekali berbeda. Saya sedang berpikir tentang menguraikan hanya hal-hal yang diperlukan, tetapi karena kita tidak memerlukan hal ini dalam proyek baru kita, saya tidak mengembangkan hal ini sama sekali.

virgo47
sumber
1
Fakta bahwa Anda mengubah waktu untuk setiap kelas berjalan dua arah ... misalnya jika Anda akan mengejek nanoTime, bukan currentTimeMillis, Anda harus sangat berhati-hati untuk tidak merusak java.util.concurrent. Sebagai aturan praktis, jika perpustakaan pihak ketiga sulit digunakan dalam tes, Anda tidak boleh mengejek input perpustakaan: Anda harus mengejek perpustakaan itu sendiri.
Dan Berindei
1
Kami menggunakan teknik ini untuk tes di mana kami tidak mengejek apa pun. Ini adalah tes integrasi dan kami menguji seluruh tumpukan apakah itu melakukan apa yang seharusnya ketika beberapa aktivitas terjadi pertama kali dalam setahun, dll. Poin bagus tentang nanoTime, untungnya kita tidak perlu mengejeknya karena tidak memiliki jam dinding artinya sama sekali. Saya ingin melihat Java dengan fitur pengalihan waktu karena saya sudah membutuhkannya lebih dari sekali dan mengacaukan waktu sistem karena ini sangat sial.
virgo47
7

Powermock bekerja dengan sangat baik. Hanya menggunakannya untuk mengejek System.currentTimeMillis().

Rene
sumber
Saya mencoba menggunakan Powermock, dan jika saya memahaminya dengan benar, itu tidak akan benar-benar mengejek sisi lain. Sebaliknya ia menemukan semua penelepon dan daripada menerapkan tiruan itu. Ini mungkin sangat memakan waktu karena Anda harus membiarkannya memeriksa SETIAP kelas di classpath Anda jika Anda ingin benar-benar yakin aplikasi Anda bekerja dalam waktu yang bergeser. Perbaiki saya jika saya salah tentang cara mengejek Powermock.
virgo47
1
@ virgo47 Tidak, Anda salah mengerti. PowerMock tidak akan pernah "memeriksa setiap kelas di classpath Anda"; itu hanya akan memeriksa kelas yang secara khusus Anda perintahkan untuk memeriksanya (melalui @PrepareForTestanotasi pada kelas tes).
Rogério
1
@ Rogério - persis seperti itulah saya memahaminya - saya berkata, "Anda harus membiarkannya ...". Itu adalah jika Anda mengharapkan panggilan ke System.currentTimeMillismana saja di jalur kelas Anda (lib apa pun) Anda harus memeriksa setiap kelas. Itu yang saya maksud, tidak ada yang lain. Intinya adalah bahwa Anda mengejek perilaku itu di sisi pemanggil ("Anda secara khusus memberi tahu"). Ini OK untuk tes sederhana tetapi tidak untuk tes di mana Anda tidak dapat memastikan apa yang memanggil metode itu dari mana (mis. Tes komponen yang lebih kompleks dengan perpustakaan yang terlibat). Itu tidak berarti Powermock salah sama sekali, itu hanya berarti bahwa Anda tidak dapat menggunakannya untuk jenis tes ini.
virgo47
1
@ virgo47 Ya, saya mengerti maksud Anda. Saya pikir Anda maksudkan bahwa PowerMock harus secara otomatis memeriksa setiap kelas di classpath, yang jelas tidak masuk akal. Namun, dalam praktiknya, untuk unit test kita biasanya tahu kelas mana yang membaca waktu, jadi bukan masalah untuk menentukannya; untuk tes integrasi, memang, persyaratan di PowerMock untuk memiliki semua kelas yang ditentukan mungkin menjadi masalah.
Rogério
6

Gunakan Pemrograman Berorientasi Aspek (AOP, misalnya AspectJ) untuk menenun kelas Sistem untuk mengembalikan nilai yang telah ditentukan yang dapat Anda tetapkan dalam kasus uji Anda.

Atau menenun kelas aplikasi untuk mengalihkan panggilan ke System.currentTimeMillis()atau ke new Date()kelas utilitas Anda sendiri.

Kelas sistem tenun ( java.lang.*) sedikit lebih rumit dan Anda mungkin perlu melakukan tenun offline untuk rt.jar dan menggunakan JDK / rt.jar yang terpisah untuk pengujian Anda.

Ini disebut Biner tenun dan ada juga alat khusus untuk melakukan tenun kelas Sistem dan menghindari beberapa masalah dengan itu (misalnya bootstrap, VM mungkin tidak bekerja)

mhaller
sumber
Ini berfungsi, tentu saja, tetapi jauh lebih mudah dilakukan dengan alat mengejek daripada dengan alat AOP; jenis alat yang terakhir terlalu umum untuk tujuan yang dimaksud.
Rogério
3

Sebenarnya tidak ada cara untuk melakukan ini secara langsung di VM, tetapi Anda dapat melakukan sesuatu untuk secara terprogram mengatur waktu sistem pada mesin uji. Sebagian besar (semua?) OS memiliki perintah baris perintah untuk melakukan ini.

Jeremy Raymond
sumber
Misalnya, pada windows datedan timeperintah. Di Linux dateperintahnya.
Jeremy Raymond
Mengapa tidak mungkin melakukan ini dengan AOP atau instrumentisasi (sudah ada yang melakukannya)?
jarnbjo
3

Cara yang bekerja untuk mengganti waktu sistem saat ini untuk keperluan pengujian JUnit dalam aplikasi web Java 8 dengan EasyMock, tanpa Joda Time, dan tanpa PowerMock.

Inilah yang perlu Anda lakukan:

Apa yang perlu dilakukan di kelas yang diuji

Langkah 1

Tambahkan java.time.Clockatribut baru ke kelas yang diuji MyServicedan pastikan atribut baru akan diinisialisasi dengan benar pada nilai default dengan blok instantiation atau konstruktor:

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
  }
  // (...)
}

Langkah 2

Suntikkan atribut baru clockke dalam metode yang membutuhkan waktu tanggal saat ini. Sebagai contoh, dalam kasus saya, saya harus melakukan pemeriksaan apakah tanggal yang disimpan dalam dataase terjadi sebelumnya LocalDateTime.now(), yang saya remplaced dengan LocalDateTime.now(clock), seperti:

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();
    }
  }
  // (...) 
}

Apa yang perlu dilakukan di kelas ujian

Langkah 3

Di kelas tes, buat objek jam tiruan dan suntikkan ke instance kelas yang diuji tepat sebelum Anda memanggil metode yang diuji doExecute(), lalu setel ulang kembali setelahnya, seperti:

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

public class MyServiceTest {
  // (...)
  private int year = 2017;
  private int month = 2;
  private int day = 3;

  @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(..)
    }
  }

Periksa dalam mode debug dan Anda akan melihat tanggal 2017 3 Februari telah disuntikkan dengan benar ke dalam myServiceinstance dan digunakan dalam instruksi perbandingan, dan kemudian telah diatur ulang dengan benar ke tanggal saat ini dengan initDefaultClock().

KiriSakow
sumber
2

Menurut pendapat saya hanya solusi yang tidak invasif yang dapat bekerja. Terutama jika Anda memiliki lib eksternal dan basis kode legacy besar tidak ada cara yang dapat diandalkan untuk mengejek waktu.

JMockit ... hanya berfungsi untuk jumlah terbatas kali

PowerMock & Co ... perlu mengejek klien ke System.currentTimeMillis (). Lagi-lagi opsi invasif.

Dari ini saya hanya melihat disebutkan javaagent atau aop pendekatan yang transparan untuk seluruh sistem. Adakah yang melakukan itu dan dapat menunjukkan solusi seperti itu?

@jarnbjo: bisakah Anda menunjukkan beberapa kode javaagent?

pengguna1477398
sumber
3
solusi jmockit bekerja untuk jumlah yang tidak terbatas dengan sedikit -XX: -Inline hack (di Sun's HotSpot): virgo47.wordpress.com/2012/06/22/changing-system-time-in-java Kelas praktis lengkap SystemTimeShifter disediakan di pos. Kelas dapat digunakan dalam tes Anda, atau dapat digunakan sebagai kelas utama pertama sebelum kelas utama Anda yang sebenarnya dengan mudah untuk menjalankan aplikasi Anda (atau bahkan seluruh server aplikasi) dalam waktu yang berbeda. Tentu saja, ini dimaksudkan untuk tujuan pengujian terutama, bukan untuk lingkungan produksi.
virgo47
2

Jika Anda menjalankan Linux, Anda dapat menggunakan cabang utama libfaketime, atau pada saat pengujian komit 4ce2835 .

Cukup setel variabel lingkungan dengan waktu Anda ingin mengejek aplikasi java Anda, dan jalankan dengan menggunakan ld-preloading:

# bash
export FAKETIME="1985-10-26 01:21:00"
export DONT_FAKE_MONOTONIC=1
LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1 java -jar myapp.jar

Variabel lingkungan kedua adalah yang terpenting untuk aplikasi java, yang sebaliknya akan membeku. Ini membutuhkan cabang utama libfaketime pada saat penulisan.

Jika Anda ingin mengubah waktu layanan terkelola systemd, tambahkan saja yang berikut ini ke file unit override Anda, mis. Untuk elasticsearch, ini akan menjadi /etc/systemd/system/elasticsearch.service.d/override.conf:

[Service]
Environment="FAKETIME=2017-10-31 23:00:00"
Environment="DONT_FAKE_MONOTONIC=1"
Environment="LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1"

Jangan lupa memuat ulang systemd menggunakan `systemctl daemon-reload

faxmodem
sumber
-1

Jika Anda ingin mengolok-olok metode yang memiliki System.currentTimeMillis()argumen maka Anda dapat melewati anyLong()kelas Matchers sebagai argumen.

PS Saya dapat menjalankan test case saya dengan sukses menggunakan trik di atas dan hanya untuk berbagi rincian lebih lanjut tentang pengujian saya bahwa saya menggunakan kerangka PowerMock dan Mockito.

Paresh Mayani
sumber