Come posso testare un metodo Java che utilizza ProcessBuilder e Process?
-
03-07-2019 - |
Domanda
Ho un metodo Java che avvia un processo con ProcessBuilder e ne convoglia l'output in un array di byte, quindi restituisce il suo array di byte al termine del processo.
pseudo-codice:
ProcessBuilder b = new ProcessBuilder("my.exe")
Process p = b.start();
... // get output from process, close process
Quale sarebbe il modo migliore per testare l'unità di questo metodo? Non ho trovato il modo di deridere ProcessBuilder (è definitivo), anche con l'incredibile JMockit , mi dà un 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)
Qualche idea?
Risposta - Come raccomandato da Olaf, ho finito per refactoring quelle linee in un'interfaccia
Process start(String param) throws IOException;
Passo ora un'istanza di questa interfaccia nella classe che volevo testare (nel suo costruttore), normalmente usando un'implementazione predefinita con le linee originali. Quando voglio fare un test, utilizzo semplicemente un'implementazione fittizia dell'interfaccia. Funziona come un incantesimo, anche se mi chiedo se sto interfacciando troppo qui ...
Soluzione
Proteggiti dalle classi da deridere. Crea un'interfaccia per fare ciò che vuoi veramente (ad esempio nascondere il fatto che i processi esterni sono coinvolti) o solo per Process e ProcessBuilder.
Non vuoi testare che ProcessBuilder e Process funzionano, ma solo che puoi lavorare con il loro output. Quando si crea un'interfaccia, un'implementazione banale (che può essere ispezionata facilmente) delega a ProcessBuilder e Process, un'altra implementazione prende in giro questo comportamento. In seguito potresti persino avere un'altra implementazione che fa ciò di cui hai bisogno senza avviare un altro processo.
Altri suggerimenti
Con le versioni più recenti di JMockit (0.98+) dovresti essere in grado di deridere facilmente le classi JRE come Process e ProcessBuilder. Quindi, non è necessario creare interfacce solo per i test ...
Esempio completo (usando 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);
}
}