chris conway
Tengo algunos métodos que deberían llamar System.exit()
en ciertas entradas. Desafortunadamente, probar estos casos hace que JUnit finalice. Poner las llamadas al método en un nuevo Thread no parece ayudar, ya que System.exit()
termina la JVM, no solo el hilo actual. ¿Hay algún patrón común para lidiar con esto? Por ejemplo, ¿puedo sustituir un talón por System.exit()
?
La clase en cuestión es en realidad una herramienta de línea de comandos que intento probar dentro de JUnit. ¿Quizás JUnit simplemente no es la herramienta adecuada para el trabajo? Se agradecen las sugerencias de herramientas de prueba de regresión complementarias (preferiblemente algo que se integre bien con JUnit y EclEmma).
VonC
En efecto, Derkeiler.com sugiere:
- Por qué
System.exit()
?
En lugar de terminar con System.exit(whateverValue), ¿por qué no lanzar una excepción no verificada? En el uso normal, se desplazará hasta el último receptor de JVM y cerrará su script (a menos que decida atraparlo en algún lugar del camino, lo que podría ser útil algún día).
En el escenario JUnit, será capturado por el marco JUnit, que informará que tal o cual prueba falló y pasará sin problemas a la siguiente.
- Prevenir
System.exit()
para salir realmente de la JVM:
Intente modificar TestCase para que se ejecute con un administrador de seguridad que evite llamar a System.exit, luego capture SecurityException.
public class NoExitTestCase extends TestCase
{
protected static class ExitException extends SecurityException
{
public final int status;
public ExitException(int status)
{
super("There is no escape!");
this.status = status;
}
}
private static class NoExitSecurityManager extends SecurityManager
{
@Override
public void checkPermission(Permission perm)
{
// allow anything.
}
@Override
public void checkPermission(Permission perm, Object context)
{
// allow anything.
}
@Override
public void checkExit(int status)
{
super.checkExit(status);
throw new ExitException(status);
}
}
@Override
protected void setUp() throws Exception
{
super.setUp();
System.setSecurityManager(new NoExitSecurityManager());
}
@Override
protected void tearDown() throws Exception
{
System.setSecurityManager(null); // or save and restore original
super.tearDown();
}
public void testNoExit() throws Exception
{
System.out.println("Printing works");
}
public void testExit() throws Exception
{
try
{
System.exit(42);
} catch (ExitException e)
{
assertEquals("Exit status", 42, e.status);
}
}
}
Actualización de diciembre de 2012:
Will propone en los comentarios usando Reglas del sistemauna colección de reglas JUnit(4.9+) para probar código que utiliza java.lang.System
.
Esto fue mencionado inicialmente por Stefan Birkner en su respuesta de diciembre de 2011.
System.exit(…)
Utilizar el
ExpectedSystemExit
regla para comprobar queSystem.exit(…)
se llama.
También puede verificar el estado de salida.
Por ejemplo:
public void MyTest {
@Rule
public final ExpectedSystemExit exit = ExpectedSystemExit.none();
@Test
public void noSystemExit() {
//passes
}
@Test
public void systemExitWithArbitraryStatusCode() {
exit.expectSystemExit();
System.exit(0);
}
@Test
public void systemExitWithSelectedStatusCode0() {
exit.expectSystemExitWithStatus(0);
System.exit(0);
}
}
-
No me gustó la primera respuesta, pero la segunda es genial: no me había metido con los gerentes de seguridad y asumí que eran más complicados que eso. Sin embargo, ¿cómo prueba el administrador de seguridad/mecanismo de prueba?
– Bill K.
21 de noviembre de 2008 a las 17:25
-
¡Asegúrese de que el desmontaje se ejecute correctamente, de lo contrario, su prueba fallará en un corredor como Eclipse porque la aplicación JUnit no puede salir! 🙂
– MetroidFan2002
23 de noviembre de 2008 a las 7:00
-
No me gusta la solución usando el administrador de seguridad. Me parece un truco solo para probarlo.
– Nicolai Reuschling
20 de mayo de 2009 a las 12:47
-
Si está utilizando junit 4.7 o superior, deje que una biblioteca se encargue de capturar sus llamadas System.exit. Reglas del sistema – stefanbirkner.github.com/system-rules
– Voluntad
17 de diciembre de 2012 a las 17:34
-
“En lugar de terminar con System.exit (cualquier valor), ¿por qué no lanzar una excepción sin marcar?” — porque estoy usando un marco de procesamiento de argumentos de línea de comando que llama
System.exit
siempre que se proporcione un argumento de línea de comando no válido.– Adam Parkin
7 ago 2013 a las 16:01
Stefan Birkner
La biblioteca Sistema lambda tiene un método catchSystemExit
.Con esta regla, puede probar el código, que llama a System.exit(…):
public class MyTest {
@Test
public void systemExitWithArbitraryStatusCode() {
SystemLambda.catchSystemExit(() -> {
//the code under test, which calls System.exit(...);
});
}
@Test
public void systemExitWithSelectedStatusCode0() {
int status = SystemLambda.catchSystemExit(() -> {
//the code under test, which calls System.exit(0);
});
assertEquals(0, status);
}
}
Para Java 5 a 7 la biblioteca Reglas del sistema tiene una regla JUnit llamada ExpectedSystemExit. Con esta regla, puede probar el código, que llama a System.exit (…):
public class MyTest {
@Rule
public final ExpectedSystemExit exit = ExpectedSystemExit.none();
@Test
public void systemExitWithArbitraryStatusCode() {
exit.expectSystemExit();
//the code under test, which calls System.exit(...);
}
@Test
public void systemExitWithSelectedStatusCode0() {
exit.expectSystemExitWithStatus(0);
//the code under test, which calls System.exit(0);
}
}
Divulgación completa: soy el autor de ambas bibliotecas.
-
Perfecto. Elegante. No tuve que cambiar una puntada de mi código original o jugar con el administrador de seguridad. ¡Esta debería ser la mejor respuesta!
– Voluntad
17 dic 2012 a las 17:35
-
Esta debería ser la mejor respuesta. En lugar de un debate interminable sobre el uso adecuado de System.exit, directo al grano. Además, una solución flexible que se adhiere a la propia JUnit para que no tengamos que reinventar la rueda o equivocarnos con el administrador de seguridad.
– L.Holanda
21/10/2015 a las 18:58
-
@LeoHolanda ¿Esta solución no sufre el mismo “problema” que usó para justificar un voto negativo en mi respuesta? Además, ¿qué pasa con los usuarios de TestNG?
– Rogerio
21/10/2015 a las 20:15
-
@LeoHolanda Tienes razón; Mirando la implementación de la regla, ahora veo que usa un administrador de seguridad personalizado que lanza una excepción cuando ocurre la llamada para la verificación de “salida del sistema”, por lo tanto, finaliza la prueba. La prueba de ejemplo agregada en mi respuesta satisface ambos requisitos (verificación adecuada con System.exit siendo llamado/no llamado), por cierto. El uso de
ExpectedSystemRule
es agradable, por supuesto; el problema es que requiere una biblioteca adicional de terceros que proporciona muy poca utilidad en el mundo real y es específica de JUnit.– Rogerio
22/10/2015 a las 16:15
-
Hay
exit.checkAssertionAfterwards()
.– Stefan Birkner
3 de noviembre de 2017 a las 15:08
¿Qué tal inyectar un “ExitManager” en estos métodos:
public interface ExitManager {
void exit(int exitCode);
}
public class ExitManagerImpl implements ExitManager {
public void exit(int exitCode) {
System.exit(exitCode);
}
}
public class ExitManagerMock implements ExitManager {
public bool exitWasCalled;
public int exitCode;
public void exit(int exitCode) {
exitWasCalled = true;
this.exitCode = exitCode;
}
}
public class MethodsCallExit {
public void CallsExit(ExitManager exitManager) {
// whatever
if (foo) {
exitManager.exit(42);
}
// whatever
}
}
El código de producción usa ExitManagerImpl y el código de prueba usa ExitManagerMock y puede verificar si se llamó a exit() y con qué código de salida.
-
+1 Buena solución. También es fácil de implementar si está utilizando Spring, ya que ExitManager se convierte en un componente simple. Solo tenga en cuenta que debe asegurarse de que su código no continúe ejecutándose después de la llamada exitManager.exit(). Cuando su código se prueba con un ExitManager simulado, el código en realidad no se cerrará después de la llamada a exitManager.exit.
– Joman68
28 de agosto de 2019 a las 23:32
rogério
en realidad poder burlarse o apagar el System.exit
método, en una prueba JUnit.
Por ejemplo, usando JMockit podrías escribir (también hay otras formas):
@Test
public void mockSystemExit(@Mocked("exit") System mockSystem)
{
// Called by code under test:
System.exit(); // will not exit the program
}
EDITAR: prueba alternativa (usando la última API de JMockit) que no permite que se ejecute ningún código después de una llamada a System.exit(n)
:
@Test(expected = EOFException.class)
public void checkingForSystemExitWhileNotAllowingCodeToContinueToRun() {
new Expectations(System.class) {{ System.exit(anyInt); result = new EOFException(); }};
// From the code under test:
System.exit(1);
System.out.println("This will never run (and not exit either)");
}
scott bale
Un truco que usamos en nuestra base de código fue encapsular la llamada a System.exit() en un impl Runnable, que el método en cuestión usó de manera predeterminada. Para la prueba unitaria, configuramos un Runnable simulado diferente. Algo como esto:
private static final Runnable DEFAULT_ACTION = new Runnable(){
public void run(){
System.exit(0);
}
};
public void foo(){
this.foo(DEFAULT_ACTION);
}
/* package-visible only for unit testing */
void foo(Runnable action){
// ...some stuff...
action.run();
}
…y el método de prueba JUnit…
public void testFoo(){
final AtomicBoolean actionWasCalled = new AtomicBoolean(false);
fooObject.foo(new Runnable(){
public void run(){
actionWasCalled.set(true);
}
});
assertTrue(actionWasCalled.get());
}
-
¿Es esto lo que llaman inyección de dependencia?
– Tomas Ahle
22 de agosto de 2010 a las 21:10
-
Este ejemplo, tal como está escrito, es una especie de inyección de dependencia a medias: la dependencia se pasa al método foo visible en el paquete (ya sea mediante el método foo público o la prueba unitaria), pero la clase principal aún codifica la implementación Runnable predeterminada.
–Scott Bale
23 de agosto de 2010 a las 19:28
jeffrey fredrick
Me gustan algunas de las respuestas ya dadas, pero quería demostrar una técnica diferente que a menudo es útil cuando se prueba el código heredado. Código dado como:
public class Foo {
public void bar(int i) {
if (i < 0) {
System.exit(i);
}
}
}
Puede hacer una refactorización segura para crear un método que envuelva la llamada System.exit:
public class Foo {
public void bar(int i) {
if (i < 0) {
exit(i);
}
}
void exit(int i) {
System.exit(i);
}
}
Luego, puede crear una falsificación para su prueba que anule la salida:
public class TestFoo extends TestCase {
public void testShouldExitWithNegativeNumbers() {
TestFoo foo = new TestFoo();
foo.bar(-1);
assertTrue(foo.exitCalled);
assertEquals(-1, foo.exitValue);
}
private class TestFoo extends Foo {
boolean exitCalled;
int exitValue;
void exit(int i) {
exitCalled = true;
exitValue = i;
}
}
Esta es una técnica genérica para sustituir el comportamiento por casos de prueba, y la uso todo el tiempo cuando refactorizo código heredado. Por lo general, no es donde voy a dejar las cosas, sino un paso intermedio para poner a prueba el código existente.
-
¿Es esto lo que llaman inyección de dependencia?
– Tomas Ahle
22 de agosto de 2010 a las 21:10
-
Este ejemplo, tal como está escrito, es una especie de inyección de dependencia a medias: la dependencia se pasa al método foo visible en el paquete (ya sea mediante el método foo público o la prueba unitaria), pero la clase principal aún codifica la implementación Runnable predeterminada.
–Scott Bale
23 de agosto de 2010 a las 19:28
Jeow Li Huan
Para que la respuesta de VonC se ejecute en JUnit 4, modifiqué el código de la siguiente manera
protected static class ExitException extends SecurityException {
private static final long serialVersionUID = -1982617086752946683L;
public final int status;
public ExitException(int status) {
super("There is no escape!");
this.status = status;
}
}
private static class NoExitSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
// allow anything.
}
@Override
public void checkPermission(Permission perm, Object context) {
// allow anything.
}
@Override
public void checkExit(int status) {
super.checkExit(status);
throw new ExitException(status);
}
}
private SecurityManager securityManager;
@Before
public void setUp() {
securityManager = System.getSecurityManager();
System.setSecurityManager(new NoExitSecurityManager());
}
@After
public void tearDown() {
System.setSecurityManager(securityManager);
}
Tengo curiosidad por saber por qué una función llamaría a System.exit()…
– Thomas Owens
21 de noviembre de 2008 a las 16:40
Sigo pensando que, en ese caso, debería haber una forma más agradable de regresar desde la aplicación en lugar de System.exit().
– Thomas Owens
21 de noviembre de 2008 a las 16:57
Si está probando main(), entonces tiene perfecto sentido llamar a System.exit(). Tenemos el requisito de que, en caso de error, un proceso por lotes debe salir con 1 y si tiene éxito, salir con 0.
– Mateo Farwell
5 de abril de 2011 a las 10:43
No estoy de acuerdo con todos los que dicen
System.exit()
es malo: su programa debería fallar rápidamente. Lanzar una excepción simplemente prolonga una aplicación en un estado no válido en situaciones en las que un desarrollador desea salir y generará errores falsos.– Sridhar Sarnobat
24/10/2017 a las 17:10
@ThomasOwens Tengo curiosidad por saber por qué cree que System.exit es malo o no es necesario. Dígame cómo devolver un lote con el código de salida 20 o 30 desde una aplicación Java.
– aturdidor
2 de octubre de 2020 a las 4:59