ลองจับในการทดสอบ JUnit

ฉันกำลังเขียน Unit Test สำหรับแอปพลิเคชันที่มีอยู่แล้วมาเป็นเวลานาน วิธีการบางอย่างที่ฉันต้องทดสอบนั้นสร้างดังนี้:

public void someMethod() throws Exception { 
   //do something 
}

หากฉันต้องการทดสอบวิธีการเหล่านี้ ฉันต้องเขียนสิ่งนี้ในการทดสอบหน่วย:

@Test
public void someTest() {
   try {
      someMethod();
   }
   catch (Exception e) {
      e.printStackTrace();
   }
}

เป็นวิธีปฏิบัติที่ดีในการทำเช่นนี้หรือไม่? หรือมีวิธีอื่นในการทดสอบวิธีการเหล่านี้หรือไม่?

ฉันค้นคว้าทางอินเทอร์เน็ตและพบวิธีแก้ปัญหาบางอย่างด้วยคำอธิบายประกอบ @Rule และ @Test(expected=Exception.class) แต่นั่นไม่ได้ผล (Eclipse ยังคงแสดงบรรทัด someMethod() ในการทดสอบว่าผิด) ฉันไม่รู้ว่าสิ่งเหล่านี้เป็นวิธีแก้ปัญหาที่ดีหรือไม่ เพราะฉันยังใหม่กับเรื่องการทดสอบหน่วยทั้งหมด

หากใครที่รู้เรื่องนี้มากสามารถช่วยฉันได้ฉันจะขอบคุณจริงๆ


person Nelsch    schedule 15.07.2015    source แหล่งที่มา
comment
อย่าระงับข้อยกเว้น เว้นแต่คุณต้องการให้การทดสอบผ่านไปไม่ว่าจะมีข้อยกเว้นเกิดขึ้นหรือไม่ก็ตาม โปรดทราบว่าการทดสอบที่ผ่านว่ามีข้อยกเว้นเกิดขึ้นหรือไม่อาจไม่มีประโยชน์มากนัก   -  person Andy Turner    schedule 15.07.2015
comment
คุณสามารถปล่อยให้ JUnit ดูแล Exception ได้โดยเพิ่มมันลงในเมธอดของคุณ sig: public void someTest() Throws Exception อย่างไรก็ตาม หากคุณต้องการจับ Exception ด้วยตัวเองเพื่อยืนยันว่ามันเป็นตัวอย่างที่คุณให้ไว้ก็ดีไป   -  person s.ijpma    schedule 15.07.2015
comment
@Makoto JUnit ดูแลมันด้วยการพิมพ์การติดตามสแต็กและทำให้การทดสอบไม่สำเร็จ   -  person user253751    schedule 15.07.2015
comment
@immibis: ใช่นี่เป็นเรื่องจริง ตามแบบแผน การประกาศข้อยกเว้นที่จะเกิดขึ้นคือการปล่อยให้เฟรมเวิร์กระดับที่สูงกว่าบางตัวจัดการมันหรือ JVM ฉันควรจะชัดเจนกว่านี้ในจุดนั้น   -  person Makoto    schedule 15.07.2015
comment
ไม่ว่าจะเป็นความคิดที่ดีหรือไม่นั้นขึ้นอยู่กับสิ่งที่คุณพยายามทำ   -  person Raedwald    schedule 17.07.2015


คำตอบ (7)


เนื่องจาก Exception เป็นข้อยกเว้นที่ถูกตรวจสอบ คุณจึง:

  • ต้องจับข้อยกเว้นในคำสั่ง try...catch หรือ
  • ประกาศข้อยกเว้นที่จะโยนลงในเมธอดนั้นเอง

สิ่งที่คุณมีอยู่นั้นใช้ได้ดี แต่ความชอบส่วนตัวของฉันคือการประกาศข้อยกเว้นที่จะถูกโยนออกไป ด้วยวิธีนี้ หากมีข้อยกเว้นที่ฉันไม่คาดหวังเกิดขึ้นระหว่างการทดสอบ การทดสอบจะล้มเหลว

@Test
public void someTest() throws Exception {
    // dodgy code here
}

หากเราต้องการดูว่ามีข้อยกเว้นเฉพาะเจาะจงเกิดขึ้นหรือไม่ คุณมีตัวเลือกในการใช้ @Rule หรือเพิ่มค่าให้กับคำอธิบายประกอบ @Test โดยตรง

@Test(expected = FileNotFoundException.class)
public void someTest() throws Exception {
    // dodgy code here
}

ใน JUnit 5 คุณสามารถใช้ประโยชน์จาก Assertions.assertThrows เพื่อทำสิ่งเดียวกันให้สำเร็จ ฉันไม่ค่อยคุ้นเคยกับสิ่งนี้โดยรวมเนื่องจากยังไม่ได้ GA ในขณะที่แก้ไข แต่ดูเหมือนว่าจะยอมรับ Executable ที่มาจาก JUnit 5

@Test
public void someTest() {
    assertThrows(FileNotFoundException.class, () ->
         { dodgyService.breakableMethod() };
}
person Makoto    schedule 15.07.2015
comment
ความจริงก็คือ: ฉันไม่คาดหวังว่าจะมีข้อยกเว้นเกิดขึ้น การทดสอบของฉันจะไม่ล้มเหลวหากไม่มีข้อยกเว้นเกิดขึ้นใช่หรือไม่ - person Nelsch; 15.07.2015
comment
ไม่! หากไม่มีข้อยกเว้นใดๆ การทดสอบจะผ่านไปตามปกติหากเป็นไปตามการยืนยันอื่นๆ ของคุณ - person Makoto; 15.07.2015
comment
โอเค ฉันคิดว่าฉันยังไม่เข้าใจเรื่องนั้นดีนัก ขอบคุณ! - person Nelsch; 15.07.2015
comment
โปรดจำไว้ว่าเพียงเพราะคุณไม่ได้คาดหวังว่าจะมีข้อยกเว้นเกิดขึ้นในระบบการผลิตของคุณ ไม่ได้หมายความว่าไม่ใช่ความคิดที่ดีที่จะตรวจสอบให้แน่ใจว่าได้ถูกส่งไปในการทดสอบ และการจัดการนั้นสมเหตุสมผล - กำลังวาง บันทึกในบันทึก, แนะนำผู้ใช้เกี่ยวกับความล้มเหลว ฯลฯ, การปิดระบบอย่างเรียบร้อย, การปิดเธรด ~ คุณไม่มีทางรู้ว่าเมื่อใดที่เครือข่ายขัดข้องจะทำให้ไม่พบไฟล์ของคุณ หรือเครื่องพิมพ์ไม่พร้อมใช้งาน ฯลฯ อย่าปล่อยให้ผู้ใช้ของคุณ พีซีค้างเนื่องจากข้อยกเว้นที่ได้รับการจัดการไม่ดีที่กำลังรอเธรดที่จะออก หรือทรัพยากรที่จะพร้อมใช้งานอีกครั้ง - person DaveM; 15.07.2015
comment
สิ่งที่คุณมีอยู่ข้างบนนั้นใช้ได้ดีใช่ไหม? ไม่มีทาง มันแย่มากจนทนไม่ไหว - person Nathan Hughes; 16.07.2015
comment
expected ใน @Test ไม่ได้รับอนุญาต ตั้งแต่ JUnit 5 - person Akash Agarwal; 05.08.2017
comment
@AkashAggarwal: เนื่องจาก JUnit5 ยังไม่ใช่ GA ฉันจึงไม่ทราบข้อเท็จจริงนี้อย่างมีความสุข ฉันได้แก้ไขคำตอบของฉันแล้ว - person Makoto; 05.08.2017
comment
@Makoto เหมือนกัน ฉันก็ไม่ทราบเหมือนกัน ฉันชอบแนวทางที่คาดหวังไว้มากและเสียเวลาประมาณ 10 นาทีเพื่อให้มันใช้งานได้จนกว่าฉันจะรู้ว่าทำไม แค่ต้องการป้องกันไม่ให้ใครก็ตามประสบปัญหาเดียวกัน - person Akash Agarwal; 05.08.2017
comment
@AkashAggarwal: ...คุณน่าจะโพสต์คำตอบของคุณเองได้นะรู้ไหม... - person Makoto; 05.08.2017
comment
@Makoto ฉันรู้สึกเหมือนมีส่วนร่วมในคำตอบที่ได้รับการโหวตสูงสุดเนื่องจากมองเห็นได้ง่ายกว่า :) - person Akash Agarwal; 05.08.2017

@Test
public void someTest() {
   try {
     someMethod();
   }
   catch (Exception e) {
     Assert.fail("Exception " + e);
   }
}

เป็นสิ่งที่คุณสามารถทำได้หากไม่ควรเกิดข้อยกเว้น อีกทางเลือกหนึ่งคือการโยนข้อยกเว้นในลายเซ็นดังนี้:

@Test
public void someTest() throws Exception {
     someMethod();
}

ความแตกต่างก็คือ ในกรณีหนึ่งการทดสอบจะล้มเหลวโดยมีข้อยกเว้นในการยืนยัน และอีกกรณีหนึ่งจะล้มเหลวเนื่องจากการทดสอบล้มเหลว (เช่นที่ไหนสักแห่งในรหัสของคุณที่คุณได้รับ NPE และการทดสอบจะเป็นเช่นนั้น)

เหตุผลที่คุณต้องทำเช่นนี้ เนื่องจาก Exception เป็นข้อยกเว้นที่ถูกตรวจสอบ ดูตรวจสอบแล้วกับข้อยกเว้นที่ไม่ได้ตรวจสอบ

@Test(expected=Exception.class) ใช้สำหรับการทดสอบที่ต้องการทดสอบว่าข้อยกเว้นจะถูกส่งออกไป

@Test(expected=ArrayIndexOutOfBounds.class)
public void testIndex() {
   int[] array = new int[0];
   int var = array[0]; //exception will be thrown here, but test will be green, because we expect this exception

}
person morpheus05    schedule 15.07.2015
comment
วิธีนี้จะปรับใช้วิธีที่ Junit จัดการกับข้อยกเว้น แต่ที่แย่กว่านั้นคือ เนื่องจากคุณสูญเสียการติดตามสแต็ก ทำให้ยากที่จะทราบว่าข้อยกเว้นนั้นเกิดขึ้นที่ใด - person Andy Turner; 15.07.2015

อย่าจับข้อยกเว้นของแอปพลิเคชันของคุณในโค้ดทดสอบของคุณ ให้ประกาศให้โยนขึ้นไปแทน

เพราะเมื่อ TestRunner ของ JUnit พบข้อยกเว้นที่ถูกโยนทิ้ง มันจะบันทึกเป็น error สำหรับกรณีทดสอบโดยอัตโนมัติ

เฉพาะในกรณีที่คุณ testcase คาดว่าเมธอดควรส่ง Exception คุณควรใช้ @Test(expected=Exception.class) หรือจับข้อยกเว้น

ในกรณีอื่น ๆ เพียงแค่โยนมันขึ้นไปด้วย

public void someTest() throws Exception {
person Codebender    schedule 15.07.2015

คุณสามารถเพิ่มข้อยกเว้นในลายเซ็นวิธีทดสอบได้ จากนั้น หากคุณกำลังทดสอบว่ามีข้อยกเว้นเกิดขึ้นหรือไม่ คุณต้องใช้ @Test(expected=Exception.class) ในกรณีที่ทดสอบซึ่งไม่ต้องส่งข้อยกเว้น การทดสอบจะผ่านไปได้สำเร็จ

@Test
public void testCaseWhereExceptionWontBeThrown() throws Exception {
    someMethod(); //Test pass
}

@Test(expected = Exception.class)
public void testCaseWhereExceptionWillBeThrown() throws Exception {
    someMethod(); //Test pass
}
person Héctor    schedule 15.07.2015
comment
ฉันคิดว่าโค้ดตัวอย่างบางส่วนจะช่วยคำตอบของคุณได้ เนื่องจากฉันคิดว่าเขาอาจลองสิ่งที่คล้ายกันแล้ว แต่ยังไม่เข้าใจไวยากรณ์ที่ถูกต้อง - person Andy Turner; 15.07.2015
comment
สำหรับฉัน บล็อกโค้ดที่สองล้มเหลว เนื่องจากโค้ดที่ฉันกำลังทดสอบมีบล็อก try-catch ฉันแค่พยายามทดสอบว่ามีข้อยกเว้นเฉพาะเกิดขึ้นเมื่อฉันโทรโดยใช้ค่าที่ไม่ถูกต้องหรือไม่ - person shapan dashore; 02.10.2020

มีกฎหลักสองข้อเกี่ยวกับวิธีการประมวลผลข้อยกเว้นสำหรับผู้ทดสอบ Junit:

  1. หากข้อยกเว้นเกิดขึ้นในโค้ดที่ทดสอบ:

    • If it was expected, declare it in the expected attribute of the Test annotation. Or, if further checks should be done on the exception object itself, catch it and ignore it. (In this case, there must be also a call to Assert.fail at the end of the try block, to indicate that the expected exception was not produced).
    • หากไม่เป็นไปตามที่คาดหวัง ให้จับมันแล้วดำเนินการ Assert.fail (การเรียก Exception.printStackTrace ก่อนหน้านี้ก็มีประโยชน์เช่นกัน)
  2. หากข้อยกเว้นไม่ได้เกิดขึ้นในโค้ดที่ทดสอบหรือไม่น่าสนใจสำหรับการทดสอบ (เช่น IOException ส่วนใหญ่ถูกสร้างขึ้นที่ระดับเครือข่าย ก่อนที่การทดสอบจะเสร็จสมบูรณ์) ให้โยนใหม่ ที่ส่วนคำสั่ง throws

เหตุใดคุณจึงควรคาดหวังข้อยกเว้นจากผู้ทดสอบ คำเตือน: คุณควรเขียนโค้ด วิธีทดสอบหนึ่งวิธีสำหรับทุกผลลัพธ์ที่เป็นไปได้ในโค้ดที่ทดสอบ (เพื่อให้ได้รับความครอบคลุมของโค้ดในระดับสูง): ในกรณีของคุณ วิธีหนึ่งที่ต้องส่งคืนได้สำเร็จ และอย่างน้อยอีกวิธีหนึ่ง ที่จะต้องสร้างข้อยกเว้น

person Little Santi    schedule 15.07.2015

สามประเด็นเกี่ยวกับ JUnit:

  • การทดสอบควรมีความแม่นยำ ควรผ่านหรือไม่ผ่านโดยไม่คลุมเครือโดยพิจารณาจากวิธีการตั้งค่าอินพุตการทดสอบเพียงอย่างเดียว

  • การทดสอบควรมีการรายงานความล้มเหลวกลับเข้าสู่เฟรมเวิร์ก

  • การทดสอบไม่ควรอาศัยการอ่านเอาต์พุต

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

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

หากคุณคาดหวังว่าการทดสอบจะสำเร็จโดยไม่เกิดข้อยกเว้น หากมีสิ่งใดในการทดสอบที่สามารถทำให้เกิดข้อยกเว้นที่เลือกได้ ให้เพิ่ม throws Exception ลงในลายเซ็นวิธีทดสอบ การเพิ่ม throws ลงในลายเซ็นไม่ได้บอกว่าวิธีการที่ ต้อง โยนสิ่งใด เพียงปล่อยให้ข้อยกเว้นใดๆ ที่เกิดขึ้นเกิดขึ้นถูกโยนออกไป เพื่อให้กรอบงานการทดสอบสามารถตรวจจับได้

อินสแตนซ์เดียวที่คุณจะตรวจจับข้อยกเว้นในการทดสอบได้จริงคือที่ที่คุณต้องการทดสอบการยืนยันเกี่ยวกับข้อยกเว้น ตัวอย่างเช่น คุณสามารถทดสอบว่าข้อความบนข้อยกเว้นนั้นเป็นสิ่งที่คุณคาดหวัง หรือถ้าข้อยกเว้นนั้นมีสาเหตุอยู่ ในกรณีนั้น คุณจะต้องเพิ่ม Assert.fail() ที่ส่วนท้ายของ try-block เพื่อไม่ให้มีข้อยกเว้นเกิดขึ้นจะทำให้การทดสอบล้มเหลว

ไม่มีบล็อก try-catch ที่ไม่ดีนัก แต่ไม่มีสิ่งใดที่จะทำให้การทดสอบล้มเหลวที่ไม่ดี

เมื่อคุณเขียนแบบทดสอบในตอนแรก จงทำให้มันล้มเหลว ด้วยวิธีนี้ คุณจะพิสูจน์ตัวเองว่าคุณรู้ว่าการทดสอบกำลังทำอะไรอยู่ และคุณยืนยันว่าเมื่อมีข้อผิดพลาด คุณจะได้รับการแจ้งให้ทราบ

person Nathan Hughes    schedule 16.07.2015

มีข้อยกเว้นประการใด? ใช่ไหม

  1. ข้อยกเว้นจากการทำบางอย่างเช่นการใช้สตรีมที่จะไม่เกิดขึ้นในการทดสอบหน่วยของคุณหรือ
  2. ข้อยกเว้นที่อาจเกิดขึ้นเนื่องจากการป้อนข้อมูลที่ไม่ดีบางประเภท

ถ้าเป็น 1 ฉันจะใส่มันไว้ที่ระดับลายเซ็นของวิธีการเพราะว่าการลองจับนั้นไม่ได้ให้บริการเพื่อวัตถุประสงค์ที่แท้จริงอื่นใดนอกจากพิธีการ

@Test
public void testFoo() throws Exception {
    // ...
}

ถ้าเป็น 2. มันจะซับซ้อนขึ้นอีกหน่อย คุณต้องถามตัวเองว่าจะเกิดอะไรขึ้นหากข้อยกเว้นถูกส่งออกไป การทดสอบควรล้มเหลวหรือไม่? เป็นไปตามคาดหรือเปล่า? มันไม่เกี่ยวข้องเหรอ? ตัวอย่างวิธีจัดการสิ่งเหล่านี้ด้านล่าง ระวัง: ฉันใช้ข้อยกเว้นเพียงเพราะคุณใช้ ฉันหวังว่ามันจะไม่เป็นเช่นนั้นจริงๆ เพราะหากเป็นไปได้ที่ข้อยกเว้น other บางอย่างจะถูกโยนทิ้งไปนอกเหนือจากที่คาดไว้ สิ่งเหล่านี้ก็จะเกะกะมาก ถ้าเป็นไปได้ อย่าใช้ Exception ให้ใช้คำที่เฉพาะเจาะจงกว่านี้ (ในโค้ด และ มิถุนายน)

// The below code assumes you've imported the org.junit.Assert class.

@Test
public void thisShouldFailIfExceptionCaught() {
    //Given...
    try {
        // When...
    } catch (Exception e) {
        Assert.fail();
    }
    // Then...
}

@Test
public void thisShouldPassOnlyIfTheExceptionIsCaught() {
    //Given...
    try {
        // When...
        Assert.fail();
    } catch (Exception expected) {}
    // No "then" needed, the fact that it didn't fail is enough.
}

@Test
public void irrelevantExceptionThatCouldBeThrown() {
    //Given...
    try {
        // When...
    } catch (Exception e) {}
    // Then...
}
person Captain Man    schedule 15.07.2015
comment
ขึ้นอยู่กับวิธีที่ฉันใช้ อาจเป็นได้ทั้ง JMSException, IOException หรือ ConfigurationException :) ขอบคุณสำหรับคำตอบที่กระจ่างแจ้ง! - person Nelsch; 16.07.2015
comment
@เนลช์ ไม่มีปัญหา! ฉันขอแนะนำในอนาคตว่าเมื่อถามคำถามอย่าใช้ Exception เว้นแต่คุณจะหมายความอย่างนั้นจริงๆ (ทดสอบบางอย่างที่ throws Exception เป็นต้น) แม้แต่ชื่ออย่าง SomeCheckedException หรือ SomeUncheckedException ก็อธิบายปัญหาที่เกิดขึ้นได้จริงๆ - person Captain Man; 16.07.2015
comment
การทดสอบหน่วยไม่ควรจับข้อยกเว้นเพียงเพื่อเรียก fail() ในความเป็นจริง มันเป็นเรื่องปกติสำหรับการทดสอบหน่วยทั้งหมดที่จะประกาศ Exception เนื่องจากวิธีการทดสอบ (ควร!) ไม่เคยถูกเรียกจากที่ใดก็ได้ยกเว้นกรอบการทดสอบ และอย่างหลังปล่อยให้การทดสอบล้มเหลวในกรณีที่มีข้อยกเว้นซึ่งเป็นสิ่งที่คุณต้องการอย่างแน่นอน - person Michel Jung; 04.05.2017