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!
sumber
Jawaban:
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:
Calendar.getInstance()
denganClock.getInstance().getCalendarInstance()
.new Date()
denganClock.getInstance().newDate()
System.currentTimeMillis()
denganClock.getInstance().currentTimeMillis()
(dll sesuai kebutuhan)
Setelah Anda mengambil langkah pertama itu, Anda dapat mengganti singleton dengan DI sedikit demi sedikit.
sumber
java.time.Clock
kelas "untuk memungkinkan jam alternatif untuk dipasang sebagai dan bila diperlukan".static
metode, 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".tl; dr
Iya.
Clock
Di java.timeKami 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: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
Clock
melaporkan 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.tickSeconds
- Peningkatan dalam seluruh detiktickMinutes
- Penambahan dalam menit penuhtick
- Penambahan olehDuration
argumen yang diteruskan .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 olehDuration
argumen 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/Kiritimati
di+14:00
.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.
Lihat kode langsung di IdeOne.com .
Waktu sebenarnya, zona waktu berbeda
Anda dapat mengontrol zona waktu mana yang ditetapkan oleh
Clock
implementasi. Ini mungkin berguna dalam beberapa pengujian. Tapi saya tidak merekomendasikan ini dalam kode produksi, di mana Anda harus selalu menentukan secara eksplisit opsiZoneId
atauZoneOffset
argumen.Anda dapat menentukan UTC sebagai zona default.
Anda dapat menentukan zona waktu tertentu. Tentukan nama zona waktu yang tepat dalam format
continent/region
, sepertiAmerica/Montreal
,Africa/Casablanca
, atauPacific/Auckland
. Jangan pernah menggunakan singkatan 3-4 huruf sepertiEST
atauIST
karena mereka bukan zona waktu yang sebenarnya, tidak terstandarisasi, dan bahkan tidak unik (!).Anda dapat menentukan zona waktu default JVM saat ini harus menjadi default untuk
Clock
objek tertentu .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.
America/Los_Angeles
adalah zona default JVM saat ini di komputer yang menjalankan kode ini.The
Instant
kelas selalu dalam UTC dengan definisi. Jadi ketigaClock
penggunaan terkait zona ini memiliki efek yang persis sama.Jam default
Implementasi yang digunakan secara default
Instant.now
adalah implementasi yang dikembalikan olehClock.systemUTC()
. Ini adalah implementasi yang digunakan ketika Anda tidak menentukanClock
. Lihat sendiri di kode sumber Java 9 pra-rilis untukInstant.now
.Default
Clock
untukOffsetDateTime.now
danZonedDateTime.now
adalahClock.systemDefaultZone()
. Lihat kode sumber .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?
sumber
Seperti yang dikatakan oleh Jon Skeet :
Jadi begini (menganggap Anda baru saja diganti semua Anda
new Date()
dengannew DateTime().toDate()
)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 ...
sumber
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:
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:
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.
sumber
Powermock bekerja dengan sangat baik. Hanya menggunakannya untuk mengejek
System.currentTimeMillis()
.sumber
@PrepareForTest
anotasi pada kelas tes).System.currentTimeMillis
mana 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.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 kenew 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)
sumber
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.
sumber
date
dantime
perintah. Di Linuxdate
perintahnya.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.Clock
atribut baru ke kelas yang diujiMyService
dan pastikan atribut baru akan diinisialisasi dengan benar pada nilai default dengan blok instantiation atau konstruktor:Langkah 2
Suntikkan atribut baru
clock
ke dalam metode yang membutuhkan waktu tanggal saat ini. Sebagai contoh, dalam kasus saya, saya harus melakukan pemeriksaan apakah tanggal yang disimpan dalam dataase terjadi sebelumnyaLocalDateTime.now()
, yang saya remplaced denganLocalDateTime.now(clock)
, seperti: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:Periksa dalam mode debug dan Anda akan melihat tanggal 2017 3 Februari telah disuntikkan dengan benar ke dalam
myService
instance dan digunakan dalam instruksi perbandingan, dan kemudian telah diatur ulang dengan benar ke tanggal saat ini denganinitDefaultClock()
.sumber
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?
sumber
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:
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
:Jangan lupa memuat ulang systemd menggunakan `systemctl daemon-reload
sumber
Jika Anda ingin mengolok-olok metode yang memiliki
System.currentTimeMillis()
argumen maka Anda dapat melewatianyLong()
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.
sumber