अतुल्यकालिक प्रक्रियाओं का परीक्षण करने के लिए JUnit का उपयोग कैसे करें


204

आप जुइनिट के साथ अतुल्यकालिक प्रक्रियाओं को आग लगाने वाले तरीकों का परीक्षण कैसे करते हैं?

मैं नहीं जानता कि कैसे अपने परीक्षण को समाप्त करने की प्रक्रिया का इंतजार करना है (यह वास्तव में एक इकाई परीक्षण नहीं है, यह एकीकरण परीक्षण की तरह अधिक है क्योंकि इसमें कई कक्षाएं शामिल हैं और सिर्फ एक नहीं है)।


आप जाट (जावा अतुल्यकालिक परीक्षण) की कोशिश कर सकते हैं: bitbucket.org/csolar/jat
cs0lar

2
JAT में 1 चौकीदार है और इसे 1.5 वर्षों में अपडेट नहीं किया गया है। Awaitility सिर्फ 1 महीने पहले अपडेट किया गया था और इस लेखन के समय संस्करण 1.6 पर है। मैं किसी भी परियोजना से जुड़ा नहीं हूं, लेकिन अगर मैं अपनी परियोजना के अतिरिक्त निवेश करने जा रहा हूं, तो मैं इस समय और अधिक विश्वसनीयता प्रदान करूंगा।
लेस हेजलवुड

जाट के पास अभी भी कोई अपडेट नहीं है: "अंतिम अपडेट 2013-01-19"। लिंक को फॉलो करने के लिए समय बचाएं।
डेमोन

@LesHazlewood, एक चौकीदार जाट के लिए बुरा है, लेकिन वर्षों से कोई अपडेट नहीं है ... बस एक उदाहरण। यदि आप बस काम करते हैं, तो आप अपने ओएस के निम्न-स्तर के टीसीपी स्टैक को कितनी बार अपडेट करते हैं? जाट को वैकल्पिक नीचे उत्तर दिया जाता है stackoverflow.com/questions/631598/...
user1742529

जवाबों:


46

IMHO यह खराब अभ्यास है कि यूनिट टेस्ट हों या थ्रेड्स पर प्रतीक्षा करें, आदि आप इन परीक्षणों को स्प्लिट सेकंड में चलाना चाहेंगे। यही कारण है कि मैं async प्रक्रियाओं के परीक्षण के लिए 2-चरण दृष्टिकोण का प्रस्ताव करना चाहता हूं।

  1. परीक्षण करें कि आपकी async प्रक्रिया ठीक से सबमिट की गई है। आप उस वस्तु का मज़ाक उड़ा सकते हैं जो आपके एसिंक्स अनुरोधों को स्वीकार करती है और यह सुनिश्चित करती है कि प्रस्तुत नौकरी में सही गुण हैं, आदि।
  2. परीक्षण करें कि आपके async कॉलबैक सही काम कर रहे हैं। यहां आप मूल रूप से सबमिट की गई नौकरी का मजाक उड़ा सकते हैं और यह मान सकते हैं कि इसे ठीक से शुरू किया गया है और सत्यापित करें कि आपके कॉलबैक सही हैं।

148
ज़रूर। लेकिन कभी-कभी आपको कोड का परीक्षण करने की आवश्यकता होती है जो विशेष रूप से थ्रेड्स को प्रबंधित करने के लिए माना जाता है।
कमी

77
हममें से जो एकीकरण परीक्षण (और न केवल इकाई परीक्षण), या उपयोगकर्ता स्वीकृति परीक्षण (जैसे w / ककड़ी) करने के लिए जूनिट या टेस्टएनजी का उपयोग करते हैं, एक async पूरा होने की प्रतीक्षा कर रहे हैं और परिणाम की पुष्टि करना नितांत आवश्यक है।
लेस हेजलवुड

38
अतुल्यकालिक प्रक्रियाएं सही होने के लिए कुछ सबसे जटिल कोड हैं और आप कहते हैं कि आपको उनके लिए यूनिट परीक्षण का उपयोग नहीं करना चाहिए और केवल एक ही धागे के साथ परीक्षण करना चाहिए? यह बहुत बुरा विचार है।
चार्ल्स

18
मॉक टेस्ट अक्सर यह साबित करने में विफल रहते हैं कि कार्यक्षमता अंत तक काम करती है। Async कार्यक्षमता को काम करने के लिए एक अतुल्यकालिक तरीके से जांचने की आवश्यकता है। यदि आप चाहें तो इसे एकीकरण परीक्षण कहें, लेकिन यह एक ऐसी परीक्षा है जिसकी अभी भी आवश्यकता है।
स्कॉट बोरिंग

4
यह स्वीकृत उत्तर नहीं होना चाहिए। परीक्षण इकाई परीक्षण से परे जाता है। ओपी इसे यूनिट टेस्ट की तुलना में अधिक एकीकरण परीक्षण कहता है।
यिर्मयाह एडम्स

190

एक विकल्प काउंटडाउनचैच क्लास का उपयोग करना है ।

public class DatabaseTest {

    /**
     * Data limit
     */
    private static final int DATA_LIMIT = 5;

    /**
     * Countdown latch
     */
    private CountDownLatch lock = new CountDownLatch(1);

    /**
     * Received data
     */
    private List<Data> receiveddata;

    @Test
    public void testDataRetrieval() throws Exception {
        Database db = new MockDatabaseImpl();
        db.getData(DATA_LIMIT, new DataCallback() {
            @Override
            public void onSuccess(List<Data> data) {
                receiveddata = data;
                lock.countDown();
            }
        });

        lock.await(2000, TimeUnit.MILLISECONDS);

        assertNotNull(receiveddata);
        assertEquals(DATA_LIMIT, receiveddata.size());
    }
}

ध्यान दें कि आप केवल लॉक के रूप में एक नियमित ऑब्जेक्ट के साथ सिंक्रोनाइज्ड का उपयोग नहीं कर सकते हैं , क्योंकि लॉक की प्रतीक्षा विधि को कॉल करने से पहले फास्ट कॉलबैक लॉक को रिलीज़ कर सकता है। जो वालनेस की इस ब्लॉग पोस्ट को देखें ।

EDIT ने काउंटडाउन लंच के आसपास सिंक किए गए ब्लॉक को @jtahlborn और @Ring की टिप्पणियों के लिए धन्यवाद दिया


8
कृपया इस उदाहरण का पालन न करें, यह गलत है। आपको काउंटडाउन लैंथ पर सिंक्रोनाइज़ नहीं करना चाहिए क्योंकि यह थ्रेड-सेफ्टी को आंतरिक रूप से हैंडल करता है।
jtahlborn

1
सिंक्रनाइज़ किए गए भाग तक यह अच्छी सलाह थी, कि शायद डिबगिंग समय के 3-4 घंटे के करीब हो। stackoverflow.com/questions/11007551/…
रिंग

2
त्रुटि के लिए क्षमा याचना। मैंने उत्तर को उचित रूप से संपादित किया है।
मार्टिन

7
यदि आप सत्यापित कर रहे हैं कि onSuccess कहा गया था, तो आपको उस लॉक की पुष्टि करनी चाहिए जो सही है।
गिल्बर्ट

1
@ मॉर्टिन जो सही होगा, लेकिन इसका मतलब होगा कि आपको एक अलग समस्या है जिसे ठीक करने की आवश्यकता है।
जॉर्ज अरस्तु

75

आप Awaitility लाइब्रेरी का उपयोग करके देख सकते हैं । यह उन प्रणालियों का परीक्षण करना आसान बनाता है जिनके बारे में आप बात कर रहे हैं।


21
एक दोस्ताना अस्वीकरण: जोहान परियोजना में मुख्य योगदानकर्ता है।
dbm

1
प्रतीक्षा करने की मूलभूत समस्या से पीड़ित (इकाई परीक्षणों को तेजी से चलाने की आवश्यकता है )। आदर्श रूप से आप वास्तव में आवश्यकता से अधिक लंबे समय तक प्रतीक्षा नहीं करना चाहते हैं, इसलिए मुझे लगता है कि CountDownLatchइस संबंध में (@Martin द्वारा उत्तर देखें) बेहतर है।
बजे जॉर्ज एरिस्टी

वाकई शानदार।
आर। करलु

यह सही पुस्तकालय है जो मेरी एसिंक्श प्रक्रिया एकीकरण परीक्षण आवश्यकताओं को पूरा करता है। वाकई शानदार। पुस्तकालय अच्छी तरह से बनाए रखा है और लगता है कि बुनियादी से उन्नत करने के लिए सुविधाओं है कि मेरा मानना ​​है कि ज्यादातर परिदृश्यों के लिए पूरा करने के लिए पर्याप्त हैं। भयानक संदर्भ के लिए धन्यवाद!
तनवीर

वाकई कमाल का सुझाव। धन्यवाद
RoyalTiger

68

यदि आप किसी CompletableFuture (जावा 8 में शुरू की गई है) या एक SettableFuture (से गूगल अमरूद ), आप जल्द ही के रूप में अपने परीक्षण खत्म कर सकते हैं के रूप में यह नहीं बल्कि समय की एक पूर्व निर्धारित राशि का इंतजार कर से, किया है। आपका परीक्षण कुछ इस तरह दिखाई देगा:

CompletableFuture<String> future = new CompletableFuture<>();
executorService.submit(new Runnable() {         
    @Override
    public void run() {
        future.complete("Hello World!");                
    }
});
assertEquals("Hello World!", future.get());

4
... और अगर आप जावा-कम-से-आठ की कोशिश कर रहे हैं, तो अमरनाथ सेटेटेबल सिवनी की कोशिश करते हैं, जो बहुत हद तक एक ही काम करता है
मार्कस टी।


18

एसिंक्रोनस विधियों के परीक्षण के लिए एक विधि जो मुझे बहुत उपयोगी लगी है वह है एक इंजेक्शन लगाना Executor , वह ऑब्जेक्ट-टू-टेस्ट के कंस्ट्रक्टर में उदाहरण रहा है। उत्पादन में, निष्पादक उदाहरण को अतुल्यकालिक रूप से चलाने के लिए कॉन्फ़िगर किया गया है, जबकि परीक्षण में इसे तुल्यकालिक रूप से चलाने के लिए नकली किया जा सकता है।

तो मान लीजिए मैं अतुल्यकालिक विधि का परीक्षण करने की कोशिश कर रहा हूं Foo#doAsync(Callback c),

class Foo {
  private final Executor executor;
  public Foo(Executor executor) {
    this.executor = executor;
  }

  public void doAsync(Callback c) {
    executor.execute(new Runnable() {
      @Override public void run() {
        // Do stuff here
        c.onComplete(data);
      }
    });
  }
}

उत्पादन में, मैं Fooएक Executors.newSingleThreadExecutor()एक्जिक्यूटर उदाहरण के साथ निर्माण करूँगा, जबकि परीक्षण में मैं संभवतः इसका निर्माण एक तुल्यकालिक निष्पादक के साथ करूंगा जो निम्नलिखित कार्य करता है -

class SynchronousExecutor implements Executor {
  @Override public void execute(Runnable r) {
    r.run();
  }
}

अब अतुल्यकालिक विधि का मेरा JUnit परीक्षण बहुत साफ है -

@Test public void testDoAsync() {
  Executor executor = new SynchronousExecutor();
  Foo objectToTest = new Foo(executor);

  Callback callback = mock(Callback.class);
  objectToTest.doAsync(callback);

  // Verify that Callback#onComplete was called using Mockito.
  verify(callback).onComplete(any(Data.class));

  // Assert that we got back the data that we expected.
  assertEquals(expectedData, callback.getData());
}

काम नहीं करता है, तो मैं कुछ परीक्षण करना चाहता हूं जिसमें वसंत की तरह एक अतुल्यकालिक पुस्तकालय कॉल शामिल हैWebClient
स्टीफन हैबर

8

थ्रेडेड / async कोड के परीक्षण में स्वाभाविक रूप से कुछ भी गलत नहीं है, खासकर यदि थ्रेडिंग आपके द्वारा परीक्षण किए जा रहे कोड का बिंदु है। इस सामान का परीक्षण करने के लिए सामान्य दृष्टिकोण है:

  • मुख्य परीक्षण थ्रेड को ब्लॉक करें
  • अन्य थ्रेड्स से कैप्चर किए गए दावे को कैप्चर करें
  • मुख्य परीक्षण थ्रेड को अनब्लॉक करें
  • किसी भी विफलताओं को रोकें

लेकिन यह एक परीक्षण के लिए बहुत सारे बॉयलरप्लेट है। एक बेहतर / सरल तरीका सिर्फ समवर्ती उपयोग करना है :

  final Waiter waiter = new Waiter();

  new Thread(() -> {
    doSomeWork();
    waiter.assertTrue(true);
    waiter.resume();
  }).start();

  // Wait for resume() to be called
  waiter.await(1000);

इस CountdownLatchदृष्टिकोण पर लाभ यह है कि यह कम क्रिया है क्योंकि किसी भी थ्रेड में होने वाली अभिक्रिया विफलताओं को मुख्य थ्रेड को ठीक से रिपोर्ट किया जाता है, जिसका अर्थ है कि परीक्षण विफल होना चाहिए। एक राइटअप जो CountdownLatchसमवर्ती यूनाइटिट के दृष्टिकोण की तुलना करता है, यहां है

मैंने उन लोगों के लिए इस विषय पर एक ब्लॉग पोस्ट भी लिखी है जो थोड़ा और विस्तार से सीखना चाहते हैं।


इसी तरह का एक समाधान जिसका मैंने अतीत में उपयोग किया है वह है github.com/MichaelTamm/junit-toolbox , जिसे junit.org/junit4
dschulten

4

कॉल करने के बारे में SomeObject.waitऔर notifyAllजैसा कि यहां बताया गया है या रोबोटियम Solo.waitForCondition(...) विधि का उपयोग करके या ऐसा करने के लिए लिखी गई एक कक्षा का उपयोग करें (टिप्पणियों को देखें और कैसे इसके लिए परीक्षण वर्ग देखें)


1
प्रतीक्षा / अधिसूचित / बाधित दृष्टिकोण के साथ समस्या यह है कि जिस कोड का आप परीक्षण कर रहे हैं वह संभावित रूप से प्रतीक्षा थ्रेड्स के साथ हस्तक्षेप कर सकता है (मैंने देखा है कि ऐसा होता है)। यही कारण है कि ConcurrentUnit एक निजी सर्किट का उपयोग करता है जो थ्रेड्स प्रतीक्षा कर सकते हैं, जो अनजाने में मुख्य परीक्षण थ्रेड में व्यवधान के साथ हस्तक्षेप नहीं किया जा सकता है।
जोनाथन

3

मुझे अतुल्यकालिक तर्क का परीक्षण करने के लिए एक पुस्तकालय socket.io मिलता है। यह लिंक्डब्लॉकिंग क्यू का उपयोग करके सरल और संक्षिप्त तरीका दिखता है । यहाँ उदाहरण है :

    @Test(timeout = TIMEOUT)
public void message() throws URISyntaxException, InterruptedException {
    final BlockingQueue<Object> values = new LinkedBlockingQueue<Object>();

    socket = client();
    socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
        @Override
        public void call(Object... objects) {
            socket.send("foo", "bar");
        }
    }).on(Socket.EVENT_MESSAGE, new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            values.offer(args);
        }
    });
    socket.connect();

    assertThat((Object[])values.take(), is(new Object[] {"hello client"}));
    assertThat((Object[])values.take(), is(new Object[] {"foo", "bar"}));
    socket.disconnect();
}

LinkedBlockingQueue का उपयोग करके API को ब्लॉक करने के लिए तब तक ले जाएं जब तक कि तुल्यकालिक तरीके से परिणाम प्राप्त न हो जाए। और परिणाम की प्रतीक्षा करने के लिए बहुत अधिक समय लगाने से बचने के लिए समय निर्धारित करें।


1
बहुत बढ़िया दृष्टिकोण!
अमीनोग्राफी

3

यह ध्यान देने योग्य है कि कॉन्सेप्ट इन प्रैक्टिसTesting Concurrent Programs में बहुत उपयोगी अध्याय है जो कुछ यूनिट परीक्षण दृष्टिकोण का वर्णन करता है और मुद्दों के लिए समाधान देता है।


1
कौन सा दृष्टिकोण है? क्या आप एक उदाहरण दे सकते हैं?
ब्रूनो फरेरा

2

यदि मैं परीक्षा परिणाम को अतुल्यकालिक रूप से उत्पादित करता हूं तो यही आजकल उपयोग कर रहा हूं।

public class TestUtil {

    public static <R> R await(Consumer<CompletableFuture<R>> completer) {
        return await(20, TimeUnit.SECONDS, completer);
    }

    public static <R> R await(int time, TimeUnit unit, Consumer<CompletableFuture<R>> completer) {
        CompletableFuture<R> f = new CompletableFuture<>();
        completer.accept(f);
        try {
            return f.get(time, unit);
        } catch (InterruptedException | TimeoutException e) {
            throw new RuntimeException("Future timed out", e);
        } catch (ExecutionException e) {
            throw new RuntimeException("Future failed", e.getCause());
        }
    }
}

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

    @Test
    public void testAsync() {
        String result = await(f -> {
            new Thread(() -> f.complete("My Result")).start();
        });
        assertEquals("My Result", result);
    }

यदि f.completeनहीं कहा जाता है, तो परीक्षा एक समय समाप्त होने के बाद विफल हो जाएगी। आप f.completeExceptionallyजल्दी असफल होने के लिए भी उपयोग कर सकते हैं।


2

यहाँ कई उत्तर हैं, लेकिन एक सरल एक पूरा कम्प्लीट फ़ॉरेस्ट फ़ॉइल बनाना है और इसका उपयोग करना है:

CompletableFuture.completedFuture("donzo")

तो मेरे परीक्षण में:

this.exactly(2).of(mockEventHubClientWrapper).sendASync(with(any(LinkedList.class)));
this.will(returnValue(new CompletableFuture<>().completedFuture("donzo")));

मैं बस यह सुनिश्चित कर रहा हूं कि इस सामान को सभी वैसे भी कहते हैं। यदि आप इस कोड का उपयोग कर रहे हैं तो यह तकनीक काम करती है:

CompletableFuture.allOf(calls.toArray(new CompletableFuture[0])).join();

यह पूरी तरह से समाप्त हो जाएगा क्योंकि यह सभी के माध्यम से सही ज़िप जाएगा!


2

जब भी आप कर सकते हैं (जो कि ज्यादातर समय होता है) समानांतर थ्रेड के परीक्षण से बचें। यह केवल आपके परीक्षणों को परतदार बना देगा (कभी-कभी पास, कभी-कभी विफल)।

केवल जब आपको किसी अन्य लाइब्रेरी / सिस्टम को कॉल करने की आवश्यकता होती है, तो आपको अन्य थ्रेड्स पर इंतजार करना पड़ सकता है, उस स्थिति में हमेशा इसके बजाय Awaitility लाइब्रेरी का उपयोग करें Thread.sleep()

कभी भी कॉल get()या join()अपने परीक्षणों में, अन्यथा भविष्य में कभी पूरा न होने की स्थिति में आपके परीक्षण आपके CI सर्वर पर हमेशा के लिए चल सकते हैं। हमेशा isDone()कॉल करने से पहले अपने परीक्षणों में पहले से ही जोर लगा लें get()। कंप्लीटेशनस्टेज के लिए, वह है .toCompletableFuture().isDone()

जब आप इस तरह से एक गैर-अवरोधक विधि का परीक्षण करते हैं:

public static CompletionStage<String> createGreeting(CompletableFuture<String> future) {
    return future.thenApply(result -> "Hello " + result);
}

तब आपको केवल एक पूर्ण भविष्य को परीक्षा में उत्तीर्ण करके परिणाम का परीक्षण नहीं करना चाहिए, आपको यह भी सुनिश्चित करना चाहिए कि आपका तरीका doSomething()कॉल करके join()या ब्लॉक नहीं करता है get()। यह विशेष रूप से महत्वपूर्ण है यदि आप एक गैर-अवरुद्ध ढांचे का उपयोग करते हैं।

ऐसा करने के लिए, एक गैर-पूर्ण भविष्य के साथ परीक्षण करें जिसे आपने मैन्युअल रूप से पूरा करने के लिए सेट किया है:

@Test
public void testDoSomething() throws Exception {
    CompletableFuture<String> innerFuture = new CompletableFuture<>();
    CompletableFuture<String> futureResult = createGreeting(innerFuture).toCompletableFuture();
    assertFalse(futureResult.isDone());

    // this triggers the future to complete
    innerFuture.complete("world");
    assertTrue(futureResult.isDone());

    // futher asserts about fooResult here
    assertEquals(futureResult.get(), "Hello world");
}

इस प्रकार, यदि आप future.join()doSomething () में जोड़ते हैं , तो परीक्षण विफल हो जाएगा।

यदि आपकी सेवा में एक ExecutorService का उपयोग किया जाता है thenApplyAsync(..., executorService), तो अपने परीक्षणों में एक एकल-थ्रेडेड ExecutorService को इंजेक्ट करें, जैसे कि अमरूद से:

ExecutorService executorService = Executors.newSingleThreadExecutor();

यदि आपका कोड forkJoinPool का उपयोग करता है, जैसे thenApplyAsync(...), एक एक्जिक्यूटर सर्विस (कई अच्छे कारण हैं) का उपयोग करने के लिए कोड को फिर से लिखें या Awaitility का उपयोग करें।

उदाहरण को छोटा करने के लिए, मैंने BarService को टेस्ट में Java8 लैम्बडा के रूप में लागू एक विधि तर्क दिया, आमतौर पर यह एक इंजेक्शन संदर्भ होगा जिसे आप मॉक करेंगे।


अरे @trruse, शायद आपके पास इस तकनीक का उपयोग करके एक परीक्षण के साथ एक सार्वजनिक गिट रेपो है?
क्रिस्टियानो

@ क्रिसटनो: यह एसओ दर्शन के खिलाफ होगा। इसके बजाय, मैंने किसी अतिरिक्त कोड (सभी आयात java8 + या जूनिट) के बिना संकलित करने के तरीकों को बदल दिया, जब आप उन्हें एक खाली जूनियर परीक्षा वर्ग में पेस्ट करते हैं। बेझिझक उत्थान करना है।
trruse

मैं अब समझ गया था। धन्यवाद। मेरी समस्या अब यह जांचने की है कि विधियां कंप्लीटटेबल सिवनी कैसे लौटाती हैं, लेकिन कंपेटिबल के सिवाय अन्य ऑब्जेक्ट के रूप में अन्य ऑब्जेक्ट को स्वीकार करते हैं।
क्रिस्टियानो

आपके मामले में, कंपेटिफ़ैब्रेट को कौन बनाता है जो विधि वापस आती है? यदि यह एक और सेवा है, तो इसका मजाक उड़ाया जा सकता है और मेरी तकनीक अभी भी लागू होती है। यदि विधि स्वयं एक कंप्लीटटेबल सिवनी बनाती है तो स्थिति बहुत बदल जाती है, इसलिए आप इसके बारे में एक नया प्रश्न पूछ सकते हैं। यह इस बात पर निर्भर करता है कि कौन सा धागा भविष्य को पूरा करेगा कि आपकी पद्धति वापस आए।
tkruse

1

मैं प्रतीक्षा और सूचना का उपयोग करना पसंद करता हूं। यह सरल और स्पष्ट है।

@Test
public void test() throws Throwable {
    final boolean[] asyncExecuted = {false};
    final Throwable[] asyncThrowable= {null};

    // do anything async
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // Put your test here.
                fail(); 
            }
            // lets inform the test thread that there is an error.
            catch (Throwable throwable){
                asyncThrowable[0] = throwable;
            }
            // ensure to release asyncExecuted in case of error.
            finally {
                synchronized (asyncExecuted){
                    asyncExecuted[0] = true;
                    asyncExecuted.notify();
                }
            }
        }
    }).start();

    // Waiting for the test is complete
    synchronized (asyncExecuted){
        while(!asyncExecuted[0]){
            asyncExecuted.wait();
        }
    }

    // get any async error, including exceptions and assertationErrors
    if(asyncThrowable[0] != null){
        throw asyncThrowable[0];
    }
}

मूल रूप से, हमें अनाम आंतरिक वर्ग के अंदर उपयोग किए जाने के लिए एक अंतिम ऐरे संदर्भ बनाने की आवश्यकता है। मैं बूलियन बनाना चाहूंगा [], क्योंकि अगर हमें प्रतीक्षा करने की आवश्यकता है तो मैं नियंत्रण के लिए एक मूल्य रख सकता हूं ()। जब सब कुछ हो जाता है, हम सिर्फ asyncExecuted जारी करते हैं।


1
यदि आपका दावा विफल हो जाता है, तो मुख्य परीक्षण सूत्र को इसके बारे में पता नहीं चलेगा।
जोनाथन

समाधान के लिए धन्यवाद, मुझे websocket कनेक्शन के साथ कोड डीबग करने में मदद करता है।
नजर सखारेंको

@ जोनाथन, मैंने किसी भी दावे और अपवाद को पकड़ने के लिए कोड को अपडेट किया और इसे मुख्य परीक्षण थ्रेड को सूचित किया।
पॉलो

1

वहाँ से बाहर आने वाले सभी स्प्रिंग उपयोगकर्ताओं के लिए, यह आमतौर पर आजकल मैं अपना एकीकरण परीक्षण करता हूं, जहां async व्यवहार शामिल है:

उत्पादन कोड में एक एप्लिकेशन ईवेंट को फायर करें, जब एक एसिंक्स कार्य (जैसे कि I / O कॉल) समाप्त हो गया है। अधिकांश समय यह घटना वैसे भी आवश्यक होती है जब उत्पादन में एसिंक्श ऑपरेशन की प्रतिक्रिया को संभालने के लिए।

इस घटना के बाद, आप अपने परीक्षण के मामले में निम्नलिखित रणनीति का उपयोग कर सकते हैं:

  1. परीक्षण के तहत प्रणाली निष्पादित करें
  2. घटना के लिए सुनो और सुनिश्चित करें कि घटना को निकाल दिया गया है
  3. अपनी मुखरता करो

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

(ध्यान दें, कि निम्न कोड स्निपेट बॉयलर प्लेट कोड से छुटकारा पाने के लिए लोम्बोक एनोटेशन का भी उपयोग करते हैं )

@RequiredArgsConstructor
class TaskCompletedEvent() {
  private final UUID taskId;
  // add more fields containing the result of the task if required
}

उत्पादन कोड स्वयं तो आमतौर पर इस तरह दिखता है:

@Component
@RequiredArgsConstructor
class Production {

  private final ApplicationEventPublisher eventPublisher;

  void doSomeTask(UUID taskId) {
    // do something like calling a REST endpoint asynchronously
    eventPublisher.publishEvent(new TaskCompletedEvent(taskId));
  }

}

मैं @EventListenerपरीक्षण कोड में प्रकाशित घटना को पकड़ने के लिए स्प्रिंग का उपयोग कर सकता हूं । ईवेंट श्रोता थोड़ा अधिक शामिल है, क्योंकि इसे दो मामलों को एक थ्रेड से सुरक्षित तरीके से संभालना है:

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

CountDownLatchका उपयोग दूसरे मामले के लिए किया जाता है जैसा कि यहां अन्य उत्तरों में वर्णित है। यह भी ध्यान दें, कि @Orderईवेंट हैंडलर विधि पर एनोटेशन यह सुनिश्चित करता है, कि इस ईवेंट हैंडलर विधि को किसी अन्य ईवेंट श्रोता के उत्पादन में उपयोग करने के बाद कहा जाता है।

@Component
class TaskCompletionEventListener {

  private Map<UUID, CountDownLatch> waitLatches = new ConcurrentHashMap<>();
  private List<UUID> eventsReceived = new ArrayList<>();

  void waitForCompletion(UUID taskId) {
    synchronized (this) {
      if (eventAlreadyReceived(taskId)) {
        return;
      }
      checkNobodyIsWaiting(taskId);
      createLatch(taskId);
    }
    waitForEvent(taskId);
  }

  private void checkNobodyIsWaiting(UUID taskId) {
    if (waitLatches.containsKey(taskId)) {
      throw new IllegalArgumentException("Only one waiting test per task ID supported, but another test is already waiting for " + taskId + " to complete.");
    }
  }

  private boolean eventAlreadyReceived(UUID taskId) {
    return eventsReceived.remove(taskId);
  }

  private void createLatch(UUID taskId) {
    waitLatches.put(taskId, new CountDownLatch(1));
  }

  @SneakyThrows
  private void waitForEvent(UUID taskId) {
    var latch = waitLatches.get(taskId);
    latch.await();
  }

  @EventListener
  @Order
  void eventReceived(TaskCompletedEvent event) {
    var taskId = event.getTaskId();
    synchronized (this) {
      if (isSomebodyWaiting(taskId)) {
        notifyWaitingTest(taskId);
      } else {
        eventsReceived.add(taskId);
      }
    }
  }

  private boolean isSomebodyWaiting(UUID taskId) {
    return waitLatches.containsKey(taskId);
  }

  private void notifyWaitingTest(UUID taskId) {
    var latch = waitLatches.remove(taskId);
    latch.countDown();
  }

}

अंतिम चरण एक परीक्षण मामले में परीक्षण के तहत प्रणाली को निष्पादित करना है। मैं यहां JUnit 5 के साथ स्प्रिंगबूट परीक्षण का उपयोग कर रहा हूं, लेकिन स्प्रिंग संदर्भ का उपयोग करके सभी परीक्षणों के लिए यह एक ही काम करना चाहिए।

@SpringBootTest
class ProductionIntegrationTest {

  @Autowired
  private Production sut;

  @Autowired
  private TaskCompletionEventListener listener;

  @Test
  void thatTaskCompletesSuccessfully() {
    var taskId = UUID.randomUUID();
    sut.doSomeTask(taskId);
    listener.waitForCompletion(taskId);
    // do some assertions like looking into the DB if value was stored successfully
  }

}

ध्यान दें, कि यहां अन्य उत्तरों के विपरीत, यह समाधान भी काम करेगा यदि आप समानांतर और कई थ्रेड्स में अपने परीक्षणों को निष्पादित करते हैं और एक ही समय में async कोड का उपयोग करते हैं।


0

यदि आप तर्क का परीक्षण करना चाहते हैं, तो इसे एसिंक्रोनस रूप से परीक्षण न करें।

उदाहरण के लिए इस कोड का परीक्षण करना जो एक अतुल्यकालिक विधि के परिणामों पर काम करता है।

public class Example {
    private Dependency dependency;

    public Example(Dependency dependency) {
        this.dependency = dependency;            
    }

    public CompletableFuture<String> someAsyncMethod(){
        return dependency.asyncMethod()
                .handle((r,ex) -> {
                    if(ex != null) {
                        return "got exception";
                    } else {
                        return r.toString();
                    }
                });
    }
}

public class Dependency {
    public CompletableFuture<Integer> asyncMethod() {
        // do some async stuff       
    }
}

परीक्षण में समकालिक कार्यान्वयन के साथ निर्भरता का मज़ाक उड़ाया। यूनिट टेस्ट पूरी तरह से सिंक्रोनस है और 150ms में चलता है।

public class DependencyTest {
    private Example sut;
    private Dependency dependency;

    public void setup() {
        dependency = Mockito.mock(Dependency.class);;
        sut = new Example(dependency);
    }

    @Test public void success() throws InterruptedException, ExecutionException {
        when(dependency.asyncMethod()).thenReturn(CompletableFuture.completedFuture(5));

        // When
        CompletableFuture<String> result = sut.someAsyncMethod();

        // Then
        assertThat(result.isCompletedExceptionally(), is(equalTo(false)));
        String value = result.get();
        assertThat(value, is(equalTo("5")));
    }

    @Test public void failed() throws InterruptedException, ExecutionException {
        // Given
        CompletableFuture<Integer> c = new CompletableFuture<Integer>();
        c.completeExceptionally(new RuntimeException("failed"));
        when(dependency.asyncMethod()).thenReturn(c);

        // When
        CompletableFuture<String> result = sut.someAsyncMethod();

        // Then
        assertThat(result.isCompletedExceptionally(), is(equalTo(false)));
        String value = result.get();
        assertThat(value, is(equalTo("got exception")));
    }
}

आप async व्यवहार का परीक्षण नहीं करते हैं लेकिन यदि तर्क सही है तो आप परीक्षण कर सकते हैं।

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