Wie testet man Methoden, die System.exit() aufrufen?
Lesezeit: 9 Minuten
Chris Conway
Ich habe ein paar Methoden, die aufrufen sollten System.exit() bei bestimmten Eingängen. Leider führt das Testen dieser Fälle dazu, dass JUnit beendet wird! Die Methodenaufrufe in einen neuen Thread zu packen scheint da nicht zu helfen System.exit() beendet die JVM, nicht nur den aktuellen Thread. Gibt es gängige Muster für den Umgang damit? Kann ich zum Beispiel einen Stub ersetzen System.exit()?
Die fragliche Klasse ist eigentlich ein Befehlszeilentool, das ich in JUnit zu testen versuche. Vielleicht ist JUnit einfach nicht das richtige Werkzeug für den Job? Vorschläge für ergänzende Regressionstest-Tools sind willkommen (vorzugsweise etwas, das sich gut in JUnit und EclEmma integrieren lässt).
Ich bin neugierig, warum eine Funktion jemals System.exit() aufrufen würde …
– Thomas Owens
21. November 2008 um 16:40 Uhr
Ich denke immer noch, dass es in diesem Fall einen schöneren Weg geben sollte, von der Anwendung zurückzukehren, als System.exit().
– Thomas Owens
21. November 2008 um 16:57 Uhr
Wenn Sie main() testen, ist es absolut sinnvoll, System.exit() aufzurufen. Wir haben die Anforderung, dass ein Batch-Prozess bei einem Fehler mit 1 und bei Erfolg mit 0 beendet werden soll.
– Matthäus Farwell
5. April 2011 um 10:43 Uhr
Ich stimme jedem zu, der sagt System.exit() ist schlecht – Ihr Programm sollte schnell fehlschlagen. Das Auslösen einer Ausnahme verlängert lediglich eine Anwendung in einem ungültigen Zustand in Situationen, in denen ein Entwickler beenden möchte und falsche Fehler ausgibt.
– Sridhar Sarnobat
24. Oktober 2017 um 17:10 Uhr
@ThomasOwens Ich bin gespannt, warum Sie System.exit für schlecht oder nicht erforderlich halten. Bitte sagen Sie mir, wie ich einen Batch mit dem Exit-Code 20 oder 30 aus einer Java-Anwendung zurückgeben kann.
Anstatt mit System.exit(whateverValue) zu enden, warum nicht eine ungeprüfte Ausnahme auslösen? Bei normaler Verwendung driftet es bis zum letzten Fang der JVM und beendet Ihr Skript (es sei denn, Sie entscheiden sich, es irgendwo auf dem Weg abzufangen, was eines Tages nützlich sein könnte).
Im JUnit-Szenario wird es vom JUnit-Framework abgefangen, das meldet, dass der und der Test fehlgeschlagen ist, und reibungslos zum nächsten übergeht.
Verhindern System.exit() um die JVM tatsächlich zu beenden:
Versuchen Sie, den Testfall so zu ändern, dass er mit einem Sicherheitsmanager ausgeführt wird, der das Aufrufen von System.exit verhindert, und fangen Sie dann die SecurityException ab.
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);
}
}
}
Update Dezember 2012:
Will schlägt in den Kommentaren die Verwendung vor Systemregelneine Sammlung von JUnit(4.9+)-Regeln zum Testen von Code, der verwendet java.lang.System.
Dies wurde ursprünglich von erwähnt Stefan Birkner in seiner Antwort im Dezember 2011.
System.exit(…)
Verwenden Sie die ExpectedSystemExit Regel, um das zu überprüfen System.exit(…) wird genannt.
Sie können auch den Exit-Status überprüfen.
Zum Beispiel:
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);
}
}
Die erste Antwort gefiel mir nicht, aber die zweite ist ziemlich cool – ich hatte mich nicht mit Sicherheitsmanagern angelegt und nahm an, dass sie komplizierter seien. Wie testen Sie jedoch den Sicherheitsmanager/Testmechanismus?
– Bill K
21. November 2008 um 17:25 Uhr
Stellen Sie sicher, dass der Teardown ordnungsgemäß ausgeführt wird, andernfalls schlägt Ihr Test in einem Runner wie Eclipse fehl, da die JUnit-Anwendung nicht beendet werden kann! 🙂
– MetroidFan2002
23. November 2008 um 7:00 Uhr
Ich mag die Lösung mit dem Sicherheitsmanager nicht. Scheint mir ein Hack zu sein, nur um es zu testen.
– Nicolai Reuschling
20. Mai 2009 um 12:47 Uhr
Wenn Sie Junit 4.7 oder höher verwenden, lassen Sie eine Bibliothek Ihre System.exit-Aufrufe erfassen. Systemregeln – stefanbirkner.github.com/system-rules
– Wille
17. Dezember 2012 um 17:34 Uhr
“Anstatt mit System.exit(whateverValue) zu enden, warum nicht eine ungeprüfte Ausnahme auslösen?” — weil ich das Framework zur Verarbeitung von Befehlszeilenargumenten verwende, das aufruft System.exit wenn ein ungültiges Befehlszeilenargument angegeben wird.
– Adam Parkin
7. August 2013 um 16:01 Uhr
Stefan Birkner
Die Bibliothek System-Lambda hat Methode catchSystemExit.Mit dieser Regel können Sie Code testen, der System.exit(…) aufruft:
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);
}
}
Für Java 5 bis 7 die Bibliothek Systemregeln hat eine JUnit-Regel namens ExpectedSystemExit. Mit dieser Regel können Sie Code testen, der System.exit(…) aufruft:
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);
}
}
Vollständige Offenlegung: Ich bin der Autor beider Bibliotheken.
Perfekt. Elegant. Ich musste keinen Stich meines ursprünglichen Codes ändern oder mit dem Sicherheitsmanager herumspielen. Dies sollte die Top-Antwort sein!
– Wille
17. Dezember 2012 um 17:35 Uhr
Dies sollte die oberste Antwort sein. Statt endloser Diskussionen über die richtige Verwendung von System.exit, direkt auf den Punkt. Darüber hinaus eine flexible Lösung in Übereinstimmung mit JUnit selbst, sodass wir das Rad nicht neu erfinden oder uns mit dem Sicherheitsmanager anlegen müssen.
– L. Holanda
21. Oktober 2015 um 18:58 Uhr
@LeoHolanda Leidet diese Lösung nicht unter demselben “Problem”, das Sie verwendet haben, um eine Ablehnung meiner Antwort zu rechtfertigen? Und was ist mit TestNG-Benutzern?
– Rogerio
21. Oktober 2015 um 20:15 Uhr
@LeoHolanda Du hast recht; Wenn ich mir die Implementierung der Regel anschaue, sehe ich jetzt, dass sie einen benutzerdefinierten Sicherheitsmanager verwendet, der eine Ausnahme auslöst, wenn der Aufruf für die Prüfung “Systemausgang” auftritt, wodurch der Test beendet wird. Der hinzugefügte Beispieltest in meiner Antwort erfüllt beide Anforderungen (ordnungsgemäße Überprüfung mit System.exit, das aufgerufen/nicht aufgerufen wird), übrigens. Die Verwendung von ExpectedSystemRule ist natürlich nett; Das Problem ist, dass eine zusätzliche Bibliothek eines Drittanbieters erforderlich ist, die in Bezug auf die praktische Nützlichkeit nur sehr wenig bietet und JUnit-spezifisch ist.
– Rogerio
22. Oktober 2015 um 16:15 Uhr
Es gibt exit.checkAssertionAfterwards().
– Stefan Birkner
3. November 2017 um 15:08 Uhr
Wie wäre es, einen “ExitManager” in diese Methoden einzufügen:
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
}
}
Der Produktionscode verwendet den ExitManagerImpl und der Testcode verwendet ExitManagerMock und kann überprüfen, ob exit() aufgerufen wurde und mit welchem Exit-Code.
+1 Schöne Lösung. Auch einfach zu implementieren, wenn Sie Spring verwenden, da der ExitManager zu einer einfachen Komponente wird. Beachten Sie nur, dass Sie sicherstellen müssen, dass Ihr Code nach dem Aufruf von exitManager.exit() nicht weiter ausgeführt wird. Wenn Ihr Code mit einem Schein-ExitManager getestet wird, wird der Code nach dem Aufruf von exitManager.exit nicht wirklich beendet.
– Joman68
28. August 2019 um 23:32 Uhr
Rogerio
Du eigentlich dürfen verspotten oder Stub aus dem System.exit Methode in einem JUnit-Test.
Zum Beispiel mit JMockit du könntest schreiben (es geht auch anders):
@Test
public void mockSystemExit(@Mocked("exit") System mockSystem)
{
// Called by code under test:
System.exit(); // will not exit the program
}
BEARBEITEN: Alternativer Test (unter Verwendung der neuesten JMockit-API), bei dem kein Code nach einem Aufruf von ausgeführt werden kann 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
Ein Trick, den wir in unserer Codebasis verwendet haben, bestand darin, den Aufruf von System.exit() in ein ausführbares Impl zu kapseln, das die betreffende Methode standardmäßig verwendet. Für den Komponententest haben wir ein anderes Schein-Runnable festgelegt. Etwas wie das:
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();
}
…und die JUnit-Testmethode…
public void testFoo(){
final AtomicBoolean actionWasCalled = new AtomicBoolean(false);
fooObject.foo(new Runnable(){
public void run(){
actionWasCalled.set(true);
}
});
assertTrue(actionWasCalled.get());
}
Nennen sie das Abhängigkeitsinjektion?
– Thomas Ahle
22. August 2010 um 21:10 Uhr
Dieses geschriebene Beispiel ist eine Art unausgegorene Abhängigkeitsinjektion – die Abhängigkeit wird an die paketsichtbare foo-Methode übergeben (entweder durch die öffentliche foo-Methode oder den Komponententest), aber die Hauptklasse codiert immer noch die standardmäßige Runnable-Implementierung.
– Scott Bale
23. August 2010 um 19:28 Uhr
Jeffrey Fredrick
Ich mag einige der bereits gegebenen Antworten, aber ich wollte eine andere Technik demonstrieren, die oft nützlich ist, wenn Legacy-Code getestet wird. Gegebener Code wie:
public class Foo {
public void bar(int i) {
if (i < 0) {
System.exit(i);
}
}
}
Sie können ein sicheres Refactoring durchführen, um eine Methode zu erstellen, die den System.exit-Aufruf umschließt:
public class Foo {
public void bar(int i) {
if (i < 0) {
exit(i);
}
}
void exit(int i) {
System.exit(i);
}
}
Dann können Sie für Ihren Test eine Fälschung erstellen, die den Ausgang überschreibt:
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;
}
}
Dies ist eine generische Technik zum Ersetzen von Verhalten für Testfälle, und ich verwende sie ständig beim Refactoring von Legacy-Code. Es ist normalerweise nicht, wo ich das Ding verlassen werde, sondern ein Zwischenschritt, um den vorhandenen Code zu testen.
Nennen sie das Abhängigkeitsinjektion?
– Thomas Ahle
22. August 2010 um 21:10 Uhr
Dieses geschriebene Beispiel ist eine Art unausgegorene Abhängigkeitsinjektion – die Abhängigkeit wird an die paketsichtbare foo-Methode übergeben (entweder durch die öffentliche foo-Methode oder den Komponententest), aber die Hauptklasse codiert immer noch die standardmäßige Runnable-Implementierung.
– Scott Bale
23. August 2010 um 19:28 Uhr
Jude Li Huan
Damit die Antwort von VonC auf JUnit 4 ausgeführt werden kann, habe ich den Code wie folgt geändert
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);
}
14500100cookie-checkWie testet man Methoden, die System.exit() aufrufen?yes
Ich bin neugierig, warum eine Funktion jemals System.exit() aufrufen würde …
– Thomas Owens
21. November 2008 um 16:40 Uhr
Ich denke immer noch, dass es in diesem Fall einen schöneren Weg geben sollte, von der Anwendung zurückzukehren, als System.exit().
– Thomas Owens
21. November 2008 um 16:57 Uhr
Wenn Sie main() testen, ist es absolut sinnvoll, System.exit() aufzurufen. Wir haben die Anforderung, dass ein Batch-Prozess bei einem Fehler mit 1 und bei Erfolg mit 0 beendet werden soll.
– Matthäus Farwell
5. April 2011 um 10:43 Uhr
Ich stimme jedem zu, der sagt
System.exit()
ist schlecht – Ihr Programm sollte schnell fehlschlagen. Das Auslösen einer Ausnahme verlängert lediglich eine Anwendung in einem ungültigen Zustand in Situationen, in denen ein Entwickler beenden möchte und falsche Fehler ausgibt.– Sridhar Sarnobat
24. Oktober 2017 um 17:10 Uhr
@ThomasOwens Ich bin gespannt, warum Sie System.exit für schlecht oder nicht erforderlich halten. Bitte sagen Sie mir, wie ich einen Batch mit dem Exit-Code 20 oder 30 aus einer Java-Anwendung zurückgeben kann.
– Wahnsinn
2. Oktober 2020 um 4:59 Uhr