问题描述
我正在尝试模拟ProcessBuilder的构造函数.问题在于调用构造函数时,它返回null.
I am trying to mock the constructor for ProcessBuilder. The problem is that when the constructor is called it return 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(command)时,结果为null.之后,调用processBuilder.start()时会引发异常.我尝试了一些模拟该构造函数的方法.有什么想法吗?
So, when it goes for new ProcessBuilder(command), the result is null. After that, when processBuilder.start() is called an exception is thrown. I tried some methods for mocking that constructor.Any ideas, please?
推荐答案
在我的团队中,严禁将PowerMock用于任何新编写的代码,因为它表明编写不良(无法调试)的代码.如果将其作为规则,通常会得到清晰得多的代码.
In my team it is prohibited to use PowerMock for any newly written code as it is an indicator of poorly written (untestable) code. If you take it as a rule you will normally end up with much cleaner code.
因此,对于您的情况,您的问题是您正在构造ProcessBuilder
的具体新实例,但是您的代码并不真正在乎它是在此类的具体实例上还是在为以下内容定义合同的接口上运行:您需要的所有方法.实际上,您仅使用start
方法,因此首先定义一个相应的接口(Java尚未定义它确实很糟糕):
So for your case, your problem is that you are constructing a concrete new instance of ProcessBuilder
, but your code does not really care if it operates on a concrete instance of this class or on an interface that defines the contract for all the methods that you need. Effectively you only use the start
method, so define a corresponding interface first (it is really terrible that Java does not define it already):
public interface ProcessStarter {
Process start() throws IOException;
}
然后将包装可见字段或添加到您的方法中,如果您不喜欢将包装可见字段用于测试目的,则将其类型为Function<String[], ProcessStarter> processStarterProvider
并在您的代码中使用:Function<String[], ProcessStarter> processStarterProvider
/p>
Then add a package visible field or an argument to your method, if you do not like package visible fields for testing purposes, of the sort: Function<String[], ProcessStarter> processStarterProvider
and use it in your code:
ProcessStarter starter = processStarterProvider.apply(commandArr);
Process proc = starter.start();
最后,提供默认的实现.如果您想去一个领域:
Finally, provide the default implementation. If you go for a field:
Function<String[], ProcessStarter> processStarterProvider = (commandArr) -> {
ProcessBuilder pb = new ProcessBuilder(commandArr);
pb.redirectErrorStream(true);
return (ProcessStarter) pb::start;
};
现在您不需要任何PowerMock,并且可以使用简单的模拟了!
Now you do not need any PowerMock and can do with a trivial mock!
对此进行了重新考虑,即使上述带有接口的方法通常适用,我还是建议在整个过程中都使用它,在这种特殊情况下,如果您愿意将IOException包装到运行时中,那么您可以使用单个Function<String[], Process>
接口和以下默认实现:
Having had a second thought about it, even though the above approach with an interface is generally applicable and I would advise to use it throughout, in this particular case you and if you are happy to wrap your IOException into a runtime one, then you can go with a single Function<String[], Process>
interface and the following default implementation:
Function<String[], Process> processProvider = (commandArr) -> {
ProcessBuilder pb = new ProcessBuilder(commandArr);
pb.redirectErrorStream(true);
try {
return pb.start();
} catch (IOException ex) {
throw new RuntimeException(ex);
};
它更短,并且可以像以上那样测试.为了清楚起见,我可能会坚持使用以上较长的代码.
It is shorter and as testable as the above one. For clarity I would probably stick to the above longer one though.
在这两种情况下,在测试中,您都将需要以某种方式提供Process
的实例,该实例本身也未实现任何接口(不良的设计),因此可能需要类似的包装器接口,如上所示.但是,给定Process
的实例,您的processStarterProvider
在测试中可能看起来像这样:
In both cases, in test you will need to come up with an instance of Process
somehow, which itself also does not implement any interfaces (poor design) and so might need a similar wrapper interface as shown above. But, given an instance of Process
your processStarterProvider
may look like this in test:
Process mockedProcess = ...
myInstance.processStarterProvider = (commandArr) -> () -> mockedProcess;
在Function<String[], Process>
情况下更简单:
Process mockedProcess = ...
myInstance.processProvider = (commandArr) -> mockedProcess;
这篇关于尝试使用PowerMockito模拟ProcessBuilder的构造函数时出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!