Java 如何正确模拟私有ExecutorService';s在JMockit中提交方法

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

我有一个包含私有ExecutorService实例的类。在这个类中,我有一个运行submit方法并捕获RejectedExecutionException的方法。但是,我在模拟ExecutorService实例以抛出异常以便完成测试覆盖时遇到困难。我使用的是JMockit1.45

我已经浏览了JMockit教程和其他网站;无论我使用@mock、@capture,甚至创建一个新的假类,它似乎都不起作用

// 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”将模拟所有测试用例的执行器。纠正这种情况的一种方法是实际使用一个真正的执行器作为模拟,但这样我就不会真正测试实际的底层实现。。。