ความสับสนของ JUnit: ใช้ 'ขยาย TestCase' หรือ '@Test' หรือไม่


152

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

หากฉันเข้าใจถูกต้องมีสองวิธีหลักในการสร้างและเรียกใช้การทดสอบ JUnit:

วิธี A (JUnit 3 สไตล์):สร้างคลาสที่ขยาย TestCase testและเริ่มวิธีการทดสอบที่มีคำว่า เมื่อรันคลาสเป็น JUnit Test (ใน Eclipse) วิธีการทั้งหมดที่เริ่มต้นด้วยคำtestจะถูกเรียกใช้โดยอัตโนมัติ

import junit.framework.TestCase;

public class DummyTestA extends TestCase {

    public void testSum() {
        int a = 5;
        int b = 10;
        int result = a + b;
        assertEquals(15, result);
    }
}

Approach B (JUnit 4-style):สร้างคลาส 'Normal' และใส่@Testหมายเหตุประกอบลงในเมธอด testโปรดทราบว่าคุณไม่ได้มีวิธีการที่จะเริ่มต้นที่มีคำว่า

import org.junit.*;
import static org.junit.Assert.*;

public class DummyTestB {

    @Test
    public void Sum() {
        int a = 5;
        int b = 10;
        int result = a + b;
        assertEquals(15, result);
    }
}

ผสมทั้งสองดูเหมือนจะไม่เป็นความคิดที่ดีดูเช่นคำถาม stackoverflow นี้ :

ตอนนี้คำถามของฉัน:

  1. อะไรคือแนวทางที่ต้องการหรือเมื่อใดที่คุณจะใช้วิธีหนึ่งแทนวิธีอื่น
  2. วิธี B ช่วยให้สำหรับการทดสอบสำหรับข้อยกเว้นโดยการขยายคำอธิบายประกอบ @Test @Test(expected = ArithmeticException.class)เหมือนใน แต่คุณจะทดสอบข้อยกเว้นเมื่อใช้วิธี A ได้อย่างไร
  3. เมื่อใช้วิธีการ A คุณสามารถจัดกลุ่มคลาสการทดสอบจำนวนหนึ่งในชุดการทดสอบดังนี้:

    TestSuite suite = new TestSuite("All tests");
    suite.addTestSuite(DummyTestA.class);
    suite.addTestSuite(DummyTestAbis.class);

    แต่สิ่งนี้ไม่สามารถใช้กับวิธี B (เนื่องจากแต่ละคลาสควรเป็นคลาสย่อย TestCase) วิธีที่เหมาะสมในการทดสอบกลุ่มสำหรับวิธีการ B คืออะไร?

แก้ไข: ฉันได้เพิ่มเวอร์ชัน JUnit ให้กับทั้งสองวิธี


ฉันเคยเห็นextends TestCaseแล้วการทดสอบแต่ละครั้งก็มีหมายเหตุประกอบ@Testเพียงเพื่อทำให้สับสน :)
EM-Creations

คำตอบ:


119

ความแตกต่างค่อนข้างง่าย:

  • การขยายTestCaseคือวิธีการทดสอบหน่วยที่เขียนใน JUnit 3 (แน่นอนว่ายังรองรับใน JUnit 4)
  • การใช้@Testคำอธิบายประกอบเป็นวิธีที่แนะนำโดย JUnit 4

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

  • คำ@Testอธิบายประกอบมีความชัดเจนมากขึ้นและง่ายต่อการสนับสนุนในเครื่องมือ (เช่นง่ายต่อการค้นหาการทดสอบทั้งหมดด้วยวิธีนี้)
  • สามารถทำหมายเหตุประกอบได้หลายวิธีด้วย@Before/ @BeforeClassและ@After/ @AfterClassให้ความยืดหยุ่นมากขึ้น
  • รองรับการใส่@Ruleคำอธิบายประกอบในสิ่งที่ชอบExpectedException
  • การสนับสนุนสำหรับ@Ignoredคำอธิบายประกอบ
  • รองรับนักวิ่งทดสอบทางเลือกโดยใช้ @RunWith

ในการทดสอบข้อยกเว้นที่คาดไว้ใน JUnit 3 TestCaseคุณจะต้องทำให้ข้อความชัดเจน

public void testMyException() {
  try {
    objectUnderTest.myMethod(EVIL_ARGUMENT);
    fail("myMethod did not throw an Exception!");
  } catch (MyException e) {
    // ok!
    // check for properties of exception here, if desired
  }
}

JUnit 5แนะนำให้รู้จักกับการเปลี่ยนแปลง API อื่น แต่ยังคงใช้คำอธิบายประกอบ @Testคำอธิบายประกอบใหม่คือorg.junit.jupiter.api.Test("เก่า" JUnit 4 อันใดอันหนึ่งorg.junit.Test) แต่มันทำงานได้ดีมากเหมือนกับ JUnit 4 อันเดียว


คำตอบที่เป็นประโยชน์และละเอียดถี่ถ้วน แต่ฉันไม่เข้าใจ "ตรวจสอบข้อความแสดงข้อยกเว้น" การตรวจสอบกับสตริง hardcoded จะเป็นฝันร้ายการบำรุงรักษา คุณต้องตั้งใจ "ตรวจสอบคุณสมบัติของประเภทการยกเว้นเฉพาะ"
thSoft

3
@thSoft: มันไม่ได้ใช้บ่อยนัก แต่บางครั้งฉันต้องการให้แน่ใจว่าเมธอด exception กล่าวถึงฟิลด์ที่ละเมิด จากนั้นง่ายassertTrue(e.getMessage().contains("foo"))จะมีประโยชน์
โจอาคิมซาวเออร์

1
แม้ใน JUnit4 นี่เป็นสำนวนที่สำคัญเมื่อคุณต้องตรวจสอบข้อความหรือคุณสมบัติอื่น ๆ ของข้อยกเว้น (เช่นสาเหตุ) expectedวิธีการตรวจสอบเฉพาะสำหรับประเภท
Yishai

@Yishai: เป็นเรื่องจริง แต่ส่วนใหญ่แล้วฉันพอใจแล้วหากวิธีการใช้ข้อยกเว้นที่ถูกต้องเกี่ยวกับอินพุตที่มีปัญหา
Joachim Sauer

ด้วยเหตุผลดังกล่าว JUnit 5 จึงทำการเปลี่ยนแปลงครั้งใหญ่ในการทดสอบยกเว้น assertThrows () ยอดเยี่ยม :-)
Marcus K.

25

ฉันชอบ JUnit 4 (Annotation approach) เพราะฉันคิดว่ามันมีความยืดหยุ่นมากกว่า

ถ้าคุณต้องการสร้างชุดทดสอบใน JUnit 4 คุณต้องสร้างคลาสการจัดกลุ่มการทดสอบทั้งหมดดังนี้:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;


@RunWith(Suite.class)
@SuiteClasses({
    Test1.class,
    Test2.class,
    Test3.class,
    Test4.class
})public class TestSuite
{
 /* empty class */
}

15

คำถามของคุณมีส่วนที่ไม่ได้รับคำตอบและนั่นคือ "วิธีทดสอบกลุ่มที่เหมาะสมสำหรับวิธีการแบบ B คืออะไร"

คำตอบอย่างเป็นทางการคือคุณใส่คำอธิบายประกอบคลาสด้วย @RunWith (Suite.class) จากนั้นใช้คำอธิบายประกอบ @ Suite.SuiteClasses เพื่อแสดงรายการชั้นเรียน นี่คือวิธีที่นักพัฒนา JUnit ทำ (แสดงรายการทุกคลาสในชุดด้วยตนเอง) ในหลาย ๆ วิธีวิธีนี้เป็นการปรับปรุงซึ่งเป็นการเพิ่มและใช้งานง่ายก่อนที่จะเพิ่มชุดและหลังพฤติกรรมของชุด (เพียงเพิ่ม @BeforeClass และ @AfterClass วิธีการในชั้นเรียนที่มีข้อเขียนด้วย @RunWith - ดีกว่า TestFixture เก่า )

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


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

4

คุณควรใช้ JUnit 4 ดีกว่า

เฟรมเวิร์กจำนวนมากเริ่มลดการสนับสนุน JUnit 3.8

นี่คือจากเอกสารอ้างอิง Spring 3.0:

[คำเตือน] ลำดับชั้นคลาส JUnit 3.8 แบบเดิมถูกคัดค้าน

โดยทั่วไปคุณควรพยายามใช้เฟรมเวิร์กที่เสถียรล่าสุดเสมอเมื่อคุณเริ่มสิ่งใหม่


1
  1. วิธีการ "ที่ต้องการ" คือการใช้คำอธิบายประกอบที่แนะนำตั้งแต่ Junit 4 พวกเขาทำสิ่งต่าง ๆ ได้ง่ายขึ้น (ดูคำถามที่สองของคุณ)

  2. คุณสามารถใช้บล็อก try / catch แบบง่าย ๆ สำหรับสิ่งนั้น:


public void testForException() {
    try {
        Integer.parseInt("just a string");
        fail("Exception should have been thrown");
    } catch (final Exception e) {
        // expected
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.