如何在 JMockit 中正确模拟私有 ExecutorService 的提交方法
How to properly mock a private ExecutorService's submit method in JMockit
我有一个 class,其中包含一个私有 ExecutorService 实例。在 class 中,我有一个运行提交方法并捕获 RejectedExecutionException 的方法。但是,我在模拟 ExecutorService 实例以抛出异常以便完成测试覆盖时遇到了麻烦。我正在使用 JMockit 1.45。
我已经浏览过 JMockit 教程和其他站点;无论我使用@Mocked、@Capturing,还是创建一个新的假 class,它似乎都不起作用。
// 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);
}
}
我希望 @Capturing 拦截真正的执行程序实现并在 executor.submit 被调用时抛出异常,但它并没有这样做。
@Capturing
的模拟可能代价高昂,并且在某些情况下可能会导致意外结果,因此(目前)所有 java.*
类 都被排除在外。因此,java.util.concurrent.ThreadPoolExecutor
不会在此测试中被嘲笑(它可以与 @Mocked
一起)。
实际上,RejectedExecutionException
异常永远不会发生(至少 ThreadPoolExecutor
不会发生——可能只有 ForkJoinPool
才会发生。所以,这个测试不值得付出努力。事实上,由于该异常是 RuntimeException
,您可以简单地完全删除 catch
块。
这是(滥用)使用模拟库时发生的坏事之一:人们有时会使用它们来测试不可能的情况,因此编写无用的测试。
您可以使用参数 Executor
创建一个新的构造函数。
因此,您的 class 将更具可配置性(因为目前您只能使用一种执行程序)并且您将能够通过使用模拟执行程序手动实例化它来测试 class。
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);
}
}
}
我有一个 class,其中包含一个私有 ExecutorService 实例。在 class 中,我有一个运行提交方法并捕获 RejectedExecutionException 的方法。但是,我在模拟 ExecutorService 实例以抛出异常以便完成测试覆盖时遇到了麻烦。我正在使用 JMockit 1.45。
我已经浏览过 JMockit 教程和其他站点;无论我使用@Mocked、@Capturing,还是创建一个新的假 class,它似乎都不起作用。
// 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);
}
}
我希望 @Capturing 拦截真正的执行程序实现并在 executor.submit 被调用时抛出异常,但它并没有这样做。
@Capturing
的模拟可能代价高昂,并且在某些情况下可能会导致意外结果,因此(目前)所有 java.*
类 都被排除在外。因此,java.util.concurrent.ThreadPoolExecutor
不会在此测试中被嘲笑(它可以与 @Mocked
一起)。
实际上,RejectedExecutionException
异常永远不会发生(至少 ThreadPoolExecutor
不会发生——可能只有 ForkJoinPool
才会发生。所以,这个测试不值得付出努力。事实上,由于该异常是 RuntimeException
,您可以简单地完全删除 catch
块。
这是(滥用)使用模拟库时发生的坏事之一:人们有时会使用它们来测试不可能的情况,因此编写无用的测试。
您可以使用参数 Executor
创建一个新的构造函数。
因此,您的 class 将更具可配置性(因为目前您只能使用一种执行程序)并且您将能够通过使用模拟执行程序手动实例化它来测试 class。
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);
}
}
}