¿Cómo probar métodos que llaman a System.exit()?

Resuelto Chris Conway asked hace 16 años • 19 respuestas

Tengo algunos métodos que deberían invocar System.exit()ciertas entradas. Desafortunadamente, probar estos casos hace que JUnit finalice. Poner las llamadas al método en un nuevo subproceso no parece ayudar, ya que System.exit()finaliza la JVM, no solo el subproceso actual. ¿Existen patrones comunes 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 estoy intentando probar dentro de JUnit. ¿Quizás JUnit simplemente no es la herramienta adecuada para el trabajo? Se aceptan sugerencias de herramientas de pruebas de regresión complementarias (preferiblemente algo que se integre bien con JUnit y EclEmma).

Chris Conway avatar Nov 21 '08 23:11 Chris Conway
Aceptado

De hecho, Derkeiler.com sugiere:

  • Por qué System.exit()?

    En lugar de terminar con System.exit(whateverValue), ¿por qué no lanzar una excepción no marcada? En uso normal, se desplazará hasta el último receptor de la JVM y cerrará su script (a menos que decida capturarlo 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.

  • Evite System.exit()salir realmente de la JVM:

    Intente modificar TestCase para que se ejecute con un administrador de seguridad que impida llamar a System.exit y luego detecte 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 sistema , una colección de reglas JUnit(4.9+) para probar código que usa java.lang.System.
Esto fue mencionado inicialmente por Stefan Birkner en su respuesta de diciembre de 2011.

System.exit(…)

Utilice la ExpectedSystemExitregla para verificar que System.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);
    }
}

2023: Emmanuel Bourg informa en los comentarios :

Para que esto funcione con Java 21, esta propiedad del sistema debe estar configurada-Djava.security.manager=allow

VonC avatar Nov 21 '2008 16:11 VonC

La biblioteca System Lambda tiene un método catchSystemExit. Con esta regla puedes probar el código que llama 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 System Rules tiene una regla JUnit llamada ExpectedSystemExit. Con esta regla puedes probar el código que llama 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.

Stefan Birkner avatar Dec 28 '2011 16:12 Stefan Birkner

¿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.

EricSchaefer avatar Nov 21 '2008 16:11 EricSchaefer

De hecho, puedes simular o eliminar el System.exitmétodo en una prueba JUnit.

Por ejemplo, usando JMockit puedes 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 (utilizando 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)");
}
Rogério avatar Jul 26 '2009 18:07 Rogério