Zaman uyumsuz süreçleri test etmek için JUnit nasıl kullanılır


205

JUnit ile eşzamansız işlemleri tetikleyen yöntemleri nasıl test edersiniz?

Testimin sürecin bitmesini beklemesini nasıl yapacağımı bilmiyorum (tam olarak bir birim test değil, sadece bir değil, birkaç sınıf içerdiği için bir entegrasyon testi gibidir).


Sen JAT'yi (Java Asenkron Testi) deneyebilirsiniz: bitbucket.org/csolar/jat
cs0lar

2
JAT, 1 izleyiciye sahiptir ve 1.5 yılda güncellenmemiştir. Bekleme 1 ay önce güncellendi ve bu yazı yazıldığı sırada 1.6 sürümündedir. Her iki projeye de bağlı değilim, ancak projeme ek olarak yatırım yapacak olsaydım, şu anda Beklenebilirliğe daha fazla güvenirim.
Les Hazlewood

JAT'in hala güncellemesi yok: "Son güncelleme 2013-01-19". Sadece bağlantıyı takip etmek için zaman kazanın.
deamon

@LesHazlewood, bir gözlemci JAT için kötü, ancak yıllarca güncelleme yok ... Sadece bir örnek. Yalnızca çalışıyorsa, işletim sisteminizin düşük düzeyli TCP yığınını ne sıklıkla güncellersiniz? JAT alternatifi aşağıdaki stackoverflow.com/questions/631598/… adresinde yanıtlanmaktadır .
user1742529

Yanıtlar:


46

IMHO, birim testlerinin iş parçacıkları vb. Oluşturmasını veya beklemesini kötü bir uygulamadır. Bu testlerin birkaç saniye içinde çalışmasını istiyorsunuz. Bu yüzden zaman uyumsuz süreçleri test etmek için 2 adımlı bir yaklaşım önermek istiyorum.

  1. Eşzamansız işleminizin düzgün gönderildiğini test edin. Zaman uyumsuz isteklerinizi kabul eden nesneyi alay edebilir ve gönderilen işin doğru özelliklere vb. Sahip olduğundan emin olabilirsiniz.
  2. Zaman uyumsuz geri aramalarınızın doğru şeyleri yaptığını test edin. Burada orijinal olarak gönderilen işi alay edebilir ve doğru şekilde başlatıldığını varsayabilir ve geri aramalarınızın doğru olduğunu doğrulayabilirsiniz.

148
Elbette. Ancak bazen iş parçacıklarını yönetmesi gereken kodu test etmeniz gerekir.
lacker

78
Junit veya TestNG'yi yalnızca birim testi değil (birim testi değil) veya kullanıcı kabul testi (örn. Salatalık) yapmak için, zaman uyumsuz bir tamamlamayı beklemek ve sonucu doğrulamak kesinlikle gereklidir.
Les Hazlewood

39
Eşzamansız süreçler, doğru bir şekilde en karmaşık kodlardan biridir ve onlar için birim testi kullanmamanız ve sadece tek bir iş parçacığıyla test etmeniz gerektiğini mi söylüyorsunuz? Bu çok kötü bir fikir.
Charles

18
Sahte testler genellikle işlevselliğin uçtan uca çalıştığını kanıtlayamaz. Asenkron işlevinin, çalıştığından emin olmak için senkronize olmayan bir şekilde test edilmesi gerekir. İsterseniz bir entegrasyon testi olarak adlandırın, ancak yine de gerekli olan bir testtir.
Scott Boring

4
Bu kabul edilen cevap olmamalıdır. Testler birim testlerin ötesine geçer. OP bunu Birim Testinden daha fazla Entegrasyon Testi olarak nitelendirir.
Jeremiah Adams

191

Alternatif olarak CountDownLatch sınıfını kullanmaktır .

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

NOT Sadece kullanılan olamaz syncronized kilidin bekleme yöntemi çağrılmadan önce hızlı geri aramaları kilidi serbest bırakabilirsiniz gibi bir kilit gibi düzenli nesneyle. Joe Walnes tarafından yazılmış bu blog gönderisine bakın .

EDIT @jtahlborn ve @Ring'in yorumları sayesinde CountDownLatch çevresindeki senkronize bloklar kaldırıldı


8
lütfen bu örneği takip etmeyin, yanlış. Eğer gereken değil dahili olarak parçacığı güvenliği kolları gibi bir CountDownLatch üzerinde senkronize edilebilir.
jtahlborn

1
Senkronize bölüme kadar iyi bir tavsiye, muhtemelen 3-4 saate kadar hata ayıklama süresi yedi. stackoverflow.com/questions/11007551/…
Ring

2
Hata için özür dileriz. Cevabı uygun şekilde düzenledim.
Martin

7
OnSuccess öğesinin çağrıldığını doğrularsanız lock.await öğesinin true değerini döndürdüğünü doğrulamanız gerekir.
Gilbert

1
@Martin bu doğru olurdu, ancak düzeltilmesi gereken farklı bir sorununuz olduğu anlamına gelir.
George Aristy

77

Bekleme kitaplığını kullanmayı deneyebilirsiniz . Bahsettiğiniz sistemleri test etmeyi kolaylaştırır.


21
Dostça bir sorumluluk reddi: Johan projeye ana katkıda bulunur.
dbm

1
Beklemek zorunda kalmanın temel sorunundan muzdariptir (birim testlerin hızlı çalışması gerekir ). İdeal olarak gerçekten gerekenden bir milisaniye daha fazla beklemek istemiyorsunuz, bu yüzden CountDownLatch(@Martin tarafından verilen cevaba bakınız) bu konuda daha iyi olduğunu düşünüyorum .
George Aristy

Gerçekten harika.
R. Karlus

Bu, zaman uyumsuz süreç entegrasyon testi gereksinimlerimi karşılayan mükemmel bir kütüphanedir. Gerçekten harika. Kütüphane iyi korunmuş gibi görünüyor ve çoğu senaryo için yiyecek ve içecek sağlamak için yeterli olduğuna inanıyorum temelden ileriye uzanan özelliklere sahiptir. Harika referans için teşekkürler!
Tanvir

Gerçekten harika bir öneri. Thanks
RoyalTiger

68

Bir CompletableFuture (Java 8'de tanıtıldı) veya bir SettableFuture ( Google Guava'dan ) kullanıyorsanız, testinizin önceden ayarlanmış bir süre beklemek yerine biter bitmez yapabilirsiniz. Testiniz şöyle görünecektir:

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

4
... ve sekizden az java ile sıkışıp kalırsanız , aynı şeyi yapan guava SettableFuture'u deneyin
Markus T


18

Eşzamansız yöntemleri test etmek için oldukça yararlı bulduğum yöntemlerden biri, Executor , nesnenin test edilecek yapıcısına örnek etmektir. Üretimde, yürütücü örneği eşzamansız olarak çalışacak şekilde yapılandırılırken, test sırasında eşzamanlı olarak çalışmak için alay edilebilir.

Diyelim ki eşzamansız yöntemi test etmeye çalışıyorum 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);
      }
    });
  }
}

Üretimde, Foobir Executors.newSingleThreadExecutor()Executor örneği ile inşa ederken test ederken muhtemelen aşağıdakileri yapan senkron bir executor ile inşa ederdim -

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

Şimdi asenkron yöntemin JUnit testim oldukça temiz -

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

Entegrasyonun Spring gibi bir eşzamansız kütüphane çağrısı içeren bir şeyi test etmek istersem işe yaramazWebClient
Stefan Haberl

8

İş parçacığı / asenkron kodu test ederken, özellikle iş parçacığı test ettiğiniz kodun noktası ise, doğal olarak yanlış bir şey yoktur . Bu şeyleri test etmek için genel yaklaşım:

  • Ana test ipliğini engelleyin
  • Diğer evrelerden başarısız bildirimleri yakalama
  • Ana test dizisinin engellemesini kaldırın
  • Arızaları yeniden geri alın

Ama bu bir test için çok fazla kazan plakası. Daha iyi / daha basit bir yaklaşım sadece ConcurrentUnit kullanmaktır :

  final Waiter waiter = new Waiter();

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

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

Bu CountdownLatchyaklaşımın yararı, herhangi bir iş parçacığında ortaya çıkan onaylama hataları ana iş parçacığına doğru bir şekilde bildirildiği için daha az ayrıntılı olmasıdır, yani test gerektiğinde başarısız olur. CountdownLatchConcurrentUnit yaklaşımını karşılaştıran bir yazma burada .

Ayrıca biraz daha ayrıntılı bilgi edinmek isteyenler için konuyla ilgili bir blog yazısı yazdım .


Geçmişte kullandığım benzer bir çözüm github.com/MichaelTamm/junit-toolbox , junit.org/junit4
dschulten

4

Arama SomeObject.waitve buradanotifyAll açıklandığı gibi VEYA Robotiums yöntemini kullanarak VEYA bunu yapmak için yazdığım bir sınıfı kullanın (nasıl kullanılacağı için yorumlara ve test sınıfına bakın) Solo.waitForCondition(...)


1
Bekleme / bildirme / kesme yaklaşımıyla ilgili sorun, test ettiğiniz kodun potansiyel olarak bekleyen iş parçacıklarına müdahale edebilmesidir (bunun olduğunu gördüm). Bu nedenle ConcurrentUnit , ana test iş parçacığındaki kesintilerle yanlışlıkla müdahale edilemeyen iş parçacıklarının bekleyebileceği özel bir devre kullanır .
Jonathan

3

Eşzamansız mantığı test etmek için bir kitaplık socket.io buluyorum . LinkedBlockingQueue kullanarak basit ve kısa bir yol gibi görünüyor . İşte örnek :

    @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 kullanmak, senkronize şekilde olduğu gibi sonuç elde edene kadar engellemek için API'yi alır. Ve sonucu beklemek için çok fazla zaman harcamaktan kaçınmak için zaman aşımını ayarlayın.


1
Müthiş bir yaklaşım!
aminografi

3

Bu çok yararlı bölüm olduğunu bahsetmemiz var Testing Concurrent Programsiçinde Uygulamada eşzamanlılık test yaklaşımları bazı üniteyi açıklar ve sorunlar için çözümler sunar.


1
Bu hangi yaklaşım? Bir örnek verebilir misiniz?
Bruno Ferreira

2

Test sonucu eşzamansız olarak üretiliyorsa bu günlerde kullanıyorum.

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

Statik içe aktarmalar kullanarak, test çok güzel okur. (not, bu örnekte fikri göstermek için bir konu açıyorum)

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

Çağrı f.completeyapılmazsa, test zaman aşımından sonra başarısız olur. f.completeExceptionallyErken arıza yapmak için de kullanabilirsiniz .


2

Burada birçok cevap var, ancak basit bir cevap sadece tamamlanmış bir CompletableFuture oluşturmak ve kullanmaktır:

CompletableFuture.completedFuture("donzo")

Testimde:

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

Sadece tüm bu şeylerin zaten çağrıldığından emin oluyorum. Bu kod, bu kodu kullanıyorsanız çalışır:

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

Tüm CompletableFutures tamamlandığında hemen içinden geçecek!


2

Mümkün olduğunca paralel dişlerle test etmekten kaçının (çoğu zaman budur). Bu sadece testlerinizi kesintiye uğratır (bazen geçer, bazen başarısız olur).

Yalnızca başka bir kütüphaneyi / sistemi çağırmanız gerektiğinde, diğer konuları beklemeniz gerekebilir, bu durumda her zaman yerine Bekleme kütüphanesini kullanın Thread.sleep().

Asla sadece arama yapmayın get()ya da join()testlerinizi yapmayın, aksi takdirde geleceğin asla tamamlanmaması durumunda testleriniz CI sunucunuzda sonsuza dek çalışabilir. Aramadan isDone()önce daima testlerinizde ilk iddia edin get(). CompletionStage için, yani .toCompletableFuture().isDone().

Böyle bir engelleme olmayan yöntemi test ettiğinizde:

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

testte tamamlanmış bir Gelecek'i geçerek sonucu test etmekle kalmamalı, aynı zamanda yönteminizin doSomething()arayarak join()veya engellemediğinden de emin olmalısınız get(). Bu, özellikle engellemeyen bir çerçeve kullanıyorsanız önemlidir.

Bunu yapmak için, manuel olarak tamamlamayı ayarladığınız tamamlanmamış bir geleceği test edin:

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

Bu şekilde, future.join()doSomething () öğesine eklerseniz test başarısız olur.

Hizmetiniz, gibi bir ExecutorService kullanıyorsa thenApplyAsync(..., executorService), testlerinize guava'dan gelen gibi tek iş parçacıklı bir ExecutorService enjekte edin:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Kodunuz forkJoinPool gibi bir kod kullanıyorsa, thenApplyAsync(...)kodu bir ExecutorService kullanmak için yeniden yazın (birçok iyi neden vardır) veya Awaitility kullanın.

Örneği kısaltmak için, BarService'i testte Java8 lambda olarak uygulanan bir yöntem argümanı yaptım, tipik olarak alay edeceğiniz enjekte edilmiş bir referans olurdu.


Hey @tkruse, belki de bu tekniği kullanarak bir test ile bir kamu git repo var mı?
Cristiano

@Christiano: SO felsefesine aykırı olurdu. Bunun yerine, boş bir junit test sınıfına yapıştırdığınızda herhangi bir ek kod (tüm ithalatlar java8 + veya junit) derleme yöntemlerini değiştirdim. Olumlu oy kullanmaktan çekinmeyin.
tkruse

Şimdi anladım. Teşekkürler. Benim sorun şimdi yöntemleri CompletableFuture döndürdüğünü sınamak ama CompletableFuture dışında parametreler olarak diğer nesneleri kabul etmektir.
Cristiano

Sizin durumunuzda, yöntemin döndürdüğü CompletableFuture'u kim oluşturur? Başka bir hizmetse, bu alay edilebilir ve tekniğim hala geçerlidir. Yöntemin kendisi bir CompletableFuture oluşturuyorsa, durum çok değişir, böylece bu konuda yeni bir soru sorabilirsiniz. Daha sonra, yönteminizin döndüreceği geleceği hangi iş parçacığını tamamlayacağına bağlıdır.
tkruse

1

Bekle ve bildir'i kullanmayı tercih ederim. Basit ve anlaşılır.

@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];
    }
}

Temel olarak, anonim iç sınıfın içinde kullanılmak üzere son bir Array referansı yaratmamız gerekir. Boolean [] oluşturmayı tercih ederim, çünkü beklememiz () gerekiyorsa kontrol etmek için bir değer koyabilirim. Her şey bittiğinde, asyncExecuted'i serbest bırakıyoruz.


1
Eğer iddianız başarısız olursa, ana test konusu bunu bilmeyecektir.
Jonathan

Çözüm için teşekkürler, websocket bağlantısı ile kod hatalarını ayıklamama yardımcı olur.
Nazar Sakharenko

@Jonathan, herhangi bir iddia ve istisna yakalamak ve ana test iş parçacığına bildirmek için kodu güncelledim.
Paulo

1

Dışarıdaki tüm Bahar kullanıcıları için, günümüzde uyumsuzluğun dahil olduğu entegrasyon testlerimi şu şekilde yaparım:

Zaman uyumsuz bir görev (G / Ç çağrısı gibi) bittiğinde, üretim kodunda bir uygulama olayını başlatın. Çoğu zaman bu olay, üretimdeki asenkron işlemin yanıtını ele almak için yine de gereklidir.

Bu etkinlik gerçekleştikten sonra, test durumunuzda aşağıdaki stratejiyi kullanabilirsiniz:

  1. Test edilen sistemi çalıştırın
  2. Etkinliği dinleyin ve etkinliğin tetiklendiğinden emin olun
  3. İddialarınızı yapın

Bunu kırmak için, önce tetiklenecek bir tür etki alanı etkinliğine ihtiyacınız olacak. Tamamlanan görevi tanımlamak için burada bir UUID kullanıyorum, ancak elbette benzersiz olduğu sürece başka bir şey kullanmakta özgürsünüz.

(Aşağıdaki kod snippet'lerinin , kazan plakası kodundan kurtulmak için Lombok ek açıklamalarını da kullandığını unutmayın )

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

Üretim kodunun kendisi genellikle şöyle görünür:

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

}

Daha sonra @EventListenertest kodunda yayınlanan olayı yakalamak için bir Bahar kullanabilirsiniz . Olay dinleyicisi biraz daha karmaşıktır, çünkü iki vakayı iş parçacığıyla güvenli bir şekilde ele almak zorundadır:

  1. Üretim kodu test senaryosundan daha hızlıdır ve test senaryosu olayı kontrol etmeden önce olay tetiklenmiştir veya
  2. Test durumu üretim kodundan daha hızlıdır ve test durumu etkinliği beklemek zorundadır.

CountDownLatchBuradaki diğer cevaplarda belirtildiği gibi A ikinci durum için kullanılır. Ayrıca @Order, olay işleyici yöntemindeki ek açıklamanın, bu olay işleyici yönteminin üretimde kullanılan diğer olay dinleyicilerinden sonra çağrıldığından emin olduğunu unutmayın .

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

}

Son adım, test edilen sistemi bir test durumunda yürütmektir. Burada JUnit 5 ile bir SpringBoot testi kullanıyorum, ama bu bir Spring bağlamı kullanan tüm testler için aynı çalışmalıdır.

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

}

Buradaki diğer yanıtların aksine, testlerinizi paralel olarak çalıştırırsanız ve birden çok iş parçacığı aynı zamanda eşzamansız kod uygularsa bu çözümün de işe yarayacağını unutmayın.


0

Eğer mantığı test etmek istiyorsanız sadece eşzamansız olarak test etmeyin.

Örneğin, eşzamansız bir yöntemin sonuçları üzerinde çalışan bu kodu test etmek için.

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

Testte eşzamanlı uygulamaya bağımlılık. Birim testi tamamen senkronizedir ve 150ms'de çalışır.

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

Zaman uyumsuz davranışı test etmezsiniz, ancak mantığın doğru olup olmadığını test edebilirsiniz.

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.