Làm cách nào để xác nhận thông báo ngoại lệ của tôi với chú thích Kiểm tra JUnit?


315

Tôi đã viết một vài bài kiểm tra JUnit với @Testchú thích. Nếu phương thức kiểm tra của tôi đưa ra một ngoại lệ được kiểm tra và nếu tôi muốn xác nhận thông báo cùng với ngoại lệ đó, có cách nào để làm như vậy với @Testchú thích JUnit không? AFAIK, JUnit 4.7 không cung cấp tính năng này nhưng có phiên bản nào trong tương lai cung cấp tính năng này không? Tôi biết trong .NET bạn có thể xác nhận thông báo và lớp ngoại lệ. Tìm kiếm tính năng tương tự trong thế giới Java.

Đây là những gì tôi muốn:

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

1
Bây giờ tôi nghĩ về nó nhiều hơn một chút ... Bạn có chắc rằng đó là một ý tưởng tốt để khẳng định thông điệp? Câu hỏi của bạn khiến tôi đào sâu vào mã nguồn Junit một chút và có vẻ như họ có thể dễ dàng thêm tính năng này. Thực tế là họ đã không làm cho tôi nghĩ rằng nó có thể không được coi là một thực hành tốt. Tại sao nó quan trọng trong dự án của bạn để khẳng định thông điệp?
c_maker

10
câu hỏi hay. Có thể một phương thức chứa 15 dòng mã ném cùng một ngoại lệ từ 2 nơi khác nhau. Các trường hợp thử nghiệm của tôi cần phải khẳng định không chỉ lớp ngoại lệ mà cả thông điệp trong đó. Trong một thế giới lý tưởng, bất kỳ hành vi bất thường nào cũng phải có ngoại lệ riêng. Nếu đó là trường hợp, câu hỏi của tôi sẽ không bao giờ phát sinh nhưng các ứng dụng sản xuất không có ngoại lệ tùy chỉnh duy nhất cho mỗi hành vi bất thường.
Cshah

Như một lưu ý phụ - có @expectedExceptionMessagechú thích trong PHPUnit.
bancer

Câu trả lời:


535

Bạn có thể sử dụng @Rulechú thích với ExpectedException, như thế này:

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

Lưu ý rằng ví dụ trong ExpectedExceptiontài liệu là (hiện tại) sai - không có nhà xây dựng công cộng, vì vậy bạn phải sử dụng ExpectedException.none().


1
Lưu ý: Đối với tôi khi chuỗi expectMessageđược chỉ định là một chuỗi trống, việc so sánh tin nhắn không được thực hiện
redDevil

1
Hữu ích cho tôi. Cảm ơn. Phép thử phải có throws RuntimeExceptionsau khi thêm mã ném ngoại lệ. Đừng bắt nó ...
Bumbolt

5
Cá nhân tôi sẽ không muốn sử dụng điều này vì việc tạo các trường cho mục đích của một tập hợp nhỏ các phương thức là thực tiễn xấu. Không chỉ trích phản hồi, mà là thiết kế của JUnit. Giải pháp giả thuyết của OP sẽ tốt hơn rất nhiều nếu nó tồn tại.
Sridhar Sarnobat

2
@redDevil: Dự kiếnMessage sẽ kiểm tra xem thông báo lỗi "có chứa" chuỗi được chỉ định trong hàm này không (như chuỗi con của thông báo lỗi)
tuan.dinh

3
wishMessage với tham số chuỗi thực hiện kiểm tra String.contains, để đối sánh chính xác thông báo ngoại lệ sử dụng trình so khớp failure.expectMessage(CoreMatchers.equalTo(...))
hamcrest

42

Tôi thích @Rulecâu trả lời. Tuy nhiên, nếu vì lý do nào đó bạn không muốn sử dụng quy tắc. Có một lựa chọn thứ ba.

@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

Bạn có phải sử dụng @Test(expected=SomeException.class)? Khi chúng ta phải xác nhận thông điệp thực sự của ngoại lệ, đây là những gì chúng ta làm.

@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
Tôi nhận thức được việc viết một khối bắt và sử dụng khẳng định trong đó nhưng để dễ đọc mã hơn tôi muốn làm với các chú thích.
Cshah

Ngoài ra, bạn sẽ không nhận được thông điệp tốt đẹp như khi thực hiện theo cách "đúng".
NeplatnyUdaj

15
Vấn đề với phiên bản thử / bắt, bây giờ JUnit cung cấp @Test(expected=...)ExpectedException, là tôi đã thấy nhiều lần ai đó quên đặt cuộc gọi đến fail()cuối trykhối . Nếu không bị bắt bởi đánh giá mã, bài kiểm tra của bạn có thể dương tính giả và luôn vượt qua.
William Giá

Đây là lý do tại sao tôi không thích tất cả những thứ khai báo này. Nó làm cho nó khó khăn để truy cập những gì bạn muốn.
Sridhar Sarnobat

30

Trong JUnit 4.13 bạn có thể làm:

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

Điều này cũng hoạt động trong JUnit 5 nhưng với các lần nhập khác nhau:

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

...

Giống như giải pháp này. Nên chuyển đến JUnit 5.
WesternGun

Gaaaaaaaaa. 4,13 vẫn còn trong giai đoạn thử nghiệm như ngày hôm nay (Mùa thu năm 2019)? mvnreposective.com/artifact/junit/junit
granadaCoder

v4.13 không còn ở trạng thái beta nữa (phát hành vào tháng 1 năm 2020)
Simon

11

Trên thực tế, cách sử dụng tốt nhất là với thử / bắt. Tại sao? Bởi vì bạn có thể kiểm soát nơi bạn mong đợi ngoại lệ.

Xem xét ví dụ này:

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

Điều gì sẽ xảy ra nếu một ngày mã được sửa đổi và chuẩn bị kiểm tra sẽ ném RuntimeException? Trong trường hợp đó, kiểm tra thực tế thậm chí không được kiểm tra và ngay cả khi nó không đưa ra bất kỳ ngoại lệ nào, kiểm tra sẽ vượt qua.

Đó là lý do tại sao sử dụng thử / bắt tốt hơn là dựa vào chú thích.


Đáng buồn thay, đây là câu trả lời của tôi quá.
Sridhar Sarnobat

2
Các mối quan tâm về thay đổi mã được giảm bớt bằng cách có các trường hợp thử nghiệm nhỏ, hoán vị cụ thể. Đôi khi điều đó là không thể tránh khỏi và chúng ta phải dựa vào phương pháp bắt / thử, nhưng nếu điều đó xảy ra thường xuyên, thì rất có thể là chúng ta cần xem lại cách chúng ta viết các hàm trường hợp thử nghiệm.
luis.espinal

Đó là một vấn đề với bài kiểm tra và / hoặc mã của bạn. Bạn KHÔNG mong đợi một RuntimeException chung, bạn mong đợi một ngoại lệ cụ thể hoặc ít nhất là một thông báo cụ thể.
DennisK

Tôi đã sử dụng RuntimeExceptionnhư một ví dụ, thay thế ngoại lệ này bằng bất kỳ ngoại lệ nào khác.
Krzysztof Cislo

8

Raystorm đã có một câu trả lời tốt. Tôi cũng không phải là một fan hâm mộ lớn của Nội quy. Tôi làm một cái gì đó tương tự, ngoại trừ việc tôi tạo lớp tiện ích sau để giúp dễ đọc và dễ sử dụng, đây là một trong những điểm cộng lớn của chú thích ở vị trí đầu tiên.

Thêm lớp tiện ích này:

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

}

Sau đó, để kiểm tra đơn vị của tôi, tất cả những gì tôi cần là mã này:

@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

Nếu sử dụng @Rule, tập ngoại lệ được áp dụng cho tất cả các phương thức kiểm tra trong lớp Kiểm tra.


2
Sử dụng phản hồi Jesse Merriman, ngoại lệ chỉ được kiểm tra trong các phương thức thử nghiệm gọi tới ExpExExExect () và kỳ vọngEx.ExectMessage (). Các phương thức khác sẽ sử dụng định nghĩa mong đợiEx = ExpectedException.none (), nghĩa là không có ngoại lệ dự kiến.
Egl

2

Tôi không bao giờ thích cách khẳng định ngoại lệ với Junit. Nếu tôi sử dụng "mong đợi" trong chú thích, có vẻ như theo quan điểm của tôi, chúng tôi đang vi phạm mẫu "được đưa ra, khi nào, sau đó" bởi vì "sau đó" được đặt ở đầu định nghĩa kiểm tra.

Ngoài ra, nếu chúng tôi sử dụng "@Rule", chúng tôi phải xử lý rất nhiều mã soạn sẵn. Vì vậy, nếu bạn có thể cài đặt các thư viện mới cho các thử nghiệm của mình, tôi khuyên bạn nên xem AssertJ (thư viện đó hiện có kèm theo SpringBoot)

Sau đó, một bài kiểm tra không vi phạm các nguyên tắc "đã cho / khi / sau đó" và được thực hiện bằng cách sử dụng AssertJ để xác minh:

1 - Ngoại lệ là những gì chúng ta mong đợi. 2 - Nó cũng có một thông điệp dự kiến

Sẽ như thế này:

 @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

Tôi thích câu trả lời của user64141 nhưng thấy rằng nó có thể khái quát hơn. Đây là của tôi:

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

}

Lưu ý rằng việc để lại câu lệnh "fail" trong khối thử khiến ngoại lệ xác nhận liên quan bị bắt; sử dụng return trong câu lệnh bắt ngăn chặn điều này.


0

Nhập thư viện ngoại lệ bắt và sử dụng thư viện đó. Nó sạch hơn nhiều so với ExpectedExceptionquy tắc hoặc a try-catch.

Ví dụ hình thành tài liệu của họ:

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
}

Ai đó có thể giúp tôi hiểu tại sao câu trả lời này là -1
aasha

Câu hỏi đang được hỏi Junitnhưng câu trả lời của bạn là đưa raTestNG
Huazhe Yin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.