Java 如何使用 JUnit 测试异步进程

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/631598/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:09:16  来源:igfitidea点击:

How to use JUnit to test asynchronous processes

javaunit-testingasynchronousjunit

提问by Sam

How do you test methods that fire asynchronous processes with JUnit?

您如何测试使用 JUnit 触发异步进程的方法?

I don't know how to make my test wait for the process to end (it is not exactly a unit test, it is more like an integration test as it involves several classes and not just one).

我不知道如何让我的测试等待过程结束(它不完全是一个单元测试,它更像是一个集成测试,因为它涉及多个类而不仅仅是一个类)。

采纳答案by Cem Catikkas

IMHO it's bad practice to have unit tests create or wait on threads, etc. You'd like these tests to run in split seconds. That's why I'd like to propose a 2-step approach to testing async processes.

恕我直言,让单元测试创​​建或等待线程等是不好的做法。您希望这些测试在几秒钟内运行。这就是为什么我想提出一种两步法来测试异步进程。

  1. Test that your async process is submitted properly. You can mock the object that accepts your async requests and make sure that the submitted job has correct properties, etc.
  2. Test that your async callbacks are doing the right things. Here you can mock out the originally submitted job and assume it's initialized properly and verify that your callbacks are correct.
  1. 测试您的异步进程是否正确提交。您可以模拟接受异步请求的对象,并确保提交的作业具有正确的属性等。
  2. 测试您的异步回调是否在做正确的事情。在这里,您可以模拟最初提交的作业并假设它已正确初始化并验证您的回调是否正确。

回答by Tom Hawtin - tackline

Start the process off and wait for the result using a Future.

开始该过程并使用Future.

回答by Martin

An alternative is to use the CountDownLatchclass.

另一种方法是使用CountDownLatch类。

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

NOTEyou can't just used syncronizedwith a regular object as a lock, as fast callbacks can release the lock before the lock's wait method is called. See thisblog post by Joe Walnes.

注意您不能只使用与常规对象同步的锁作为锁,因为快速回调可以在调用锁的等待方法之前释放锁。请参阅Joe Walnes 的这篇博文。

EDITRemoved syncronized blocks around CountDownLatch thanks to comments from @jtahlborn and @Ring

编辑由于@jtahlborn 和@Ring 的评论,删除了 CountDownLatch 周围的同步块

回答by Johan

You can try using the Awaitilitylibrary. It makes it easy to test the systems you're talking about.

您可以尝试使用Awaitility库。它可以轻松测试您正在谈论的系统。

回答by Dori

How about calling SomeObject.waitand notifyAllas described hereOR using RobotiumsSolo.waitForCondition(...)method OR use a class i wroteto do this (see comments and test class for how to use)

如何调用SomeObject.waitnotifyAllhere所述或使用RobotiumsSolo.waitForCondition(...)方法或使用我编写来执行此操作(有关如何使用,请参阅注释和测试类)

回答by Matt

One method I've found pretty useful for testing asynchronous methods is injecting an Executorinstance in the object-to-test's constructor. In production, the executor instance is configured to run asynchronously while in test it can be mocked to run synchronously.

我发现对测试异步方法非常有用的一种方法是Executor在 object-to-test 的构造函数中注入一个实例。在生产中,执行器实例被配置为异步运行,而在测试中它可以被模拟为同步运行。

So suppose I'm trying to test the asynchronous method Foo#doAsync(Callback c),

所以假设我正在尝试测试异步方法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);
      }
    });
  }
}

In production, I would construct Foowith an Executors.newSingleThreadExecutor()Executor instance while in test I would probably construct it with a synchronous executor that does the following --

在生产中,我会Foo用一个Executors.newSingleThreadExecutor()Executor 实例来构建,而在测试中我可能会用一个同步执行器来构建它,它会执行以下操作——

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

Now my JUnit test of the asynchronous method is pretty clean --

现在我对异步方法的 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());
}

回答by user393274

If you use a CompletableFuture(introduced in Java 8) or a SettableFuture(from Google Guava), you can make your test finish as soon as it's done, rather than waiting a pre-set amount of time. Your test would look something like this:

如果您使用CompletableFuture(在 Java 8 中引入)或SettableFuture(来自Google Guava),您可以在测试完成后立即完成,而不是等待预设的时间。你的测试看起来像这样:

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

回答by Jonathan

There's nothing inherently wrong with testing threaded/async code, particularly if threading is the pointof the code you're testing. The general approach to testing this stuff is to:

测试线程/异步代码本身并没有什么问题,特别是如果线程是您正在测试的代码的重点。测试这些东西的一般方法是:

  • Block the main test thread
  • Capture failed assertions from other threads
  • Unblock the main test thread
  • Rethrow any failures
  • 阻塞主测试线程
  • 从其他线程捕获失败的断言
  • 解除阻塞主测试线程
  • 重新抛出任何失败

But that's a lot of boilerplate for one test. A better/simpler approach is to just use ConcurrentUnit:

但是对于一项测试来说,这是很多样板。更好/更简单的方法是只使用ConcurrentUnit

  final Waiter waiter = new Waiter();

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

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

The benefit of this over the CountdownLatchapproach is that it's less verbose since assertion failures that occur in any thread are properly reported to the main thread, meaning the test fails when it should. A writeup that compares the CountdownLatchapproach to ConcurrentUnit is here.

这种CountdownLatch方法的好处是它不那么冗长,因为任何线程中发生的断言失败都会正确地报告给主线程,这意味着测试会在应该失败时失败。比较CountdownLatchConcurrentUnit 方法的文章是here

I also wrote a blog poston the topic for those who want to learn a bit more detail.

我还为那些想要了解更多细节的人写了一篇关于该主题的博客文章

回答by Paulo

I prefer use wait and notify. It is simple and clear.

我更喜欢使用等待和通知。它简单明了。

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

Basically, we need to create a final Array reference, to be used inside of anonymous inner class. I would rather create a boolean[], because I can put a value to control if we need to wait(). When everything is done, we just release the asyncExecuted.

基本上,我们需要创建一个最终的 Array 引用,以便在匿名内部类中使用。我宁愿创建一个布尔值[],因为我可以放置一个值来控制是否需要等待()。当一切都完成后,我们只需释放 asyncExecuted。

回答by Nils El-Himoud

If you want to test the logic just don′t test it asynchronously.

如果你想测试逻辑,就不要异步测试它。

For example to test this code which works on results of an asynchronous method.

例如测试这段代码,它适用于异步方法的结果。

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

In the test mock the dependency with synchronous implementation. The unit test is completely synchronous and runs in 150ms.

在测试中模拟具有同步实现的依赖项。单元测试是完全同步的,运行时间为 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")));
    }
}

You don′t test the async behaviour but you can test if the logic is correct.

您不测试异步行为,但您可以测试逻辑是否正确。