如何对使用 ProcessBuilder 和 Process 的 Java 方法进行单元测试?

发布于 2024-07-07 22:28:56 字数 961 浏览 9 评论 0原文

我有一个 Java 方法,它使用 ProcessBuilder 启动一个进程,并将其输出传输到一个字节数组中,然后在进程完成时返回其字节数组。

伪代码:

ProcessBuilder b = new ProcessBuilder("my.exe")
Process p = b.start();
... // get output from process, close process

对该方法进行单元测试的最佳方法是什么? 我还没有找到一种方法来模拟 ProcessBuilder (这是最终的),即使有令人难以置信的很棒的 JMockit,它给了我一个 NoClassDefFoundError :

java.lang.NoClassDefFoundError: test/MockProcessBuilder
    at java.lang.ProcessBuilder.<init>(ProcessBuilder.java)
    at mypackage.MyProcess.start(ReportReaderWrapperImpl.java:97)
    at test.MyProcessTest.testStart(ReportReaderWrapperImplTest.java:28)

有什么想法吗?


答案 - 正如 Olaf 所建议的,我最终将这些行重构为一个接口,

Process start(String param) throws IOException;

现在我将该接口的一个实例传递到我想要测试的类中(在其构造函数中),通常使用默认实现原始线路。 当我想测试时,我只需使用接口的模拟实现。 就像一个魅力,虽然我确实想知道我是否在这里过度交互......

I have a Java method which starts up a Process with ProcessBuilder, and pipes its output into a byte array, and then returns its byte array when the process is finished.

Pseudo-code:

ProcessBuilder b = new ProcessBuilder("my.exe")
Process p = b.start();
... // get output from process, close process

What would be the best way to go about unit testing this method? I haven't found a way to mock ProcessBuilder (it's final), even with the incredibly awesome JMockit, it gives me a NoClassDefFoundError:

java.lang.NoClassDefFoundError: test/MockProcessBuilder
    at java.lang.ProcessBuilder.<init>(ProcessBuilder.java)
    at mypackage.MyProcess.start(ReportReaderWrapperImpl.java:97)
    at test.MyProcessTest.testStart(ReportReaderWrapperImplTest.java:28)

Any thoughts?


Answer - As Olaf recommended, I ended up refactoring those lines to an interface

Process start(String param) throws IOException;

I now pass an instance of this interface into the class I wanted to test (in its constructor), normally using a default implementation with the original lines. When I want to test I simply use a mock implementation of the interface. Works like a charm, though I do wonder if I'm over-interfacing here...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

メ斷腸人バ 2024-07-14 22:28:56

保护自己免受嘲笑。 创建一个接口来执行您真正想要的操作(例如隐藏涉及外部进程的事实)或仅用于 Process 和 ProcessBuilder。

您不想测试 ProcessBuilder 和 Process 是否工作,只想测试您可以使用它们的输出。 当您创建接口时,一个简单的实现(可以轻松检查)委托给 ProcessBuilder 和 Process,另一个实现会模拟此行为。 稍后您甚至可能有另一个实现来完成您需要的操作,而无需启动另一个进程。

Shield yourself from the classes to be mocked. Create an interface either for doing what you really want (e.g. hiding the fact that external processes are involved at all) or only for Process and ProcessBuilder.

You don't want to test, that ProcessBuilder and Process work, only that you can work with their output. When you create an interface one trivial implementation (that can be inspected easily) delegates to ProcessBuilder and Process, another implementation mocks this behaviour. Later on you might even have another implementation that does what you need without starting another process.

萌︼了一个春 2024-07-14 22:28:56

使用较新版本的 JMockit (0.98+),您应该能够轻松模拟 JRE 类,例如 Process 和 ProcessBuilder。 因此,无需仅为测试而创建接口...

完整示例(使用 JMockit 1.16):

public class MyProcessTest
{
    public static class MyProcess {
        public byte[] run() throws IOException, InterruptedException {
            Process process = new ProcessBuilder("my.exe").start();
            process.waitFor();

            // Simplified example solution:
            InputStream processOutput = process.getInputStream();
            byte[] output = new byte[8192];
            int bytesRead = processOutput.read(output);

            return Arrays.copyOf(output, bytesRead);
        }
   }

    @Test
    public void runProcessReadingItsOutput(@Mocked final ProcessBuilder pb)
        throws Exception
    {
        byte[] expectedOutput = "mocked output".getBytes();
        final InputStream output = new ByteArrayInputStream(expectedOutput);
        new Expectations() {{ pb.start().getInputStream(); result = output; }};

        byte[] processOutput = new MyProcess().run();

        assertArrayEquals(expectedOutput, processOutput);
    }
}

With newer releases of JMockit (0.98+) you should be able to easily mock JRE classes like Process and ProcessBuilder. So, no need to create interfaces just for testing...

Full example (using JMockit 1.16):

public class MyProcessTest
{
    public static class MyProcess {
        public byte[] run() throws IOException, InterruptedException {
            Process process = new ProcessBuilder("my.exe").start();
            process.waitFor();

            // Simplified example solution:
            InputStream processOutput = process.getInputStream();
            byte[] output = new byte[8192];
            int bytesRead = processOutput.read(output);

            return Arrays.copyOf(output, bytesRead);
        }
   }

    @Test
    public void runProcessReadingItsOutput(@Mocked final ProcessBuilder pb)
        throws Exception
    {
        byte[] expectedOutput = "mocked output".getBytes();
        final InputStream output = new ByteArrayInputStream(expectedOutput);
        new Expectations() {{ pb.start().getInputStream(); result = output; }};

        byte[] processOutput = new MyProcess().run();

        assertArrayEquals(expectedOutput, processOutput);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文