ฉันจะยืนยันข้อความแสดงข้อยกเว้นด้วยคำอธิบายประกอบการทดสอบ JUnit ได้อย่างไร


315

ฉันได้เขียนการทดสอบ JUnit สองสาม@Testฉบับพร้อมคำอธิบายประกอบ หากวิธีทดสอบของฉันมีข้อยกเว้นที่ตรวจสอบและหากฉันต้องการยืนยันข้อความพร้อมกับข้อยกเว้นจะมีวิธีการทำ@TestคำอธิบายประกอบJUnit หรือไม่? AFAIK, JUnit 4.7 ไม่ได้มีคุณสมบัตินี้ แต่มีรุ่นอื่น ๆ ในอนาคตหรือไม่ ฉันรู้ใน. NET คุณสามารถยืนยันข้อความและชั้นยกเว้น กำลังมองหาคุณสมบัติที่คล้ายกันในโลก Java

นี่คือสิ่งที่ฉันต้องการ:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}

1
ตอนนี้ฉันคิดอีกเล็กน้อยแล้ว ... คุณแน่ใจหรือไม่ว่าจะเป็นการยืนยันข้อความ คำถามของคุณทำให้ฉันขุดลงในซอร์สโค้ด Junit เล็กน้อยและดูเหมือนว่าพวกเขาสามารถเพิ่มคุณสมบัตินี้ได้อย่างง่ายดาย ความจริงที่ว่าพวกเขาไม่ได้ทำให้ฉันคิดว่ามันอาจไม่ได้รับการพิจารณาว่าเป็นการปฏิบัติที่ดี เหตุใดโครงการของคุณจึงสำคัญต่อการยืนยันข้อความ
c_maker

10
เป็นคำถามที่ดีบอกว่าวิธีการที่บรรจุโค้ด 15 บรรทัดนั้นมีข้อยกเว้นเดียวกันจากที่ต่างกัน 2 แห่ง กรณีทดสอบของฉันต้องยืนยันไม่ใช่แค่คลาสยกเว้น แต่รวมถึงข้อความในนั้นด้วย ในโลกอุดมคติพฤติกรรมที่ผิดปกติใด ๆ ควรมีข้อยกเว้นของตัวเองหากเป็นเช่นนั้นคำถามของฉันจะไม่เกิดขึ้น แต่แอปพลิเคชันการผลิตไม่ได้มีข้อยกเว้นเฉพาะสำหรับพฤติกรรมที่ผิดปกติแต่ละอย่าง
Cshah

ในฐานะที่เป็นข้อความด้าน - มี@expectedExceptionMessageคำอธิบายประกอบใน PHPUnit
bancer

คำตอบ:


535

คุณสามารถใช้@Ruleคำอธิบายประกอบด้วยExpectedExceptionเช่นนี้:

@Rule
public ExpectedException expectedEx = ExpectedException.none();

@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
    expectedEx.expect(RuntimeException.class);
    expectedEx.expectMessage("Employee ID is null");

    // do something that should throw the exception...
    System.out.println("=======Starting Exception process=======");
    throw new NullPointerException("Employee ID is null");
}

หมายเหตุว่าตัวอย่างในที่ExpectedExceptionเอกสารถูก (ปัจจุบัน) ผิด - ExpectedException.none()ไม่มีการสร้างสาธารณะเพื่อให้คุณมีการใช้งาน


1
หมายเหตุ: สำหรับฉันเมื่อมีการexpectMessageระบุว่าเป็นสตริงว่างเปล่าการเปรียบเทียบข้อความไม่ได้ดำเนินการ
redDevil

1
มีประโยชน์กับฉัน ขอบคุณ วิธีทดสอบควรมีthrows RuntimeExceptionหลังจากเพิ่มรหัสที่ส่งข้อยกเว้น อย่าจับมัน ...
Bumbolt

5
ฉันเองไม่ต้องการใช้สิ่งนี้ตั้งแต่สร้างเขตข้อมูลสำหรับวัตถุประสงค์ของชุดย่อยของวิธีเป็นการปฏิบัติที่ไม่ดี ไม่ใช่คำวิจารณ์ของการตอบสนอง แต่เป็นการออกแบบของ JUnit ทางออกสมมุติของ OP จะดีขึ้นมากถ้ามันมีอยู่
Sridhar Sarnobat

2
@redDevil: ตรวจสอบ expectedMessage หากเกิดข้อผิดพลาด "มี" สตริงที่ระบุไว้ในฟังก์ชั่นนี้ (เช่นย่อยของข้อความข้อผิดพลาด)
tuan.dinh

3
expectMessage กับพารามิเตอร์สตริงไม่ได้ตรวจสอบ String.contains สำหรับการแข่งขันที่แน่นอนของการใช้ข้อความยกเว้น hamcrest จับคู่failure.expectMessage(CoreMatchers.equalTo(...))
ซิวาบาลน

42

ฉันชอบ@Ruleคำตอบ อย่างไรก็ตามถ้าด้วยเหตุผลบางอย่างคุณไม่ต้องการใช้กฎ มีตัวเลือกที่สามคือ

@Test (expected = RuntimeException.class)
public void myTestMethod()
{
   try
   {
      //Run exception throwing operation here
   }
   catch(RuntimeException re)
   {
      String message = "Employee ID is null";
      assertEquals(message, re.getMessage());
      throw re;
    }
    fail("Employee Id Null exception did not throw!");
  }

32

คุณต้องใช้@Test(expected=SomeException.class)หรือไม่? เมื่อเราต้องยืนยันข้อความจริงของข้อยกเว้นนี่คือสิ่งที่เราทำ

@Test
public void myTestMethod()
{
  try
  {
    final Integer employeeId = null;
    new Employee(employeeId);
    fail("Should have thrown SomeException but did not!");
  }
  catch( final SomeException e )
  {
    final String msg = "Employee ID is null";
    assertEquals(msg, e.getMessage());
  }
}

6
ฉันตระหนักถึงการเขียน catch block และใช้ assert ภายใน แต่เพื่อความสะดวกในการอ่านโค้ดที่ดีขึ้นฉันต้องการทำหมายเหตุประกอบ
Cshah

นอกจากนี้คุณจะไม่ได้รับข้อความที่ดีเช่นเมื่อทำในลักษณะ "ถูกต้อง"
NeplatnyUdaj

15
ปัญหากับรุ่นลอง / จับตอนนี้ที่ JUnit ให้@Test(expected=...)และExpectedExceptionเป็นที่ฉันได้เห็นหลายครั้งหลายคนลืมที่จะนำการเรียกร้องให้fail()ในตอนท้ายของtryบล็อก หากไม่ได้รับการตรวจสอบจากรหัสการทดสอบของคุณอาจเป็นเท็จและผ่านได้เสมอ
William Price

นี่คือเหตุผลที่ฉันไม่ชอบสิ่งที่ประกาศนี้ทั้งหมด ทำให้เข้าถึงสิ่งที่คุณต้องการได้ยาก
Sridhar Sarnobat

30

ใน JUnit 4.13 คุณสามารถทำได้:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

...

@Test
void exceptionTesting() {
  IllegalArgumentException exception = assertThrows(
    IllegalArgumentException.class, 
    () -> { throw new IllegalArgumentException("a message"); }
  );

  assertEquals("a message", exception.getMessage());
}

สิ่งนี้สามารถใช้งานได้ในJUnit 5แต่มีการนำเข้าที่แตกต่างกัน:

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

...

ชอบวิธีนี้ ควรย้ายไปที่ JUnit 5
WesternGun

Gaaaaaaaaa 4.13 ยังอยู่ในช่วงเบต้า ณ วันนี้ (ฤดูใบไม้ร่วง 2019)? mvnrepository.com/artifact/junit/junit
granadaCoder

v4.13 ไม่ได้อยู่ในสถานะเบต้าอีกต่อไป (เปิดตัวในเดือนมกราคม 2020)
Simon

11

ที่จริงแล้วการใช้งานที่ดีที่สุดคือลอง / จับ ทำไม? เพราะคุณสามารถควบคุมสถานที่ที่คุณคาดว่าจะได้รับการยกเว้น

ลองพิจารณาตัวอย่างนี้:

@Test (expected = RuntimeException.class)
public void someTest() {
   // test preparation
   // actual test
}

จะเกิดอะไรขึ้นถ้าวันหนึ่งมีการแก้ไขโค้ดและการเตรียมการทดสอบจะมี RuntimeException ในกรณีนั้นการทดสอบจริงจะไม่ได้ทดสอบและแม้ว่าจะไม่เกิดข้อยกเว้นการทดสอบจะผ่าน

นั่นคือสาเหตุที่ดีกว่าการใช้ลอง / จับมากกว่าพึ่งพาคำอธิบายประกอบ


น่าเศร้าที่นี่เป็นคำตอบของฉันเช่นกัน
Sridhar Sarnobat

2
ความกังวลเกี่ยวกับการเปลี่ยนแปลงรหัสจะลดลงโดยมีกรณีทดสอบขนาดเล็กเปลี่ยนแปลงเฉพาะ บางครั้งมันเป็นสิ่งที่หลีกเลี่ยงไม่ได้และเราต้องพึ่งพาวิธีการจับ / ลอง แต่ถ้าเกิดขึ้นบ่อยครั้งโอกาสที่เราต้องแก้ไขวิธีที่เราเขียนฟังก์ชั่นกรณีทดสอบของเรา
luis.espinal

นั่นเป็นปัญหาของการทดสอบและ / หรือรหัสของคุณ คุณไม่คาดหวังว่า RuntimeException ทั่วไปคุณคาดหวังว่าจะมีข้อยกเว้นเฉพาะหรืออย่างน้อยก็มีข้อความเฉพาะ
DennisK

ฉันใช้RuntimeExceptionเป็นตัวอย่างแทนที่ข้อยกเว้นนี้ด้วยข้อยกเว้นอื่น ๆ
Krzysztof Cislo

8

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

เพิ่มคลาสยูทิลิตี้นี้:

import org.junit.Assert;

public abstract class ExpectedRuntimeExceptionAsserter {

    private String expectedExceptionMessage;

    public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run(){
        try{
            expectException();
            Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
        } catch (RuntimeException e){
            Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
        }
    }

    protected abstract void expectException();

}

จากนั้นสำหรับการทดสอบหน่วยของฉันทั้งหมดที่ฉันต้องการคือรหัสนี้:

@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
    new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
        @Override
        protected void expectException() {
            throw new RuntimeException("anonymous user can't access privileged resource");
        }
    }.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}

2

หากใช้ @Rule จะมีการใช้ชุดข้อยกเว้นกับวิธีการทดสอบทั้งหมดในคลาสทดสอบ


2
ใช้การตอบสนอง Jesse Merriman ข้อยกเว้นจะถูกตรวจสอบเฉพาะในวิธีการทดสอบที่เรียกไปยังExExExpect () และที่คาดไว้ วิธีอื่น ๆ จะใช้คำจำกัดความที่คาดไว้ Ex = ExpectedException.none () นั่นคือไม่มีข้อยกเว้นที่คาดไว้
Egl

2

ฉันไม่เคยชอบวิธีที่จะจัดการกับข้อยกเว้นกับ Junit หากฉันใช้คำว่า "คาดหวัง" ในคำอธิบายประกอบดูเหมือนว่าจากมุมมองของฉันเรากำลังละเมิด "ที่กำหนดเมื่อนั้น" รูปแบบเพราะ "วาง" ที่อยู่ด้านบนของคำนิยามการทดสอบ

นอกจากนี้หากเราใช้ "@Rule" เราต้องจัดการกับรหัสสำเร็จรูปมาก ดังนั้นหากคุณสามารถติดตั้งไลบรารีใหม่สำหรับการทดสอบของคุณฉันขอแนะนำให้ดู AssertJ (ตอนนี้ไลบรารีนั้นมาพร้อมกับ SpringBoot)

จากนั้นการทดสอบที่ไม่ละเมิดหลักการ "ที่กำหนด / เมื่อ / แล้ว" และใช้ AssertJ ในการตรวจสอบ:

1 - ข้อยกเว้นคือสิ่งที่เราคาดหวัง 2 - นอกจากนี้ยังมีข้อความที่คาดหวัง

จะมีลักษณะเช่นนี้:

 @Test
void should_throwIllegalUse_when_idNotGiven() {

    //when
    final Throwable raisedException = catchThrowable(() -> getUserDAO.byId(null));

    //then
    assertThat(raisedException).isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("Id to fetch is mandatory");
}

1

ฉันชอบคำตอบของผู้ใช้ 64,141 แต่พบว่ามันอาจจะเป็นแบบทั่วไปมากขึ้น นี่คือของฉัน:

public abstract class ExpectedThrowableAsserter implements Runnable {

    private final Class<? extends Throwable> throwableClass;
    private final String expectedExceptionMessage;

    protected ExpectedThrowableAsserter(Class<? extends Throwable> throwableClass, String expectedExceptionMessage) {
        this.throwableClass = throwableClass;
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run() {
        try {
            expectException();
        } catch (Throwable e) {
            assertTrue(String.format("Caught unexpected %s", e.getClass().getSimpleName()), throwableClass.isInstance(e));
            assertEquals(String.format("%s caught, but unexpected message", throwableClass.getSimpleName()), expectedExceptionMessage, e.getMessage());
            return;
        }
        fail(String.format("Expected %s, but no exception was thrown.", throwableClass.getSimpleName()));
    }

    protected abstract void expectException();

}

โปรดทราบว่าการปล่อยให้คำสั่ง "ล้มเหลว" ในบล็อกลองเป็นสาเหตุให้เกิดข้อยกเว้นการยืนยันที่เกี่ยวข้อง การใช้การส่งคืนภายในคำสั่ง catch จะป้องกันสิ่งนี้


0

นำเข้าไลบรารีcatch-exceptionและใช้สิ่งนั้น มันสะอาดมากขึ้นกว่ากฎหรือExpectedExceptiontry-catch

ตัวอย่างจากเอกสาร:

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;

// given: an empty list
List myList = new ArrayList();

// when: we try to get the first element of the list
catchException(myList).get(1);

// then: we expect an IndexOutOfBoundsException with message "Index: 1, Size: 0"
assertThat(caughtException(),
  allOf(
    instanceOf(IndexOutOfBoundsException.class),
    hasMessage("Index: 1, Size: 0"),
    hasNoCause()
  )
);

-2
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}

บางคนสามารถช่วยฉันเข้าใจว่าทำไมคำตอบนี้คือ -1
aasha

คำถามกำลังถามJunitแต่คำตอบคือให้TestNG
Huazhe Yin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.