Java 8 के java.time API में मॉकिंग टाइम


जवाबों:


72

निकटतम Clockवस्तु वस्तु है। आप किसी भी समय (या सिस्टम वर्तमान समय से) का उपयोग करके एक घड़ी ऑब्जेक्ट बना सकते हैं। सभी date.time ऑब्जेक्ट्स में ओवरलोडेड nowविधियाँ हैं जो वर्तमान समय के बजाय घड़ी ऑब्जेक्ट लेती हैं। तो आप एक विशिष्ट समय के साथ घड़ी को इंजेक्ट करने के लिए निर्भरता इंजेक्शन का उपयोग कर सकते हैं:

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

देखें घड़ी JavaDoc अधिक जानकारी के लिए


11
हाँ। विशेष रूप से, Clock.fixedपरीक्षण में उपयोगी है, जबकि Clock.systemया Clock.systemUTCआवेदन में इस्तेमाल किया जा सकता है।
मैट जॉनसन-पिंट

8
शर्म की बात है कि ऐसी कोई परिवर्तनशील घड़ी नहीं है जो मुझे इसे नॉन-टिकिंग टाइम पर सेट करने की अनुमति देती है, लेकिन बाद में उस समय को संशोधित करें (आप इसे जोडा के साथ कर सकते हैं)। यह समय-संवेदनशील कोड के परीक्षण के लिए उपयोगी होगा, उदाहरण के लिए समय-आधारित समाप्ति या भविष्य में घटनाओं को निर्धारित करने वाले वर्ग के साथ एक कैश।
बैकर

2
@bacar घड़ी सार वर्ग है, आप अपने खुद के परीक्षण घड़ी कार्यान्वयन बना सकते हैं
Bjarne Boström

मुझे विश्वास है कि हमने वही किया जो करना था।
बेकर

23

मैंने Clock.fixedसृजन को छिपाने और परीक्षणों को सरल बनाने के लिए एक नए वर्ग का उपयोग किया :

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
थ्रेड-सेफ्टी के बारे में क्या होगा यदि कई परीक्षण समानांतर में चलाए जाते हैं और TimeMachine घड़ी को बदलते हैं?
यानि

आपको घड़ी का परीक्षण किए गए ऑब्जेक्ट को पास करना होगा और समय से संबंधित तरीकों को कॉल करते समय इसका उपयोग करना होगा। और आप getClock()विधि को हटा सकते हैं और सीधे क्षेत्र का उपयोग कर सकते हैं। इस पद्धति में कोड की कुछ पंक्तियों के अलावा कुछ नहीं है।
बहरा

1
Banktime या TimeMachine?
इमानुएल

11

मैंने एक मैदान का इस्तेमाल किया

private Clock clock;

और फिर

LocalDate.now(clock);

मेरे उत्पादन कोड में। तब मैंने अपने यूनिट परीक्षणों में मॉकिटो का उपयोग क्लॉक का उपयोग करके किया था।

@Mock
private Clock clock;
private Clock fixedClock;

मजाक:

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

अभिकथन:

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

9

मैं Clockअपने उत्पादन कोड का उपयोग कर पाता हूं ।

आप अपने परीक्षण कोड में स्थैतिक विधि इनवोकेशन को मॉक करने के लिए JMockit या PowerMock का उपयोग कर सकते हैं । JMockit के साथ उदाहरण:

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

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

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

EDIT : जॉन स्केट के जवाब पर टिप्पणियों को पढ़ने के बाद एसओ पर एक समान प्रश्न पर मैं अपने पिछले स्वयं से असहमत हूं। जब आप स्थैतिक तरीकों का मजाक उड़ाते हैं तो कुछ और से अधिक तर्क ने मुझे आश्वस्त किया कि आप परीक्षणों को लंबित नहीं कर सकते।

यदि आपको विरासत कोड से निपटना है, तो भी आपको स्थैतिक मॉकिंग का उपयोग करना चाहिए / कर सकते हैं।


1
+1 "विरासत कोड के लिए स्थैतिक नकली का उपयोग कर" टिप्पणी के लिए। तो नए कोड के लिए, डिपेंडेंसी इंजेक्शन में दुबले होने और एक घड़ी को इंजेक्ट करने के लिए प्रोत्साहित किया जाना चाहिए (परीक्षणों के लिए निश्चित घड़ी, उत्पादन समय के लिए सिस्टम घड़ी)।
डेविड ग्रूमेस

1

मुझे LocalDateइसके बजाय उदाहरण की आवश्यकता है LocalDateTime
ऐसे कारण से मैंने उपयोगिता वर्ग का निर्माण किया:

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

और इसके लिए उपयोग निम्न है:

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

आउटपुट:

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

परियोजना में सभी निर्माण LocalDate.now()को बदल Clock.getCurrentDate()दिया।

क्योंकि यह स्प्रिंग बूट एप्लिकेशन है। testप्रोफ़ाइल निष्पादन से पहले , सभी परीक्षणों के लिए पूर्वनिर्धारित तिथि निर्धारित करें:

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

और करने के लिए जोड़ spring.factories :

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


1

यहाँ EasyMock के साथ जावा 8 वेब एप्लिकेशन में JUnit परीक्षण प्रयोजनों के लिए एक विशेष तिथि के लिए वर्तमान सिस्टम समय को ओवरराइड करने का एक तरीका है।

जोडा टाइम निश्चित रूप से अच्छा है (धन्यवाद स्टीफन, ब्रायन, आपने हमारी दुनिया को एक बेहतर जगह बना दिया है) लेकिन मुझे इसका उपयोग करने की अनुमति नहीं थी।

कुछ प्रयोग करने के बाद, मैं अंततः जावा 8 के जावा में एक विशेष तिथि के लिए समय का मजाक उड़ाने का एक तरीका लेकर आया।

  • बिना Joda Time API के
  • पॉवरमॉक के बिना।

यहाँ क्या किया जाना चाहिए:

परीक्षित वर्ग में क्या करने की आवश्यकता है

चरण 1

java.time.Clockपरीक्षण किए गए वर्ग में एक नई विशेषता जोड़ें MyServiceऔर सुनिश्चित करें कि तात्कालिकता ब्लॉक या एक कंस्ट्रक्टर के साथ नए मूल्यों को डिफ़ॉल्ट मानों पर ठीक से आरंभीकृत किया जाएगा:

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

चरण 2

नई विशेषता clockको उस पद्धति में इंजेक्ट करें जो वर्तमान तिथि-समय के लिए कहता है। उदाहरण के लिए, मेरे मामले में मुझे इस बात की जाँच करनी थी कि क्या डेटाबेस में संग्रहीत तारीख पहले हुई थी LocalDateTime.now(), जिसे मैंने प्रतिस्थापित किया 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();
    }
  }
  // (...) 
}

टेस्ट क्लास में क्या करने की जरूरत है

चरण 3

टेस्ट क्लास में, एक मॉक क्लॉक ऑब्जेक्ट बनाएं और परीक्षण किए गए तरीके को कॉल करने से पहले परीक्षण किए गए क्लास के इंस्टेंस में इंजेक्ट करें doExecute(), फिर इसे ठीक बाद में रीसेट करें, जैसे:

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

इसे डिबग मोड में जांचें और आप देखेंगे कि 2017 फरवरी की तारीख 3 फरवरी को सही तरीके से इंजेक्ट की गई है myServiceऔर तुलना निर्देश में उपयोग की गई है, और फिर इसके साथ वर्तमान तिथि को ठीक से रीसेट किया गया है initDefaultClock()


0

इस उदाहरण से यह भी पता चलता है कि इंस्टेंट और लोकल टाइम को कैसे संयोजित किया जाए ( रूपांतरण के साथ समस्याओं का विस्तृत विवरण )

परीक्षण के तहत एक वर्ग

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

}

एक ग्रूवी परीक्षण

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

स्प्रिंग बूट टेस्ट के लिए पावरमॉकिटो की मदद से आप मॉक मॉक कर सकते हैं ZonedDateTime । आपको निम्नलिखित की आवश्यकता है

एनोटेशन

परीक्षण वर्ग पर आपको उस सेवा को तैयार करने की आवश्यकता है जो इसका उपयोग करता है ZonedDateTime

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

परीक्षण का मामला

परीक्षण में आप एक वांछित समय तैयार कर सकते हैं, और इसे विधि कॉल के जवाब में प्राप्त कर सकते हैं।

  @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 
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.