multithreading junit - Java:如何測試調用System.exit()的方法?





unit-testing testability (13)


您可以使用替換運行時實例來測試System.exit(..)。 例如TestNG + Mockito:

public class ConsoleTest {
    /** Original runtime. */
    private Runtime originalRuntime;

    /** Mocked runtime. */
    private Runtime spyRuntime;

    @BeforeMethod
    public void setUp() {
        originalRuntime = Runtime.getRuntime();
        spyRuntime = spy(originalRuntime);

        // Replace original runtime with a spy (via reflection).
        Utils.setField(Runtime.class, "currentRuntime", spyRuntime);
    }

    @AfterMethod
    public void tearDown() {
        // Recover original runtime.
        Utils.setField(Runtime.class, "currentRuntime", originalRuntime);
    }

    @Test
    public void testSystemExit() {
        // Or anything you want as an answer.
        doNothing().when(spyRuntime).exit(anyInt());

        System.exit(1);

        verify(spyRuntime).exit(1);
    }
}

我有幾個方法應該在某些輸入上調用System.exit() 。 不幸的是,測試這些情況會導致JUnit終止! 將方法調用放在一個新的Thread中似乎沒有什麼幫助,因為System.exit()終止了JVM,而不僅僅是當前線程。 有沒有處理這個問題的共同模式? 例如,我可以用System.exit()替代一個存根嗎?

[編輯]有問題的類實際上是一個命令行工具,我試圖在JUnit內部進行測試。 也許JUnit根本就不是這份工作的正確工具? 歡迎對互補回歸測試工具提出建議(最好是與JUnit和EclEmma很好地結合的東西)。




系統規則有一個名為ExpectedSystemExit的JUnit規則。 通過這個規則,你可以測試代碼,調用System.exit(...):

public void 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);
    }
}

充分披露:我是該圖書館的作者。




快速查看api,顯示System.exit可以引發異常。 如果安全管理者禁止關閉虛擬機。 也許解決辦法是安裝這樣的經理。




有些環境返回的退出代碼被調用程序使用(例如MS批處理中的ERRORLEVEL)。 我們對代碼中的主要方法進行了測試,我們的方法是使用類似於其他測試中使用的SecurityManager覆蓋。

昨天晚上,我使用Junit @Rule註釋將一個小型JAR放在一起,以隱藏安全管理器代碼,並根據預期的返回代碼添加期望值。 http://code.google.com/p/junitsystemrules/




SecurityManager解決方案存在一個小問題。 某些方法(如JFrame.exitOnClose )也會調用SecurityManager.checkExit 。 在我的應用程序中,我不想讓這個調用失敗,所以我使用了

Class[] stack = getClassContext();
if (stack[1] != JFrame.class && !okToExit) throw new ExitException();
super.checkExit(status);



調用System.exit()是一種不好的做法,除非它在main()中完成。 這些方法應該拋出一個異常,最終被main()捕獲,然後用相應的代碼調用System.exit。




您可以使用java SecurityManager來防止當前線程關閉Java VM。 下面的代碼應該做你想做的事情:

SecurityManager securityManager = new SecurityManager() {
    public void checkPermission(Permission permission) {
        if ("exitVM".equals(permission.getName())) {
            throw new SecurityException("System.exit attempted and blocked.");
        }
    }
};
System.setSecurityManager(securityManager);



我喜歡已經給出的一些答案,但我想演示一種不同的技術,在獲取遺留代碼時經常有用。 給定代碼如:

public class Foo {
  public void bar(int i) {
    if (i < 0) {
      System.exit(i);
    }
  }
}

你可以做一個安全的重構來創建一個包裝System.exit調用的方法:

public class Foo {
  public void bar(int i) {
    if (i < 0) {
      exit(i);
    }
  }

  void exit(int i) {
    System.exit(i);
  }
}

然後,您可以為覆蓋退出的測試創建一個偽造:

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;
    }
}

這是一種替代測試用例行為的通用技術,我在重構遺留代碼時一直使用它。 它通常不是我要離開的地方,而是獲取現有代碼的中間步驟。




使用Runtime.exec(String command)在單獨的進程中啟動JVM。




創建一個包裝System.exit()的可模擬類

我同意EricSchaefer 。 但是如果你使用像Mockito這樣的好的Mockito框架,一個簡單的具體類就足夠了,不需要接口和兩個實現。

停止System.exit()上的測試執行

問題:

// do thing1
if(someCondition) {
    System.exit(1);
}
// do thing2
System.exit(0)

Sytem.exit()不會終止執行。 如果你想測試thing2沒有執行,這是不好的。

解:

你應該按照martin建議重構這段代碼:

// do thing1
if(someCondition) {
    return 1;
}
// do thing2
return 0;

並在調用函數中執行System.exit(status) 。 這迫使你將所有的System.exit()放在main()中或附近的一個地方。 這比在你的邏輯內調用System.exit()更清潔。

包裝:

public class SystemExit {

    public void exit(int status) {
        System.exit(status);
    }
}

主要:

public class Main {

    private final SystemExit systemExit;


    Main(SystemExit systemExit) {
        this.systemExit = systemExit;
    }


    public static void main(String[] args) {
        SystemExit aSystemExit = new SystemExit();
        Main main = new Main(aSystemExit);

        main.executeAndExit(args);
    }


    void executeAndExit(String[] args) {
        int status = execute(args);
        systemExit.exit(status);
    }


    private int execute(String[] args) {
        System.out.println("First argument:");
        if (args.length == 0) {
            return 1;
        }
        System.out.println(args[0]);
        return 0;
    }
}

測試:

public class MainTest {

    private Main       main;

    private SystemExit systemExit;


    @Before
    public void setUp() {
        systemExit = mock(SystemExit.class);
        main = new Main(systemExit);
    }


    @Test
    public void executeCallsSystemExit() {
        String[] emptyArgs = {};

        // test
        main.executeAndExit(emptyArgs);

        verify(systemExit).exit(1);
    }
}



如何注入一個“ExitManager”到這個方法:

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
    }
}

生產代碼使用ExitManagerImpl,測試代碼使用ExitManagerMock,並可以檢查是否調用了exit()以及使用哪個退出代碼。




我們在代碼庫中使用的一個技巧是將對System.exit()的調用封裝在Runnable impl中,默認情況下使用該方法。 為了進行單元測試,我們設置了一個不同的模擬Runnable。 像這樣的東西:

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(); 
}

...和JUnit測試方法...

public void testFoo(){   
  final AtomicBoolean actionWasCalled = new AtomicBoolean(false);   
  fooObject.foo(new Runnable(){
    public void run(){
      actionWasCalled.set(true);
    }   
  });   
  assertTrue(actionWasCalled.get()); 
}



方法:

 1. Integer.parseInt(s)
 2. Integer.parseInt(s, radix)
 3. Integer.parseInt(s, beginIndex, endIndex, radix)
 4. Integer.parseUnsignedInt(s)
 5. Integer.parseUnsignedInt(s, radix)
 6. Integer.parseUnsignedInt(s, beginIndex, endIndex, radix)
 7. Integer.valueOf(s)
 8. Integer.valueOf(s, radix)
 9. Integer.decode(s)
 10. NumberUtils.toInt(s)
 11. NumberUtils.toInt(s, defaultValue)

Integer.valueOf生成Integer對象,所有其他方法 - primitive int。

來自commons-lang3最後兩種方法和關於轉換的大文章。





java multithreading unit-testing junit testability