尝试使用PowerMockito模拟ProcessBuilder的构造函数时出错

时间:2018-04-21 01:30:56

标签: java junit processbuilder powermockito

我正在尝试模拟ProcessBuilder的构造函数。问题是,当调用构造函数时,它返回null。

班级代码:

 public static void enable() throws IOException, InterruptedException {


        logger.info("Enable NTP server...");

        String ntpAddress = AppConfig.getInstance().getString(AppConfig.NTP_SERVER, "");
        AppConfig.getInstance().getBoolean(AppConfig.NTP_ENABLED, true);
        String enableNtp = "chkconfig ntpd on " + SEPARATOR + " service ntpd stop " + SEPARATOR + " ntpdate " + ntpAddress + " " + SEPARATOR + " service ntpd start";

        String[] commandArr = {"bash", "-c", enableNtp};

        ProcessBuilder pb = new ProcessBuilder(commandArr);
        pb.redirectErrorStream(true);
        Process proc = pb.start();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(
                proc.getInputStream()))) {

            String line;
            while ((line = in.readLine()) != null) {
                logger.info(line);
            }
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Error while trying to enable NTP Server");
        }

        proc.waitFor();
        proc.destroy();
        logger.info("NTP server has been enabled");


    }

测试代码:

@RunWith(PowerMockRunner.class)
@PrepareForTest({NtpServerUtil.class, ProcessBuilder.class})
public class NtpServerUtilTest extends AbstractDbTest {

    @Test
    public void testEnableNtp() throws Exception {
        ProcessBuilder pb = PowerMockito.mock(ProcessBuilder.class);
        PowerMockito.whenNew(ProcessBuilder.class).withAnyArguments().thenReturn(pb);

        NtpServerUtil.enable();
        PowerMockito.verifyNew(ProcessBuilder.class).withArguments(Matchers.anyString());
    }

}

因此,当它用于新的ProcessBuilder(命令)时,结果为null。之后,当调用processBuilder.start()时,抛出异常。我尝试了一些模拟构造函数的方法。 有什么想法吗?

2 个答案:

答案 0 :(得分:0)

在这种情况下,我会尝试一些事情:

  1. 将PowerMockito.mock更改为Mockito.mock(它应该正常工作)。

  2. 尝试whenNew传递正确的参数(或者至少是Matchers.anyString())作为参数。

  3. 如果您将NtpServerUtil创建为实例并监视它,该怎么办?

    CREATE TABLE t (id binary(16) PRIMARY KEY);
    
    INSERT INTO t VALUES(UUID_TO_BIN(UUID(), true));
    INSERT INTO t VALUES(UUID_TO_BIN(UUID(), true));
    INSERT INTO t VALUES(UUID_TO_BIN(UUID(), true));
    
    SELECT *, BIN_TO_UUID(id) FROM t;
    
  4. Ps。:你忘了在那里做你的嘲笑。也许它也很有用。

答案 1 :(得分:0)

在我的团队中,禁止将PowerMock用于任何新编写的代码,因为它表示编写不良(不可测试)的代码。如果你把它作为一项规则,你通常会得到更清晰的代码。

因此,对于您的情况,您的问题是您正在构建ProcessBuilder的具体新实例,但您的代码并不关心它是否在此类的具体实例上或在定义该类的接口上运行签订所需的所有方法。实际上你只使用start方法,所以首先定义一个相应的接口(Java确实没有定义它真的很糟糕):

public interface ProcessStarter {
    Process start() throws IOException;
}

然后在您的方法中添加一个包可见字段参数,如果您不喜欢用于测试目的的包可见字段,请添加以下类型:Function<String[], ProcessStarter> processStarterProvider并在代码中使用它:

ProcessStarter starter = processStarterProvider.apply(commandArr);
Process proc = starter.start();

最后,提供默认实现。如果你去一个领域:

Function<String[], ProcessStarter> processStarterProvider = (commandArr) -> {
    ProcessBuilder pb = new ProcessBuilder(commandArr);
    pb.redirectErrorStream(true);
    return (ProcessStarter) pb::start;
};

现在你不需要任何PowerMock,可以做一个琐碎的模拟!

第二次想到它,即使上面的接口方法通常适用,我建议在整个过程中使用它,在这种特殊情况下,如果您愿意将IOException包装到运行时,那么你可以使用单个Function<String[], Process>接口和以下默认实现:

Function<String[], Process> processProvider = (commandArr) -> {
    ProcessBuilder pb = new ProcessBuilder(commandArr);
    pb.redirectErrorStream(true);
    try {
        return pb.start();
    } catch (IOException ex) {
        throw new RuntimeException(ex);
};

它比上述更短且可测试。为清楚起见,我可能会坚持使用上面较长的一个。

在这两种情况下,在测试中你需要以某种方式提出Process的实例,它本身也没有实现任何接口(设计不佳),因此可能需要一个类似的包装器接口,如上所示。但是,如果Process的{​​{1}}实例,您的processStarterProvider可能会在测试中看起来像这样:

Process mockedProcess = ...

myInstance.processStarterProvider = (commandArr) -> () -> mockedProcess;

Function<String[], Process>的情况下,它更简单:

Process mockedProcess = ...

myInstance.processProvider = (commandArr) -> mockedProcess;