尝试使用 PowerMockito 模拟 ProcessBuilder 的构造函数时出错
Error trying to mock constructor for ProcessBuilder using PowerMockito
我正在尝试模拟 ProcessBuilder 的构造函数。问题是当调用构造函数时 return null.
Class代码:
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());
}
}
因此,当执行 new ProcessBuilder(command) 时,结果为空。之后,当调用 processBuilder.start() 时会抛出异常。我尝试了一些方法来模拟该构造函数。
有什么想法吗?
在这种情况下我会尝试一些事情:
将PowerMockito.mock改为Mockito.mock(应该可以正常使用)。
尝试 whenNew 传递正确的参数(或至少 Matchers.anyString())作为参数。
如果您创建 NtpServerUtil 作为实例并监视它会怎样?
private ProcessBuilder processBuilder;
private NtpServerUtil ntpServerUtil;
@Before
public void init(){
processBuilder = Mockito.mock(ProcessBuilder.class);
ntpServerUtil = Mockito.spy(NtpServerUtil.class);
MockitoAnnotations.initMocks(this);
PowerMockito.whenNew(ProcessBuilder.class).withArguments(Mockito.anyString()).thenReturn(processBuilder);
}
@Test
public void testEnableNtp() throws Exception {
...
NtpServerUtil.enable();
...
}
Ps.: 你忘了在那里初始化你的模拟。也许它也有用。
在我的团队中,禁止将 PowerMock 用于任何新编写的代码,因为它表明代码编写不当(无法测试)。如果你把它作为一个规则,你通常会得到更清晰的代码。
所以对于你的情况,你的问题是你正在构建 ProcessBuilder
的具体新实例,但你的代码并不真正关心它是否在这个 class 的具体实例上运行,或者在为您需要的所有方法定义契约的接口上。实际上你只使用了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
的实例,您的 processStarterProvider
在测试中可能看起来像这样:
Process mockedProcess = ...
myInstance.processStarterProvider = (commandArr) -> () -> mockedProcess;
在Function<String[], Process>
的情况下就更简单了:
Process mockedProcess = ...
myInstance.processProvider = (commandArr) -> mockedProcess;
我正在尝试模拟 ProcessBuilder 的构造函数。问题是当调用构造函数时 return null.
Class代码:
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());
}
}
因此,当执行 new ProcessBuilder(command) 时,结果为空。之后,当调用 processBuilder.start() 时会抛出异常。我尝试了一些方法来模拟该构造函数。 有什么想法吗?
在这种情况下我会尝试一些事情:
将PowerMockito.mock改为Mockito.mock(应该可以正常使用)。
尝试 whenNew 传递正确的参数(或至少 Matchers.anyString())作为参数。
如果您创建 NtpServerUtil 作为实例并监视它会怎样?
private ProcessBuilder processBuilder; private NtpServerUtil ntpServerUtil; @Before public void init(){ processBuilder = Mockito.mock(ProcessBuilder.class); ntpServerUtil = Mockito.spy(NtpServerUtil.class); MockitoAnnotations.initMocks(this); PowerMockito.whenNew(ProcessBuilder.class).withArguments(Mockito.anyString()).thenReturn(processBuilder); } @Test public void testEnableNtp() throws Exception { ... NtpServerUtil.enable(); ... }
Ps.: 你忘了在那里初始化你的模拟。也许它也有用。
在我的团队中,禁止将 PowerMock 用于任何新编写的代码,因为它表明代码编写不当(无法测试)。如果你把它作为一个规则,你通常会得到更清晰的代码。
所以对于你的情况,你的问题是你正在构建 ProcessBuilder
的具体新实例,但你的代码并不真正关心它是否在这个 class 的具体实例上运行,或者在为您需要的所有方法定义契约的接口上。实际上你只使用了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
的实例,您的 processStarterProvider
在测试中可能看起来像这样:
Process mockedProcess = ...
myInstance.processStarterProvider = (commandArr) -> () -> mockedProcess;
在Function<String[], Process>
的情况下就更简单了:
Process mockedProcess = ...
myInstance.processProvider = (commandArr) -> mockedProcess;