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



Answers

系統規則有一個名為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);
    }
}

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

Question

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

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




對於VonC在JUnit 4上運行的答案,我修改了代碼,如下所示

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



如何注入一個“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()以及使用哪個退出代碼。




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

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




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




使用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);
    }
}



Related