Zamana duyarlı kodu test etmek için Java System.currentTimeMillis'i geçersiz kılın


129

System.currentTimeMillisAna makinede sistem saatini manuel olarak değiştirmek dışında, kod içinde veya JVM bağımsız değişkenleriyle sunulan geçerli saati geçersiz kılmanın bir yolu var mı ?

Biraz arka plan:

Mantıklarının çoğunu geçerli tarih etrafında döndüren bir dizi muhasebe işini yürüten bir sistemimiz var (yani ayın 1'i, yılın 1'i vb.)

Ne yazık ki, eski kodların çoğu, new Date()veya gibi işlevleri çağırır Calendar.getInstance(), bunların her ikisi de nihayetinde çağrılır System.currentTimeMillis.

Test amacıyla, şu anda, kodun testin yürütüldüğünü düşündüğü saat ve tarihi değiştirmek için sistem saatini manuel olarak güncellemek zorunda kaldık.

Yani sorum şu:

Tarafından döndürüleni geçersiz kılmanın bir yolu var mı? System.currentTimeMillis ? Örneğin, JVM'ye bu yöntemden dönmeden önce bazı ofsetlerin otomatik olarak eklenmesini veya çıkarılmasını söylemek için?

Şimdiden teşekkürler!


1
Artık alakalı olup olmadığını bilmiyorum, ancak AspectJ ile bunu başarmanın başka bir yöntemi var, cevabıma şu adresten bakın: stackoverflow.com/questions/18239859/…
Nándor Előd Fekete

@ NándorElődFekete bağlantıdaki çözüm ilginç, ancak kodu yeniden derlemeyi gerektiriyor. Eski kodla uğraştığını iddia ettiği gerçeği göz önüne alındığında, orijinal posterin yeniden derleme yeteneğine sahip olup olmadığını merak ediyorum.
cleberz

1
@cleberz AspectJ'nin güzel özelliklerinden biri, doğrudan bayt kodu üzerinde çalışmasıdır, bu nedenle çalışmak için orijinal kaynak koduna ihtiyaç duymaz.
Nándor Előd Fekete

@ NándorElődFekete ipucu için çok teşekkür ederim. En-boy-j bayt kodu seviyesinde enstrümantasyonun (özellikle JDK sınıfı enstrümantasyonunun) farkında değildim. Biraz zaman aldı, ancak ihtiyaçlarımı karşılamak için hem rt.jar'ın derleme zamanı dokumasına hem de jdk olmayan sınıfların yükleme zamanı dokumasına ihtiyacım olduğunu anlayabildim (sistemi geçersiz kılın. currentTimeMillis () ve System.nanoTime ()).
cleberz

@cleberz JRE derslerinde dokuma yapmak için başka bir cevabım var , siz de kontrol edebilirsiniz.
Nándor Előd Fekete

Yanıtlar:


115

Ben kuvvetle yerine sistem saati ile uğraşmaktansa mermi ve eski kodu değiştirilebilir bir saati kullanmak için o Refactor lokma önerilir. İdeal olarak bu bağımlılık enjeksiyonu ile yapılmalıdır, ancak değiştirilebilir bir singleton kullansanız bile test edilebilirlik kazanırsınız.

Bu, singleton sürümü için arama ve değiştirme ile neredeyse otomatik hale getirilebilir:

  • Değiştir Calendar.getInstance()ile Clock.getInstance().getCalendarInstance().
  • Değiştir new Date()ileClock.getInstance().newDate()
  • Değiştir System.currentTimeMillis()ileClock.getInstance().currentTimeMillis()

(vb. gerektiği gibi)

İlk adımı attıktan sonra, tek seferde biraz DI ile tekli değiştirebilirsiniz.


50
Test edilebilirliği artırmak için tüm potansiyel API'leri soyutlayarak veya paketleyerek kodunuzu karıştırmak IMHO çok iyi bir fikir değildir. Yeniden düzenleme işlemini basit bir arama ve değiştirme ile bir kez yapabilseniz bile, kodun okunması, anlaşılması ve bakımı çok daha zor hale gelir. Birkaç sahte çerçevenin System.currentTimeMillis'in davranışını değiştirebilmesi gerekir, eğer değilse, AOP veya kendi kendine araçsallaştırma kullanmak daha iyi bir seçimdir.
jarnbjo

21
@jarnbjo: Elbette, fikrinize hoş geldiniz - ama bu oldukça iyi bilinen bir IMO tekniği ve kesinlikle harika bir etki için kullandım. Yalnızca test edilebilirliği iyileştirmekle kalmaz, aynı zamanda sistem zamanına bağımlılığınızı da belirgin hale getirir, bu da koda genel bir bakış elde ederken faydalı olabilir.
Jon Skeet

4
@ virgo47: Sanırım katılmama konusunda anlaşmamız gerekecek. "Bağımlılıklarınızı açıkça belirtmenin" kodu kirlettiğini görmüyorum - bunu kodu daha net hale getirmenin doğal bir yolu olarak görüyorum. Statik çağrılar yoluyla bu bağımlılıkları gizlemeyi gereksiz yere "tamamen kabul edilebilir" olarak görmüyorum.
Jon Skeet

26
GÜNCELLEME Java 8'de yerleşik olarak bulunan yeni java.time paketi , java.time.Clock"gerektiğinde ve gerektiğinde takılabilecek alternatif saatlere izin veren" bir sınıf içerir .
Fesleğen Bourque

7
Bu cevap, DI'nin tamamen iyi olduğunu ima eder. Şahsen, gerçek dünyada onu iyi kullanan bir uygulamayı henüz görmedim. Bunun yerine, anlamsız ayrı arayüzler, durumsuz "nesneler", yalnızca veri "nesneler" ve düşük uyum sınıflarından oluşan bir bolluk görüyoruz. IMO, basitlik ve nesne yönelimli tasarım (gerçek, durum dolu nesnelerle) daha iyi bir seçimdir. staticYöntemlerle ilgili olarak , OO olmadıklarından emin olun, ancak örneği herhangi bir örnek durumunda çalışmayan durumsuz bir bağımlılık enjekte etmek gerçekten daha iyi değildir; bu sadece etkin bir şekilde "durağan" davranışı gizlemenin süslü bir yolu.
Rogério

78

tl; Dr.

Ana makinede sistem saatini manuel olarak değiştirmenin dışında, System.currentTimeMillis aracılığıyla sunulan geçerli saati geçersiz kılmanın kod içinde veya JVM bağımsız değişkenleriyle bir yolu var mı?

Evet.

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

Clock Java.time içinde

Sahte tarih-saat değerleriyle testi kolaylaştırmak için tak-çıkar saat değişimi sorununa yeni bir çözümümüz var . Java.time paket içinde Java 8 soyut bir sınıf içeren java.time.Clockbir hedefiyle,:

alternatif saatlerin gerektiğinde ve gerektiği zaman takılmasına izin vermek için

Kendi uygulamanızı takabilirsiniz Clock, ancak muhtemelen ihtiyaçlarınızı karşılamak için zaten yapılmış bir uygulama bulabilirsiniz. Size kolaylık sağlamak için, java.time özel uygulamalar sağlamak için statik yöntemler içerir. Bu alternatif uygulamalar, test sırasında değerli olabilir.

Kadans değişikliği

Çeşitli tick…yöntemler, geçerli anı farklı bir kadansla artıran saatler üretir.

Varsayılan , Java 8'de milisaniyeClock kadar sık ve Java 9'da nanosaniye kadar (donanımınıza bağlı olarak) güncellenen bir zamanı bildirir . Gerçek geçerli anın farklı bir ayrıntı düzeyinde bildirilmesini isteyebilirsiniz.

Yanlış saatler

Bazı saatler yalan söyleyebilir ve ana işletim sisteminin donanım saatinden farklı bir sonuç üretebilir.

  • fixed - Değişmeyen (artmayan) tek bir anı mevcut an olarak bildirir.
  • offset- Geçerli anı bildirir, ancak geçirilen Durationbağımsız değişken tarafından değiştirilir .

Örneğin, bu yıl en erken Noel'in ilk anını kilitleyin. başka bir deyişle, Noel Baba ve ren geyikleri ilk durduklarında . En erken saat dilimi bugünlerde gibi görünüyor Pacific/Kiritimatiat +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 );

Her zaman aynı ana dönmek için o özel sabit saati kullanın. Kiritimati'de Noel gününün ilk anını alıyoruz , UTC'nin duvar saati on dört saat önce, 24 Aralık'ın önceki günü saat 10: 00'da gösteriliyor.

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 [Pacific / Kiritimati]

IdeOne.com'da canlı koda bakın .

Gerçek zaman, farklı saat dilimi

ClockUygulama tarafından hangi saat diliminin atanacağını kontrol edebilirsiniz . Bu, bazı testlerde faydalı olabilir. Ancak bunu, isteğe bağlı ZoneIdveya ZoneOffsetbağımsız değişkenleri her zaman açıkça belirtmeniz gereken üretim kodunda önermiyorum .

UTC'nin varsayılan bölge olmasını belirtebilirsiniz.

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

Herhangi bir belirli saat dilimini belirtebilirsiniz. Bir belirtme uygun bir zaman dilimi adı biçiminde continent/regiongibi America/Montreal, Africa/Casablancaya da Pacific/Auckland. Hiçbir zaman gibi 3-4 harfli bir kısaltma kullanın ESTveya ISToldukları gibi değil , gerçek zaman dilimleri, değil standardize ve hatta benzersiz değil (!).

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

JVM'nin geçerli varsayılan saat diliminin belirli bir Clocknesne için varsayılan olması gerektiğini belirtebilirsiniz .

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

Karşılaştırmak için bu kodu çalıştırın. Hepsinin aynı anı, zaman çizelgesinde aynı noktayı bildirdiklerini unutmayın. Sadece duvar saati süresinde farklılık gösterirler ; başka bir deyişle, aynı şeyi söylemenin üç yolu, aynı anı göstermenin üç yolu.

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

America/Los_Angeles bu kodu çalıştıran bilgisayardaki JVM geçerli varsayılan bölgesiydi.

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 [Amerika / Los_Angeles]

InstantSınıf tanımı gereği UTC her zaman. Yani bölgeyle ilgili bu üç Clockkullanım tamamen aynı etkiye sahiptir.

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

Varsayılan saat

Varsayılan olarak kullanılan gerçekleme, tarafından Instant.nowdöndürülendir Clock.systemUTC(). Bu, a belirtmediğinizde kullanılan uygulamadır Clock. Java 9 kaynak kodunun ön sürümündeInstant.now kendiniz görün .

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

Varsayılan Clockiçin OffsetDateTime.nowve ZonedDateTime.nowolduğu Clock.systemDefaultZone(). Kaynak koduna bakın .

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

Varsayılan uygulamaların davranışı Java 8 ve Java 9 arasında değişti. Java 8'de, sınıfların çözünürlüğü nanosaniye depolayabilmesine rağmen, mevcut an yalnızca milisaniye cinsinden bir çözünürlükle yakalanır . Java 9, mevcut anı nanosaniye çözünürlükle yakalayabilen yeni bir uygulama getiriyor - elbette, bilgisayar donanım saatinizin kapasitesine bağlı olarak.


Hakkında java.time

Java.time çerçevesi daha sonra Java 8 ve yerleşiktir. Bu sınıflar zahmetli eski yerini mirası gibi tarih-saat sınıfları java.util.Date, Calendar&SimpleDateFormat .

Daha fazla bilgi edinmek için Oracle Eğitimi'ne bakın . Ve birçok örnek ve açıklama için Stack Overflow'da arama yapın. Spesifikasyon JSR 310 .

Şu anda bakım modunda olan Joda-Time projesi, java.time'a geçişi tavsiye ediyor sınıflarına .

Sen değiştirebilir java.time sizin veritabanı ile doğrudan nesneleri. JDBC 4.2 veya üstü ile uyumlu bir JDBC sürücüsü kullanın . Dizelere gerek yok, derslere gerek yok . Hibernate 5 ve JPA 2.2 java.time desteğijava.sql.* .

Java.time derslerini nereden edinebilirim?


doğru cevap bu
bharal

42

Jon Skeet'in söylediği gibi :

"Joda Zamanını kullanın", "java.util.Date/Calendar ile X'e nasıl ulaşabilirim?" sorusuna neredeyse her zaman en iyi yanıttır.

Yani buraya gelir (varsayarsak sadece tamamını değiştirdiyseniz senin new Date()ile 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();

Arayüze sahip bir kütüphaneyi içe aktarmak istiyorsanız (aşağıdaki Jon'un yorumuna bakın), standart arayüzün yanı sıra uygulamalar da sağlayacak olan Prevayler's Clock'ı kullanabilirsiniz . Dolu kavanoz sadece 96kB'dir, bu yüzden bankayı bozmamalı ...


13
Hayır, bunu tavsiye etmem - sizi gerçekten değiştirilebilir bir saate doğru götürmez; boyunca statiği kullanmaya devam etmenizi sağlar. Joda Time'ı kullanmak elbette iyi bir fikir, ancak yine de bir Saat arayüzü veya benzeriyle gitmek istiyorum.
Jon Skeet

@Jon: Doğru - test etmek için sorun değil, ancak bir arayüze sahip olmak daha iyi.
Stephen

5
@Jon: Zaten JodaTime kullanan zamana bağlı kodu test etmek istiyorsanız, bu pürüzsüz ve IMHO mükemmel ve kolay bir yoldur. JodaTime ile zaten her şey sizin için çözüldüyse, tekerleği yeniden başka bir soyutlama katmanı / çerçevesini tanıtarak yeniden keşfetmeye çalışmak, yapılacak yol değildir.
Stefan Haberl

2
@JonSkeet: Bu Mike'ın istediği şey değildi. Üretim kodunda tamamen değiştirilebilir bir saat değil, zamana bağlı kodu test etmek için bir araç istedi. Mimarimi gerektiği kadar karmaşık ama olabildiğince basit tutmayı tercih ederim. Burada ek bir soyutlama katmanı (yani arayüz) tanıtmak gerekli değildir
Stefan Haberl

2
@StefanHaberl: Kesinlikle "gerekli" olmayan pek çok şey var - ama gerçekten önerdiğim şey, zaten var olan bir bağımlılığı açık ve daha kolay test edilebilir hale getirmek . Sistem zamanını statik ile taklit etmek, tüm normal statik sorunlarına sahiptir - iyi bir neden olmaksızın küresel durumdur . Mike, geçerli tarih ve saati kullanan test edilebilir bir kod yazmanın bir yolunu istiyordu. Hala önerimin bir durağanı etkili bir şekilde doldurmaktan çok daha temiz olduğuna (ve bağımlılıkları daha net hale getirdiğine) inanıyorum.
Jon Skeet

15

Bazı DateFactory modellerini kullanmak güzel görünse de, kontrol edemediğiniz kitaplıkları kapsamaz - System.currentTimeMillis'e (böyle bir şey var) dayalı uygulama ile Doğrulama ek açıklamasını @ Geçmişte hayal edin.

Bu nedenle, sistem saatiyle doğrudan dalga geçmek için jmockit kullanıyoruz:

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);

Milisin orijinal unmocked değerine ulaşmak mümkün olmadığından, bunun yerine nano zamanlayıcı kullanıyoruz - bu duvar saatiyle ilgili değildir, ancak burada göreceli zaman yeterlidir:

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

HotSpot'ta, birkaç aramadan sonra sürenin normale döndüğü belgelenmiş bir sorun var - işte sorun raporu: http://code.google.com/p/jmockit/issues/detail?id=43

Bunun üstesinden gelmek için belirli bir HotSpot optimizasyonu açmalıyız - bu argümanla JVM'yi çalıştırın -XX:-Inline.

Bu, üretim için mükemmel olmasa da, testler için gayet uygundur ve uygulama için kesinlikle şeffaftır, özellikle DataFactory iş açısından mantıklı olmadığında ve sadece testler nedeniyle tanıtıldığında. Yerleşik JVM seçeneğinin farklı zamanlarda çalışması güzel olurdu, çok kötü, böyle hackler olmadan mümkün değil.

Hikayenin tamamı buradaki blog yazımda : http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/

Gönderide eksiksiz kullanışlı sınıf SystemTimeShifter sağlanmıştır. Sınıf, testlerinizde kullanılabilir veya uygulamanızı (hatta tüm uygulama sunucusunu) farklı bir zamanda çalıştırmak için gerçek ana sınıfınızdan çok kolay bir şekilde önce birinci ana sınıf olarak kullanılabilir. Elbette bu, üretim ortamı için değil, esas olarak test amaçlıdır.

DÜZENLEME Temmuz 2014: JMockit son zamanlarda çok değişti ve bunu doğru şekilde kullanmak için JMockit 1.0'ı kullanmak zorundasınız (IIRC). Arayüzün tamamen farklı olduğu en yeni sürüme kesinlikle yükseltilemez. Ben sadece gerekli olan şeyleri aktarmayı düşünüyordum ama yeni projelerimizde buna ihtiyacımız olmadığı için bunu hiç geliştirmiyorum.


1
Her sınıf için zamanı değiştiriyor olmanız her iki yöne de gider ... örneğin, currentTimeMillis yerine nanoTime alay ederseniz, java.util.concurrent'ı bozmamak için çok dikkatli olmanız gerekir. Genel bir kural olarak, bir 3. taraf kitaplığının testlerde kullanılması zorsa, kitaplığın girdileriyle dalga geçmemelisiniz: kitaplığın kendisiyle dalga geçmelisiniz.
Dan Berindei

1
Bu tekniği başka hiçbir şeyle alay etmediğimiz testler için kullanırız. Bu entegrasyon testidir ve yıl içinde ilk kez bazı aktiviteler gerçekleştiğinde olması gerekeni yapıp yapmadığını test ederiz. NanoTime hakkında iyi bir nokta, neyse ki duvar saati olmadığı için onunla dalga geçmemize gerek yok hiç anlamı yok. Zaten bir kereden fazla ihtiyaç duyduğumdan ve bu nedenle sistem zamanını karıştırmak çok şanssız olduğu için Java'yı zaman kaydırma özellikleriyle görmeyi çok isterim.
virgo47

7

Powermock harika çalışıyor. Sadece dalga geçmek için kullandım System.currentTimeMillis().


Powermock'u kullanmaya çalıştım ve eğer doğru anladıysam, gerçekten aranan tarafla dalga geçmezdi. Bunun yerine tüm arayanları bulur ve daha sonra taklidi uygular. Uygulamanızın değişen zamanda çalıştığından gerçekten emin olmak istiyorsanız, sınıf yolunuzdaki HER sınıfı kontrol etmesine izin vermeniz gerektiğinden, bu son derece zaman alıcı olabilir. Powermock ile alay etme şekli konusunda yanılıyorsam düzelt.
başak47

1
@ virgo47 Hayır, yanlış anladınız. PowerMock asla "sınıf yolunuzdaki her sınıfı kontrol etmez"; sadece sizin özellikle kontrol etmesini söylediğiniz sınıfları kontrol edecektir ( @PrepareForTesttest sınıfındaki açıklama yoluyla ).
Rogério

1
@ Rogério - tam olarak bunu anlıyorum - "Buna izin vermelisin ..." dedim. Yani, System.currentTimeMillissınıf yolunuzda (herhangi bir kitaplık) herhangi bir yere çağrı bekliyorsanız , her sınıfı kontrol etmeniz gerekir. Demek istediğim buydu, başka bir şey değil. Mesele şu ki, arayan tarafında bu davranışla dalga geçiyorsunuz ("özellikle söylüyorsunuz"). Bu, basit test için uygundur, ancak bu yöntemi nereden çağırdığından emin olamadığınız testler için geçerli değildir (örneğin, ilgili kütüphanelerle daha karmaşık bileşen testleri). Bu, Powermock'un yanlış olduğu anlamına gelmez, sadece bu tür bir test için kullanamayacağınız anlamına gelir.
virgo47

1
@ virgo47 Evet, ne demek istediğini anlıyorum. PowerMock'un sınıf yolundaki her sınıfı otomatik olarak incelemesi gerektiğini kastettiğinizi düşünmüştüm, ki bu apaçık saçma olurdu. Pratikte, yine de, birim testleri için genellikle hangi sınıfın (sınıfların) saati okuduğunu biliyoruz, bu yüzden onu belirtmek bir sorun değil; entegrasyon testleri için, aslında, PowerMock'taki tüm kullanım sınıflarının belirtilmiş olması gereksinimi bir sorun olabilir.
Rogério

6

Test senaryolarınızda ayarlayabileceğiniz önceden tanımlanmış bir değer döndürmek için System sınıfını örmek için Boyuta Dayalı Programlama (AOP, örneğin AspectJ) kullanın.

Ya çağrısına yönlendirmek için uygulama sınıfları örgü System.currentTimeMillis()veyanew Date() kendi başka yarar sınıfına.

Dokuma sistemi sınıfları (java.lang.*Ancak ) biraz daha zordur ve rt.jar için çevrimdışı dokuma yapmanız ve testleriniz için ayrı bir JDK / rt.jar kullanmanız gerekebilir.

İkili dokuma olarak adlandırılır ve ayrıca Sistem sınıflarının dokumasını gerçekleştirmek ve bununla ilgili bazı sorunları aşmak için özel araçlar da vardır (örneğin, sanal makinenin önyüklenmesi çalışmayabilir)


Bu elbette işe yarar, ancak bir alay aracı ile yapmak, bir AOP aracından çok daha kolaydır; ikinci tür araç, söz konusu amaç için çok geneldir.
Rogério

3

Bunu doğrudan sanal makinede yapmanın gerçekten bir yolu yoktur, ancak sistem saatini test makinesinde programlı olarak ayarlamak için bir şeyler yapabilirsiniz. Çoğu (tümü?) İşletim sisteminin bunu yapmak için komut satırı komutları vardır.


Örneğin, pencerelerde dateve timekomutları. Linux'ta datekomut.
Jeremy Raymond

Bunu AOP veya araçsallaştırma ile yapmak neden mümkün olmasın (orada yapıldı mı)?
jarnbjo

3

EasyMock ile, Joda Time olmadan ve PowerMock olmadan bir Java 8 web uygulamasında JUnit testi amaçları için geçerli sistem zamanını geçersiz kılmak için çalışan bir yöntem.

İşte yapmanız gerekenler:

Test edilen sınıfta yapılması gerekenler

Aşama 1

java.time.ClockTest edilen sınıfa yeni bir öznitelik ekleyin ve yeni özniteliğin MyServicebir örnekleme bloğu veya bir kurucu ile varsayılan değerlerde düzgün şekilde başlatıldığından emin olun:

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

Adım 2

Yeni özniteliği clockgeçerli bir tarih-saati çağıran yönteme enjekte edin . Örneğin, benim durumumda LocalDateTime.now(), veri tabanında depolanan bir tarihin daha önce olup olmadığını kontrol etmek zorunda kaldım, bunun yerine LocalDateTime.now(clock)şu şekilde değiştirdim:

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

Test sınıfında yapılması gerekenler

Aşama 3

Test sınıfında, bir sahte saat nesnesi oluşturun ve test edilen yöntemi çağırmadan hemen önce onu test edilen sınıfın örneğine enjekte edin doExecute(), ardından hemen ardından aşağıdaki gibi sıfırlayın:

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

Hata ayıklama modunda kontrol edin ve 3 Şubat 2017 tarihinin myServiceörneğe doğru şekilde enjekte edildiğini ve karşılaştırma talimatında kullanıldığını ve ardından ile geçerli tarihe uygun şekilde sıfırlandığını göreceksiniz initDefaultClock().


2

Bence sadece invaziv olmayan bir çözüm işe yarayabilir. Özellikle harici kütüphanelere ve büyük bir eski kod tabanına sahipseniz, zamanı alay etmenin güvenilir bir yolu yoktur.

JMockit ... kısıtlı sayıda çalışır kez

PowerMock & Co ... istemcileri System.currentTimeMillis () ile alay etmelidir . Yine istilacı bir seçenek.

Bundan sadece belirtilen bkz javaagent veya aop yaklaşımının tüm sisteme şeffaf olduğunu . Bunu yapan ve böyle bir çözüme işaret eden var mı?

@jarnbjo: javaagent kodunun bir kısmını gösterir misiniz lütfen?


3
-XX: -Inline hack (Sun's HotSpot'ta ) ile sınırsız sayıda çalışan jmockit çözümü: virgo47.wordpress.com/2012/06/22/changing-system-time-in-java Komple kullanışlı sınıf SystemTimeShifter sağlanır gönderide. Sınıf, testlerinizde kullanılabilir veya uygulamanızı (hatta tüm uygulama sunucusunu) farklı bir zamanda çalıştırmak için gerçek ana sınıfınızdan çok kolay bir şekilde önce birinci ana sınıf olarak kullanılabilir. Elbette bu, üretim ortamı için değil, esas olarak test amaçlıdır.
virgo47

2

Linux çalıştırıyorsanız, libfaketime'ın ana dalını veya commit 4ce2835'i test ederken kullanabilirsiniz .

Basitçe ortam değişkenini java uygulamanızla alay etmek istediğiniz zamanla ayarlayın ve ld-preloading kullanarak çalıştırın:

# 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

İkinci ortam değişkeni, aksi takdirde donacak olan java uygulamaları için çok önemlidir. Yazma sırasında libfaketime'nin ana dalını gerektirir.

Sistem tarafından yönetilen bir hizmetin saatini değiştirmek isterseniz, aşağıdakileri birim dosyası geçersiz kılmalarınıza eklemeniz yeterlidir, örneğin, elasticsearch için bu /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"

Systemctl daemon-reload kullanarak systemd'yi yeniden yüklemeyi unutmayın


-1

System.currentTimeMillis()Argüman içeren yöntemle alay etmek istiyorsanız anyLong(), Matchers sınıfını argüman olarak geçirebilirsiniz .

Not: Yukarıdaki hileyi kullanarak test durumumu başarıyla çalıştırabiliyorum ve sadece testim hakkında PowerMock ve Mockito çerçevelerini kullandığım hakkında daha fazla ayrıntı paylaşabiliyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.