gpt4 book ai didi

java - 如何在单元测试中测试异步操作内的方法调用

转载 作者:行者123 更新时间:2023-11-30 05:26:20 56 4
gpt4 key购买 nike

我有一个方法,它首先执行一系列操作,然后启动异步任务。我想测试这个方法,但我不明白如何验证异步操作是否已完成。

使用 Moсkito,我想验证 foo 方法是否执行了 2 次,一次是在异步任务开始之前,一次是在异步任务内部。问题是,在 Mockito 检查时,异步任务可能尚未调用异步操作内的方法。因此,有时进行测试,有时不进行测试。

这是我的方法的示例:

void testingMethod() {
// some operations
someObject.foo();
CompletableFuture.runAsync(() -> {
// some other operations
someObject.foo();
});
}

我的测试示例,其中 someObject 被模拟:

@Test
public void testingMethodTest() {
testObject.testingMethod();

Mockito.verify(someObject, Mockito.times(2)).foo();
}

有没有办法在验证方法之前等待异步操作完成。或者这是一种不好的测试方法,在这种情况下您有什么建议?

最佳答案

问题归结为测试方法调用静态方法:CompletableFuture.runAsync()。一般来说,静态方法对模拟和断言几乎没有控制。

即使您在测试中使用 sleep(),您也无法断言 someObject.foo() 是否被异步调用。如果在调用线程上进行调用,测试仍然会通过。此外,使用 sleep() 会减慢测试速度,太短的 sleep() 会导致测试随机失败。

如果这确实是唯一的解决方案,您应该使用像 Awaitability 这样的库轮询直到断言得到满足,并超时。

有几种替代方法可以使您的代码更易于测试:

  1. 使 testingMethod() 返回一个 Future (正如您在评论中所想的那样):这不允许断言异步执行,但是它避免了太多等待;
  2. runAsync() 方法包装在您可以模拟的另一个服务中,并捕获参数;
  3. 如果您使用 Spring,请将 lambda 表达式移至另一个服务,并使用 @Async 注释的方法。这允许轻松地模拟和单元测试该服务,并消除直接调用 runAsync() 的负担;
  4. 使用自定义执行器,并将其传递给runAsync()

如果您使用 Spring,我建议使用第三种解决方案,因为它确实是最干净的,并且可以避免随处调用 runAsync() 使代码变得困惑。

选项 2 和 4 非常相似,只是改变了您必须模拟的内容。

如果您选择第四种解决方案,请按以下步骤操作:

更改测试类以使用自定义执行器:

class TestedObject {
private SomeObject someObject;
private Executor executor;

public TestedObject(SomeObject someObject, Executor executor) {
this.someObject = someObject;
this.executor = executor;
}

void testingMethod() {
// some operations
someObject.foo();
CompletableFuture.runAsync(() -> {
// some other operations
someObject.foo();
}, executor);
}
}

实现一个自定义的Executor,它只是捕获命令而不是运行它:

class CapturingExecutor implements Executor {

private Runnable command;

@Override
public void execute(Runnable command) {
this.command = command;
}

public Runnable getCommand() {
return command;
}
}

(您也可以@Mock Executor并使用ArgumentCaptor,但我认为这种方法更干净)

在测试中使用CapturingExecutor:

@RunWith(MockitoJUnitRunner.class)
public class TestedObjectTest {
@Mock
private SomeObject someObject;

private CapturingExecutor executor;

private TestedObject testObject;

@Before
public void before() {
executor = new CapturingExecutor();
testObject = new TestedObject(someObject, executor);
}

@Test
public void testingMethodTest() {
testObject.testingMethod();

verify(someObject).foo();
// make sure that we actually captured some command
assertNotNull(executor.getCommand());

// now actually run the command and check that it does what it is expected to do
executor.getCommand().run();
// Mockito still counts the previous call, hence the times(2).
// Not relevant if the lambda actually calls a different method.
verify(someObject, times(2)).foo();
}
}

关于java - 如何在单元测试中测试异步操作内的方法调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58520164/

56 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com