Java 如何正确模拟私有ExecutorService';s在JMockit中提交方法
我有一个包含私有ExecutorService实例的类。在这个类中,我有一个运行submit方法并捕获RejectedExecutionException的方法。但是,我在模拟ExecutorService实例以抛出异常以便完成测试覆盖时遇到困难。我使用的是JMockit1.45 我已经浏览了JMockit教程和其他网站;无论我使用@mock、@capture,甚至创建一个新的假类,它似乎都不起作用Java 如何正确模拟私有ExecutorService';s在JMockit中提交方法,java,java.util.concurrent,jmockit,Java,Java.util.concurrent,Jmockit,我有一个包含私有ExecutorService实例的类。在这个类中,我有一个运行submit方法并捕获RejectedExecutionException的方法。但是,我在模拟ExecutorService实例以抛出异常以便完成测试覆盖时遇到困难。我使用的是JMockit1.45 我已经浏览了JMockit教程和其他网站;无论我使用@mock、@capture,甚至创建一个新的假类,它似乎都不起作用 // Implemented Class: public class TaskRegister
// Implemented Class:
public class TaskRegister {
private ExecutorService executor;
public TaskRegister() {
this.executor = Executors.newFixedThreadPool(5);
}
public void executeTask(Runnable task) {
try {
this.executor.submit(task);
} catch (RejectedExecutionException e) {
System.out.println(e.getMessage);
}
}
}
// Unit Test Class:
public class TestTaskRegister {
@Tested
private TaskRegister tested;
private static int counter;
@Test // this works
public void runNormalTask() throws InterruptedException {
counter = 0;
Runnable mockTask = new Runnable() {
counter++;
}
tested.executeTask(mockTask);
Thread.sleep(100); // Allow executor to finish other thread.
assertEquals(1, counter);
}
@Test // this doesn't work, will have missing invocation error.
public void throwsError (@Capturing ExecutorService executor) throws InterruptedException {
counter = 0;
// somehow the tested class still runs the actual executor
// and not the mocked one.
new Expectations() {{
executor.submit((Runnable) any);
result = new RejectedExecutionException();
}};
Runnable mockTask = new Runnable() {
// some task
}
tested.executeTask(mockTask);
Thread.sleep(100);
assertEquals(0, counter);
}
}
我希望@capting能够截获真正的执行器实现,并在调用executor.submit时抛出异常,但它没有这样做。通过
@capting
进行模拟可能代价高昂,并且在某些情况下可能导致意外结果,因此(目前)所有java.
类都被排除在外。因此,java.util.concurrent.ThreadPoolExecutor
在本测试中不会被模拟(可以使用@mocked
)
实际上,RejectedExecutionException
异常永远不会发生(至少在ThreadPoolExecutor
中不会发生-可能仅在ForkJoinPool
中发生)。因此,这项测试不值得努力。事实上,由于该异常是一个RuntimeException
异常,您只需完全删除catch
块即可
这是(ab)使用模拟库时发生的一件坏事:人们有时使用它们来测试不可能的情况,因此编写无用的测试。您可以使用参数中的执行器创建一个新的构造函数。
因此,您的类将更具可配置性(因为目前您只能使用一种执行器),并且您可以通过使用模拟执行器手动实例化类来测试该类
public class TaskRegister {
private ExecutorService executor;
public TaskRegister(ExecutorService executor) {
this.executor = executor;
}
public TaskRegister() {
this(Executors.newFixedThreadPool(5));
}
public void executeTask(Runnable task) {
try {
this.executor.submit(task);
} catch (RejectedExecutionException e) {
System.out.println(e.getMessage);
}
}
}
谢谢你的澄清。然而,我尝试使用@mock和newexpections,但是测试总是会返回缺少的调用错误。我还可以问一下,如果我删除catch块,那么记录异常的最佳方式是什么?它应该与测试方法中的
@Mocked ThreadPoolExecutor-executor
参数一起工作。该运行时异常应作为任何其他异常记录,也就是说,在应用程序调用堆栈的中心位置和更高级别,所有未处理的异常都会被捕获,然后写入日志文件。这是我见过的真正的应用程序通常都会做的事情。低级代码应该允许将此类异常传播到调用方,除非它们真的有办法处理它们。我已成功使用ThreadPoolExecutor模拟executor,并将选择此答案作为最佳答案。此外,我还更倾向于认为我当前的类已经是一个基类,不需要额外的注入/配置。谢谢,好的。在JMockit 1.48中,@Capturing
对java.
类的限制被放宽,允许java.util.concurrent
,因此@Capturing-ExecutorService
现在模拟实现类(如ThreadPoolExecutor
)。该类已经能够按原样进行测试。就我个人而言,我不知道这样一个类是否有充分的理由存在,但公开实现细节(使用ExecutorService
)可能并不可取。另外,在我看来,构造函数注入通常是一个糟糕的选择——我更喜欢在使用DI时注入到带注释的字段中。其实这并不是一个实现细节,因为有几种类型的执行器具有可配置的线程数。强制用户只有一个可用的配置在我看来不是一个好的设计。它是taskrister
类的一个具体实现,因为它只允许在后台线程中执行任务。这个类是一个更高级别的抽象,它使用的线程/并发API是一个封装的实现细节。所以,我认为你们对类或API设计到底应该是什么感到困惑。我不想听取你们的建议。抛开DI的正确性不谈,我还尝试使用“@Autowired”注入执行器。虽然这当然可以消除模拟执行器的问题,但我现在面临的问题是,如果我想测试正确的执行,如何使用真正的实现。JMockit的“@Injectable”将模拟所有测试用例的执行器。纠正这种情况的一种方法是实际使用一个真正的执行器作为模拟,但这样我就不会真正测试实际的底层实现。。。