Java: วิธีทดสอบวิธีที่เรียกว่า System.exit ()


199

ฉันมีวิธีการสองสามอย่างที่ควรเรียกใช้System.exit()ในอินพุตบางตัว น่าเสียดายที่การทดสอบกรณีเหล่านี้ทำให้ JUnit สิ้นสุดลง! การวางการเรียกเมธอดในเธรดใหม่ดูเหมือนจะไม่ช่วยได้เนื่องจากSystem.exit()ยกเลิก JVM ไม่ใช่เฉพาะเธรดปัจจุบัน มีรูปแบบทั่วไปในการจัดการกับเรื่องนี้หรือไม่? ตัวอย่างเช่นผมสามารถ Subsitute ต้นขั้วหาSystem.exit()?

[แก้ไข] ชั้นเรียนที่สงสัยเป็นเครื่องมือบรรทัดคำสั่งที่ฉันพยายามทดสอบภายใน JUnit บางที JUnit ไม่ใช่เครื่องมือที่เหมาะสมสำหรับงานใช่ไหม ข้อเสนอแนะสำหรับเครื่องมือทดสอบการถดถอยเสริมนั้นยินดีต้อนรับ (โดยเฉพาะสิ่งที่รวมเข้ากับ JUnit และ EclEmma ได้ดี)


2
ฉันอยากรู้ว่าทำไมถึงฟังก์ชั่นที่เคยจะเรียก System.exit () ...
โทมัส Owens

2
หากคุณกำลังเรียกใช้ฟังก์ชันที่ออกจากแอปพลิเคชัน ตัวอย่างเช่นหากผู้ใช้พยายามที่จะทำงานพวกเขาไม่ได้รับอนุญาตให้ทำงานมากกว่านั้น x ครั้งในแถวคุณบังคับให้พวกเขาออกจากแอพลิเคชัน
Elie

5
ฉันยังคิดว่าในกรณีนั้นควรมีวิธีที่ดีกว่าในการส่งคืนจากแอปพลิเคชันมากกว่า System.exit ()
Thomas Owens

28
หากคุณกำลังทดสอบ main () แสดงว่าเหมาะสมที่สุดที่จะเรียก System.exit () เรามีข้อกำหนดว่าด้วยข้อผิดพลาดกระบวนการแบทช์ควรออกด้วย 1 และออกสำเร็จด้วย 0
Matthew Farwell

5
ฉันไม่เห็นด้วยกับทุกคนที่บอกว่าSystem.exit()ไม่ดี - โปรแกรมของคุณควรล้มเหลวอย่างรวดเร็ว การทิ้งข้อยกเว้นจะทำให้แอปพลิเคชั่นยืดเยื้อในสถานะที่ไม่ถูกต้องในสถานการณ์ที่ผู้พัฒนาต้องการออกและจะให้ข้อผิดพลาดปลอม
Sridhar Sarnobat

คำตอบ:


218

อันที่จริงDerkeiler.comแนะนำ:

  • ทำไมSystem.exit()?

แทนที่จะยกเลิกด้วย System.exit (anyValue) ทำไมไม่โยนข้อยกเว้นที่ไม่ได้ตรวจสอบ ในการใช้งานปกติมันจะลอยออกไปจนถึงตัวจับสุดท้ายของ JVM และปิดสคริปต์ของคุณ (เว้นแต่คุณตัดสินใจที่จะจับมันที่ไหนสักแห่งระหว่างทางซึ่งอาจเป็นประโยชน์ในบางวัน)

ในสถานการณ์จำลอง JUnit จะถูกจับโดยกรอบงาน JUnit ซึ่งจะรายงานว่าการทดสอบดังกล่าวล้มเหลวและดำเนินการไปอย่างราบรื่นไปยังบทถัดไป

  • ป้องกันSystem.exit()การออกจาก JVM จริง:

ลองปรับเปลี่ยน TestCase เพื่อเรียกใช้กับตัวจัดการความปลอดภัยที่ป้องกันการเรียก System.exit จากนั้นตรวจสอบ 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);
        }
    }
}

อัปเดตธันวาคม 2555:

จะนำเสนอในการแสดงความคิดเห็นโดยใช้ระบบกฎคอลเลกชันของ JUnit (4.9+) java.lang.Systemกฎระเบียบสำหรับการทดสอบรหัสที่ใช้
นี่เป็นครั้งแรกที่ได้รับการกล่าวถึงโดยStefan Birknerในคำตอบของเขาในเดือนธันวาคม 2011

System.exit(…)

ใช้ExpectedSystemExitกฎเพื่อตรวจสอบว่าSystem.exit(…)มีการเรียก
คุณสามารถตรวจสอบสถานะการออกได้เช่นกัน

ตัวอย่างเช่น

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

4
ไม่ชอบคำตอบแรก แต่ข้อที่สองค่อนข้างเท่ห์ - ฉันไม่ได้ยุ่งกับผู้จัดการความปลอดภัยและคิดว่ามันซับซ้อนกว่านั้น อย่างไรก็ตามคุณจะทดสอบกลไกความปลอดภัย / กลไกการทดสอบได้อย่างไร
Bill K

8
ตรวจสอบให้แน่ใจว่าดำเนินการอย่างถูกต้องไม่เช่นนั้นการทดสอบของคุณจะล้มเหลวในนักวิ่งเช่น Eclipse เนื่องจากแอปพลิเคชัน JUnit ไม่สามารถออกได้! :)
MetroidFan2002

6
ฉันไม่ชอบวิธีแก้ปัญหาโดยใช้ตัวจัดการความปลอดภัย ดูเหมือนว่าแฮ็คกับฉันเพียงเพื่อทดสอบ
Nicolai Reuschling

7
หากคุณใช้ junit 4.7 ขึ้นไปให้ไลบรารีจัดการกับการเรียก System.exit ของคุณ กฎของระบบ - stefanbirkner.github.com/system-rules
จะถึง

6
"แทนที่จะจบด้วย System.exit (อะไรก็ตามที่มีค่า) ทำไมไม่โยนข้อยกเว้นที่ไม่ได้ตรวจสอบล่ะ?" - เพราะฉันใช้เฟรมเวิร์กการประมวลผลอาร์กิวเมนต์บรรทัดคำสั่งซึ่งจะเรียกSystem.exitเมื่อใดก็ตามที่มีการระบุอาร์กิวเมนต์บรรทัดคำสั่งที่ไม่ถูกต้อง
Adam Parkin

108

ระบบห้องสมุดแลมบ์ดามีวิธีการcatchSystemExitด้วยกฎนี้คุณสามารถทดสอบโค้ดที่เรียกว่า System.exit (... ):

public void MyTest {
    @Test
    public void systemExitWithArbitraryStatusCode() {
        SystemLambda.catchSystemExit(() -> {
            //the code under test, which calls System.exit(...);
        });
    }
}

สำหรับ Java 5 ถึง 7 กฎระบบห้องสมุดมีกฎ JUnit ชื่อ ExpectedSystemExit ด้วยกฎนี้คุณจะสามารถทดสอบโค้ดที่เรียกว่า 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);
    }
}

การเปิดเผยแบบเต็ม: ฉันเป็นผู้แต่งห้องสมุดทั้งสองแห่ง


3
สมบูรณ์ สง่า ไม่จำเป็นต้องเปลี่ยนรหัสเดิมของฉันหรือเล่นกับผู้จัดการความปลอดภัย นี่ควรเป็นคำตอบที่ดีที่สุด!
จะถึง

2
นี่ควรเป็นคำตอบที่ดีที่สุด แทนที่จะอภิปรายอย่างไม่มีที่สิ้นสุดในการใช้ System.exit ที่ถูกต้องให้ตรงประเด็น นอกจากนี้โซลูชันที่ยืดหยุ่นในการยึดมั่นกับ JUnit ดังนั้นเราจึงไม่จำเป็นต้องบูรณาการล้อเลื่อนหรือจัดการกับผู้จัดการความปลอดภัย
L. Holanda

@LeoHolanda วิธีการแก้ปัญหานี้ไม่ได้ประสบกับ "ปัญหา" เดียวกันที่คุณใช้ในการพิสูจน์ downvote ในคำตอบของฉันหรือไม่ แล้วผู้ใช้ TestNG ล่ะ
Rogério

2
@LeoHolanda คุณพูดถูก เมื่อดูการใช้งานของกฎตอนนี้ฉันเห็นว่ามันใช้ตัวจัดการความปลอดภัยแบบกำหนดเองซึ่งจะมีข้อยกเว้นเมื่อมีการเรียกใช้การตรวจสอบ ตัวอย่างการทดสอบที่เพิ่มเข้ามาในคำตอบของฉันเป็นไปตามข้อกำหนดทั้งสอง (การตรวจสอบที่เหมาะสมกับ System.exit คือการเรียก / ไม่เรียก), BTW การใช้งานExpectedSystemRuleเป็นสิ่งที่ดีแน่นอน ปัญหาคือมันต้องใช้ห้องสมุดบุคคลที่สามพิเศษซึ่งให้ประโยชน์น้อยมากในแง่ของประโยชน์ในโลกแห่งความเป็นจริงและเป็นเฉพาะ JUnit
Rogério

2
exit.checkAssertionAfterwards()นอกจากนี้
Stefan Birkner

31

วิธีการฉีด "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 () และรหัสทางออกใด


1
+1 ทางออกที่ดี ง่ายต่อการนำไปใช้หากคุณใช้ Spring เนื่องจาก ExitManager กลายเป็นคอมโพเนนต์อย่างง่าย เพิ่งทราบว่าคุณต้องตรวจสอบให้แน่ใจว่ารหัสของคุณไม่ได้ดำเนินการหลังจากเรียกใช้ exitManager.exit () เมื่อรหัสของคุณถูกทดสอบด้วยโปรแกรมจำลอง ExitManager รหัสนั้นจะไม่ออกหลังจากเรียกไปที่ exitManager.exit
Joman68

31

จริงๆคุณสามารถเยาะเย้ยหรือ stub System.exitวิธีในการทดสอบ JUnit

ตัวอย่างเช่นการใช้JMockitคุณสามารถเขียน (มีวิธีอื่นด้วย):

@Test
public void mockSystemExit(@Mocked("exit") System mockSystem)
{
    // Called by code under test:
    System.exit(); // will not exit the program
}


แก้ไข: การทดสอบทางเลือก (ใช้ JMockit API ล่าสุด) ซึ่งไม่อนุญาตให้ใช้รหัสใด ๆ หลังจากเรียกไปที่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)");
}

2
เหตุผลในการลงคะแนนเสียง: ปัญหาของการแก้ไขนี้คือถ้า System.exit ไม่ใช่บรรทัดสุดท้ายในรหัส (เช่นภายในหากเงื่อนไข) รหัสจะยังคงทำงานต่อไป
L. Holanda

@LeoHolanda เพิ่มการทดสอบรุ่นที่ป้องกันไม่ให้โค้ดเรียกใช้หลังจากการexitโทร (ไม่ใช่ว่ามันเป็นปัญหาจริงๆ IMO)
Rogério

มันอาจจะเหมาะสมกว่าที่จะแทนที่ System.out.println () ล่าสุดด้วยคำสั่ง assert?
user515655

"@Mocked (" exit ")" ดูเหมือนจะไม่ทำงานอีกต่อไปกับ JMockit 1.43: "ค่าแอตทริบิวต์ไม่ได้กำหนดไว้สำหรับประเภทคำอธิบายประกอบที่เยาะเย้ย" The docs: "มีคำอธิบายประกอบที่เยาะเย้ยต่างกันสามประการที่เราสามารถใช้ได้ พารามิเตอร์: @Mocked ซึ่งจะเยาะเย้ยวิธีการและตัวสร้างทั้งหมดในอินสแตนซ์ที่มีอยู่และในอนาคตทั้งหมดของคลาสที่เยาะเย้ย (สำหรับระยะเวลาของการทดสอบโดยใช้มัน); " jmockit.github.io/tutorial/Mocking.html#mocked
Thorsten Schöning

คุณลองใช้ข้อเสนอแนะของคุณจริงหรือ ฉันได้รับ: "java.lang.IllegalArgumentException: คลาส java.lang.System ไม่Runtimeสามารถเยาะเย้ย" สามารถล้อเลียน แต่ไม่หยุดSystem.exitอย่างน้อยในสถานการณ์ของฉันที่รันการทดสอบใน Gradle
Thorsten Schöning

20

เคล็ดลับอย่างหนึ่งที่เราใช้ในฐานรหัสของเราคือให้เรียกใช้ System.exit () เพื่อ encapsulated ใน 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()); 
}

นี่คือสิ่งที่พวกเขาเรียกว่าการฉีดพึ่งพา?
โทมัส Ahle

2
ตัวอย่างนี้เป็นลายลักษณ์อักษรคือการเรียงของการฉีดพึ่งพาครึ่ง - การอ้างอิงถูกส่งผ่านไปยังวิธี foo แพคเกจที่มองเห็นได้ (โดยวิธีสาธารณะ foo หรือการทดสอบหน่วย) แต่ชั้นหลักยังคง hardcodes เริ่มต้นใช้งาน Runnable
Scott Bale

6

สร้างคลาสที่จำลองได้ซึ่งทำให้ System.exit ()

ผมเห็นด้วยกับEricSchaefer แต่ถ้าคุณใช้กรอบการเยาะเย้ยที่ดีเช่นMockitoคลาสคอนกรีตที่เรียบง่ายก็เพียงพอแล้วไม่จำเป็นต้องมีอินเตอร์เฟสและการใช้งานสองแบบ

การหยุดการทดสอบการทำงานบน System.exit ()

ปัญหา:

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

การเยาะเย้ยSytem.exit()จะไม่ยุติการดำเนินการ สิ่งนี้ไม่ดีถ้าคุณต้องการทดสอบที่thing2ไม่ได้ดำเนินการ

สารละลาย:

คุณควร refactor รหัสนี้ตามที่แนะนำโดยmartin :

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

และทำSystem.exit(status)ในฟังก์ชั่นการโทร กองกำลังนี้คุณจะมีทั้งหมดของคุณในสถานที่แห่งหนึ่งในหรือใกล้System.exit() main()สิ่งนี้สะอาดกว่าการโทรSystem.exit()ลึก ๆ ภายในตรรกะของคุณ

รหัส

wrapper:

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

5

ฉันชอบคำตอบบางข้อที่ได้รับไปแล้ว แต่ฉันต้องการแสดงให้เห็นถึงเทคนิคที่แตกต่างซึ่งมักจะมีประโยชน์เมื่อรับรหัสดั้งเดิมภายใต้การทดสอบ รับรหัสเช่น:

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

นี่เป็นเทคนิคทั่วไปสำหรับการแทนที่พฤติกรรมสำหรับกรณีทดสอบและฉันใช้มันตลอดเวลาเมื่อทำการเปลี่ยนรหัสดั้งเดิม ไม่ปกติที่ฉันจะทิ้งอะไร แต่เป็นขั้นตอนกลางในการรับรหัสที่มีอยู่ภายใต้การทดสอบ


2
tecniques นี้ไม่ได้หยุดการไหลของ conrol เมื่อเรียก exit () ใช้ข้อยกเว้นแทน
Andrea Francia

3

ดูอย่างรวดเร็วที่ API แสดงให้เห็นว่า System.exit สามารถโยนข้อยกเว้น ถ้า securitymanager ห้ามการปิดระบบ vm อาจจะมีวิธีแก้ปัญหาคือการติดตั้งผู้จัดการดังกล่าว


3

คุณสามารถใช้ 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);

ฮึ่ม เอกสาร System.exit พูดโดยเฉพาะว่าจะเรียกใช้ checkExit (int) ไม่ใช่ checkPermission พร้อมชื่อ = "exitVM" ฉันสงสัยว่าฉันควรจะแทนที่ทั้งสองหรือไม่
Chris Conway

ชื่อการอนุญาตดูเหมือนจะเป็น exitVM (สถานะรหัส) เช่น exitVM.0 - อย่างน้อยในการทดสอบล่าสุดของฉันใน OSX
Mark Derricutt

3

เพื่อให้คำตอบของ 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);
}

3

คุณสามารถทดสอบ 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);
    }
}

2

มีสภาพแวดล้อมที่ใช้รหัสทางออกที่ส่งคืนโดยโปรแกรมเรียกใช้ (เช่น ERRORLEVEL ใน MS Batch) เรามีการทดสอบเกี่ยวกับวิธีการหลักที่ทำสิ่งนี้ในรหัสของเราและวิธีการของเราคือการใช้การแทนที่ SecurityManager ที่คล้ายกันที่ใช้ในการทดสอบอื่น ๆ ที่นี่

เมื่อคืนฉันรวบรวม JAR ขนาดเล็กโดยใช้คำอธิบายประกอบ Junit @Rule เพื่อซ่อนรหัสตัวจัดการความปลอดภัยรวมทั้งเพิ่มความคาดหวังตามรหัสส่งคืนที่คาดไว้ http://code.google.com/p/junitsystemrules/


2

โซลูชั่นส่วนใหญ่จะ

  • ยุติการทดสอบ (วิธีการไม่ใช่การวิ่งทั้งหมด) ช่วงเวลาที่System.exit()เรียกว่า
  • ละเว้นการติดตั้งแล้ว SecurityManager
  • บางครั้งค่อนข้างเฉพาะเจาะจงกับกรอบการทดสอบ
  • จำกัด ให้ใช้ได้สูงสุดหนึ่งครั้งต่อหนึ่งกรณีทดสอบ

ดังนั้นการแก้ปัญหาส่วนใหญ่ไม่เหมาะสำหรับสถานการณ์ที่:

  • การตรวจสอบผลข้างเคียงจะต้องดำเนินการหลังจากการโทรไปที่ System.exit()
  • ผู้จัดการความปลอดภัยที่มีอยู่เป็นส่วนหนึ่งของการทดสอบ
  • ใช้กรอบการทดสอบที่แตกต่างกัน
  • คุณต้องการให้มีการยืนยันหลายรายการในกรณีทดสอบเดียว สิ่งนี้อาจไม่แนะนำอย่างเคร่งครัด แต่สามารถทำได้อย่างสะดวกสบายในบางครั้งโดยเฉพาะอย่างยิ่งเมื่อใช้ร่วมกับassertAll()ตัวอย่างเช่น

ฉันไม่พอใจกับข้อ จำกัด ที่กำหนดโดยโซลูชันที่มีอยู่ซึ่งนำเสนอในคำตอบอื่น ๆ และทำให้เกิดอะไรขึ้นกับตัวฉันเอง

คลาสต่อไปนี้ให้วิธีการassertExits(int expectedStatus, Executable executable)ที่ยืนยันว่าSystem.exit()ถูกเรียกด้วยstatusค่าที่ระบุและการทดสอบสามารถดำเนินต่อไปหลังจากนั้น มันทำงานแบบเดียวกับ JUnit assertThrows5 นอกจากนี้ยังเคารพผู้จัดการความปลอดภัยที่มีอยู่

มีปัญหาหนึ่งที่เหลืออยู่: เมื่อโค้ดภายใต้การทดสอบติดตั้งตัวจัดการความปลอดภัยใหม่ซึ่งแทนที่ตัวจัดการความปลอดภัยที่กำหนดโดยการทดสอบอย่างสมบูรณ์ SecurityManagerโซลูชันที่อิงกับอื่น ๆ ทั้งหมดที่ฉันรู้จักประสบปัญหาเดียวกัน

import java.security.Permission;

import static java.lang.System.getSecurityManager;
import static java.lang.System.setSecurityManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

public enum ExitAssertions {
    ;

    public static <E extends Throwable> void assertExits(final int expectedStatus, final ThrowingExecutable<E> executable) throws E {
        final SecurityManager originalSecurityManager = getSecurityManager();
        setSecurityManager(new SecurityManager() {
            @Override
            public void checkPermission(final Permission perm) {
                if (originalSecurityManager != null)
                    originalSecurityManager.checkPermission(perm);
            }

            @Override
            public void checkPermission(final Permission perm, final Object context) {
                if (originalSecurityManager != null)
                    originalSecurityManager.checkPermission(perm, context);
            }

            @Override
            public void checkExit(final int status) {
                super.checkExit(status);
                throw new ExitException(status);
            }
        });
        try {
            executable.run();
            fail("Expected System.exit(" + expectedStatus + ") to be called, but it wasn't called.");
        } catch (final ExitException e) {
            assertEquals(expectedStatus, e.status, "Wrong System.exit() status.");
        } finally {
            setSecurityManager(originalSecurityManager);
        }
    }

    public interface ThrowingExecutable<E extends Throwable> {
        void run() throws E;
    }

    private static class ExitException extends SecurityException {
        final int status;

        private ExitException(final int status) {
            this.status = status;
        }
    }
}

คุณสามารถใช้คลาสดังนี้:

    @Test
    void example() {
        assertExits(0, () -> System.exit(0)); // succeeds
        assertExits(1, () -> System.exit(1)); // succeeds
        assertExits(2, () -> System.exit(1)); // fails
    }

รหัสสามารถพอร์ตได้อย่างง่ายดายเพื่อ JUnit 4, TestNG หรือกรอบอื่น ๆ หากจำเป็น องค์ประกอบเฉพาะเฟรมเวิร์กเท่านั้นที่ล้มเหลวในการทดสอบ สิ่งนี้สามารถเปลี่ยนเป็นสิ่งที่เป็นกรอบงานได้อย่างง่ายดาย (นอกเหนือจากJunit 4 Rule

มีห้องสำหรับการปรับปรุงเช่นการโหลดมากเกินไปassertExits()พร้อมข้อความที่ปรับแต่ง


1

ใช้Runtime.exec(String command)เพื่อเริ่ม JVM ในกระบวนการแยกต่างหาก


3
คุณจะโต้ตอบกับชั้นเรียนภายใต้การทดสอบอย่างไรหากอยู่ในกระบวนการแยกจากการทดสอบหน่วย
DNA

1
ใน 99% ของกรณี System.exit อยู่ภายในวิธีการหลัก ดังนั้นคุณโต้ตอบกับพวกเขาโดยอาร์กิวเมนต์บรรทัดคำสั่ง (เช่น args)
L. Holanda

1

มีปัญหาเล็กน้อยกับการSecurityManagerแก้ปัญหาคือ วิธีการบางอย่างเช่นโทรยังJFrame.exitOnClose SecurityManager.checkExitในแอปพลิเคชันของฉันฉันไม่ต้องการให้การโทรนั้นล้มเหลวดังนั้นฉันจึงใช้

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

0

การเรียก System.exit () เป็นแนวปฏิบัติที่ไม่ดีเว้นแต่จะทำไว้ใน main () วิธีการเหล่านี้ควรจะทิ้งข้อยกเว้นซึ่งในที่สุดจะถูกจับโดย main () ของคุณที่แล้วเรียก System.exit ด้วยรหัสที่เหมาะสม


8
แต่นั่นไม่ได้ตอบคำถาม จะเกิดอะไรขึ้นถ้าฟังก์ชั่นที่ถูกทดสอบนั้นเป็นวิธีหลักในท้ายที่สุด ดังนั้นการเรียก System.exit () อาจถูกต้องและใช้ได้จากมุมมองการออกแบบ คุณจะเขียนกรณีทดสอบได้อย่างไร
Elie

3
คุณไม่ควรทดสอบวิธีหลักเนื่องจากเมธอดหลักควรใช้อาร์กิวเมนต์ใด ๆ ส่งผ่านไปยังเมธอด parser จากนั้นเริ่มใช้แอปพลิเคชัน ไม่ควรมีตรรกะในวิธีการหลักที่จะทดสอบ
Thomas Owens

5
@Elie: ในคำถามประเภทนี้มีสองคำตอบที่ถูกต้อง มีใครตอบคำถามที่โพสต์และถามว่าทำไมคำถามจึงขึ้นอยู่ คำตอบทั้งสองประเภทให้ความเข้าใจที่ดีขึ้นและโดยเฉพาะอย่างยิ่งทั้งสองเข้าด้วยกัน
runaros
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.