Java 8'in java.time API'sinde alay süresi


Yanıtlar:


73

En yakın şey Clocknesnedir. İstediğiniz herhangi bir zamanı kullanarak (veya Sistemin geçerli saatinden) bir Saat nesnesi oluşturabilirsiniz. Tüm date.time nesneleri now, geçerli saat yerine bir saat nesnesi alan aşırı yüklenmiş yöntemlere sahiptir. Böylece, bir Saati belirli bir saatle enjekte etmek için bağımlılık enjeksiyonunu kullanabilirsiniz:

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

Daha fazla ayrıntı için Clock JavaDoc'a bakın


11
Evet. Özellikle, uygulamada kullanılırken veya kullanılabilirken Clock.fixedtestte faydalıdır . Clock.systemClock.systemUTC
Matt Johnson-Pint

8
Utanç verici olmayan bir zamana ayarlamama izin veren değiştirilebilir bir saatin olmaması ne yazık ki, bu saati daha sonra değiştirebilir (bunu joda ile yapabilirsiniz). Bu, zamana duyarlı kodun test edilmesi için yararlı olacaktır, örneğin, zamana dayalı sona erme tarihlerine sahip bir önbellek veya gelecekteki olayları planlayan bir sınıf.
bacar

2
@bacar Clock soyut bir sınıftır, kendi test Clock uygulamanızı oluşturabilirsiniz
Bjarne Boström

Sonunda yaptığımız şeyin bu olduğuna inanıyorum.
bacar

25

Clock.fixedYaratılışı gizlemek ve testleri basitleştirmek için yeni bir sınıf kullandım :

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
Paralel olarak birkaç test yapılırsa ve TimeMachine saatini değiştirirse iş parçacığı güvenliği ne olur?
youri

Saati test edilen nesneye geçirmeli ve zamanla ilgili yöntemleri çağırırken kullanmalısınız. Ve getClock()yöntemi kaldırıp alanı doğrudan kullanabilirsiniz. Bu yöntem birkaç satır koddan başka bir şey eklememektedir.
deamon

1
Banktime mı yoksa TimeMachine mi?
Emanuele

11

Bir alan kullandım

private Clock clock;

ve sonra

LocalDate.now(clock);

üretim kodumda. Daha sonra Saat ile alay etmek için birim testlerimde Mockito kullandım Clock.fixed ():

@Mock
private Clock clock;
private Clock fixedClock;

Alaycı:

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

İddia:

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

9

ClockÜretim kodunuzu dağınıklık kullanarak buluyorum .

Test kodunuzdaki statik yöntem çağrılarıyla alay etmek için JMockit veya PowerMock'u kullanabilirsiniz . JMockit ile örnek:

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

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

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

DÜZENLEME : Jon Skeet'in SO'daki benzer bir soruya verdiği yanıta ilişkin yorumları okuduktan sonra geçmiş benliğime katılmıyorum. Her şeyden çok, argüman beni statik yöntemlerle dalga geçtiğinizde testleri paralize edemeyeceğiniz konusunda ikna etti.

Yine de eski kodla uğraşmak zorunda kalırsanız statik alaycı kullanabilirsiniz / kullanmalısınız.


1
"Eski kod için statik alay kullanma" yorumu için +1. Bu nedenle, yeni kod için, Bağımlılık Enjeksiyonuna eğilmeye ve bir Saat enjekte etmeye teşvik edilmelidir (testler için sabit Saat, üretim çalışma süresi için sistem Saati).
David Groomes

1

Bunun LocalDateyerine örneğe ihtiyacım var LocalDateTime.
Bu nedenle aşağıdaki yardımcı sınıfını yarattım:

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

Ve bunun için kullanım şöyle:

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

Çıktı:

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

Tüm oluşturulmasını Değiştirilen LocalDate.now()için Clock.getCurrentDate()projede.

Çünkü yaylı önyükleme uygulamasıdır. testProfil yürütmeden önce tüm testler için önceden tanımlanmış bir tarih belirleyin:

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

Ve bahar fabrikalarına ekleyin :

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


1

İşte EasyMock ile bir Java 8 web uygulamasında JUnit testi amaçları için geçerli sistem saatini belirli bir tarihe geçersiz kılmanın çalışma yolu

Joda Time kesinlikle güzel (teşekkürler Stephen, Brian, dünyamızı daha iyi bir yer haline getirdin) ama onu kullanmama izin verilmedi.

Biraz deney yaptıktan sonra, sonunda Java 8'in java.time API'sinde EasyMock ile belirli bir tarihe zaman alay etmenin bir yolunu buldum.

  • Joda Time API olmadan
  • PowerMock olmadan.

İşte yapılması 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, veritabanında depolanan bir tarihin daha önce olup olmadığını kontrol etmek zorunda kaldım ve LocalDateTime.now()bunu şöyle değiştirdim LocalDateTime.now(clock):

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 sıfırlayın, şöyle:

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

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


0

Bu örnek, Instant ve LocalTime'ın nasıl birleştirileceğini bile gösterir ( dönüştürmeyle ilgili sorunların ayrıntılı açıklaması )

Test edilen bir sınıf

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

}

Harika bir test

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

Bir yay önyükleme testi için PowerMockito'nun yardımıyla ZonedDateTime. Aşağıdakilere ihtiyacınız var.

Ek açıklamalar

Test sınıfının üzerinde hizmeti hazırlamanız gerekir kullanır ZonedDateTime.

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

Test durumu

Testte istediğiniz zamanı hazırlayabilir ve yöntem çağrısına yanıt olarak alabilirsiniz.

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