Làm thế nào để bạn khẳng định rằng một ngoại lệ nhất định được ném trong các bài kiểm tra JUnit 4?


2000

Làm cách nào tôi có thể sử dụng JUnit4 một cách tự nhiên để kiểm tra rằng một số mã ném ngoại lệ?

Trong khi tôi chắc chắn có thể làm một cái gì đó như thế này:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  boolean thrown = false;

  try {
    foo.doStuff();
  } catch (IndexOutOfBoundsException e) {
    thrown = true;
  }

  assertTrue(thrown);
}

Tôi nhớ lại rằng có một chú thích hoặc một Assert.xyz hoặc một cái gì đó ít hơn rất nhiều và tinh thần của JUnit cho các tình huống này.


21
Vấn đề với bất kỳ cách tiếp cận nào khác nhưng điều này là họ luôn kết thúc bài kiểm tra một khi ngoại lệ đã được đưa ra. Mặt khác, tôi vẫn thường muốn gọi org.mockito.Mockito.verifyvới nhiều tham số khác nhau để đảm bảo rằng một số điều đã xảy ra (ví dụ như dịch vụ logger được gọi với các tham số chính xác) trước khi ném ngoại lệ.
ZeroOne

5
Bạn có thể xem cách kiểm tra ngoại lệ trong trang wiki JUnit github.com/junit-team/junit/wiki/Exception-testing
PhoneixS

6
@ZeroOne - Vì vậy, tôi sẽ có hai bài kiểm tra khác nhau - một cho ngoại lệ và một để xác minh sự tương tác với giả của bạn.
tddmonkey

Có một cách để làm điều này với JUnit 5, tôi đã cập nhật câu trả lời của tôi dưới đây.
Dilini Rajapaksha

Câu trả lời:


2363

Nó phụ thuộc vào phiên bản JUnit và những thư viện khẳng định bạn sử dụng.

Câu trả lời ban đầu JUnit <= 4.12là:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {

    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);

}

Mặc dù câu trả lời https://stackoverflow.com/a/31826781/2986984 có nhiều tùy chọn hơn cho JUnit <= 4.12.

Tài liệu tham khảo :


66
Đoạn mã này sẽ không hoạt động nếu bạn mong đợi một ngoại lệ chỉ ở đâu đó trong mã của bạn và không phải là một cái chăn như thế này.
Oh Chin Boon

4
@skaffman Điều này sẽ không hoạt động với org.junit.experimental.theories. Lý thuyết được điều hành bởi org.junit.experimental.theories .ories
Artem Oboturov

74
Roy Osherove không khuyến khích loại thử nghiệm Ngoại lệ này trong Nghệ thuật thử nghiệm đơn vị , vì Ngoại lệ có thể ở bất kỳ đâu trong thử nghiệm và không chỉ trong thử nghiệm.
Kevin Wittek

21
Tôi không đồng ý với @ Kiview / Roy Osherove. Theo quan điểm của tôi, các bài kiểm tra nên dành cho hành vi, không phải thực hiện. Bằng cách kiểm tra rằng một phương pháp cụ thể có thể gây ra lỗi, bạn đang buộc các bài kiểm tra của mình trực tiếp vào việc thực hiện. Tôi sẽ lập luận rằng thử nghiệm trong phương pháp hiển thị ở trên cung cấp một thử nghiệm có giá trị hơn. Nhắc nhở tôi sẽ thêm là trong trường hợp này tôi sẽ kiểm tra một ngoại lệ tùy chỉnh, để tôi biết tôi đang nhận được ngoại lệ tôi thực sự muốn.
nickbdyer

6
Cũng không. Tôi muốn kiểm tra hành vi của lớp. Điều quan trọng là nếu tôi cố lấy lại thứ gì đó không có ở đó, tôi sẽ có một ngoại lệ. Thực tế là cấu trúc dữ liệu ArrayListđáp ứng get()là không liên quan. Nếu tôi chọn trong tương lai để chuyển sang một mảng nguyên thủy, thì tôi sẽ phải thay đổi việc thực hiện thử nghiệm này. Cấu trúc dữ liệu nên được ẩn, để kiểm tra có thể tập trung vào hành vi của lớp .
nickbdyer

1317

Chỉnh sửa: Bây giờ JUnit 5 và JUnit 4.13 đã được phát hành, tùy chọn tốt nhất sẽ là sử dụng Assertions.assertThrows() (cho JUnit 5) và Assert.assertThrows()(cho JUnit 4.13). Xem câu trả lời khác của tôi để biết chi tiết.

Nếu bạn chưa di chuyển sang JUnit 5, nhưng có thể sử dụng JUnit 4.7, bạn có thể sử dụng ExpectedExceptionQuy tắc:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

Điều này tốt hơn nhiều so với @Test(expected=IndexOutOfBoundsException.class)thử nghiệm sẽ thất bại nếu IndexOutOfBoundsExceptionbị ném trước đófoo.doStuff()

Xem bài viết này để biết chi tiết


14
@skaffman - Nếu tôi hiểu điều này một cách chính xác, có vẻ như ngoại lệ. Khai thác chỉ được áp dụng trong một thử nghiệm, không phải cho cả lớp.
thịt xông khói

5
Nếu ngoại lệ mà chúng ta mong đợi được ném là một ngoại lệ được kiểm tra, chúng ta có nên thêm ném hoặc thử bắt hoặc kiểm tra tình huống này theo cách khác không?
Mohammad Jafar Mashhadi

5
@MartinTrummer Không có mã nào nên chạy sau foo.doStuff () vì ngoại lệ được ném và phương thức được thoát. Có mã sau một ngoại lệ dự kiến ​​(ngoại trừ đóng tài nguyên cuối cùng) dù sao cũng không có ích vì nó không bao giờ được thực thi nếu ném ngoại lệ.
Jason Thompson

9
Đây là cách tiếp cận tốt nhất. Có hai lợi thế ở đây, so với giải pháp của skaffman. Thứ nhất, ExpectedExceptionlớp có các cách khớp thông điệp của ngoại lệ hoặc thậm chí viết trình so khớp của riêng bạn phụ thuộc vào lớp ngoại lệ. Thứ hai, bạn có thể đặt kỳ vọng của mình ngay lập tức trước dòng mã mà bạn dự kiến ​​sẽ ném ngoại lệ - điều đó có nghĩa là thử nghiệm của bạn sẽ thất bại nếu dòng mã sai ném ngoại lệ; trong khi không có cách nào để làm điều đó với giải pháp của skaffman.
Dawood ibn Kareem

5
@MJafarMash nếu kiểm tra ngoại lệ bạn muốn ném, thì bạn sẽ thêm ngoại lệ đó vào mệnh đề ném của phương thức kiểm tra. Bạn làm tương tự bất cứ khi nào bạn đang thử nghiệm một phương thức được khai báo để ném ngoại lệ được kiểm tra, ngay cả khi ngoại lệ không được kích hoạt trong trường hợp thử nghiệm cụ thể.
NamshubWriter

472

Hãy cẩn thận khi sử dụng ngoại lệ dự kiến, bởi vì nó chỉ xác nhận rằng phương thức đã ném ngoại lệ đó, không phải là một dòng mã cụ thể trong thử nghiệm.

Tôi có xu hướng sử dụng điều này để kiểm tra xác thực tham số, bởi vì các phương thức như vậy thường rất đơn giản, nhưng các thử nghiệm phức tạp hơn có thể được phục vụ tốt hơn với:

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

Áp dụng phán đoán.


95
Có lẽ tôi là trường học cũ nhưng tôi vẫn thích điều này. Nó cũng cho tôi một nơi để tự kiểm tra ngoại lệ: đôi khi tôi có ngoại lệ với getters cho một số giá trị nhất định hoặc tôi có thể chỉ cần tìm một giá trị cụ thể trong tin nhắn (ví dụ: tìm "xyz" trong thông báo "mã không được nhận dạng 'xyz' ").
Rodney Gitzel

3
Tôi nghĩ cách tiếp cận của NamshubWriter mang đến cho bạn những điều tốt nhất của cả hai thế giới.
Eddie

4
Sử dụng ExpectedException, bạn có thể gọi N ngoại lệ.Exect cho mỗi phương thức để kiểm tra như ngoại lệ này.Exect (IndexOutOfBoundException. Class); foo.doStuff1 (); ngoại lệ.Exect (IndexOutOfBoundException. class); foo.doStuff2 (); ngoại lệ.Exect (IndexOutOfBoundException. class); foo.doStuff3 ();
dùng1154664

10
@ user1154664 Thật ra, bạn không thể. Sử dụng ExpectedException, bạn chỉ có thể kiểm tra một phương thức đưa ra một ngoại lệ, bởi vì khi phương thức đó được gọi, kiểm tra sẽ dừng thực thi vì nó đã ném ngoại lệ dự kiến!
NamshubWriter

2
Câu đầu tiên của bạn không đúng. Khi sử dụng ExpectedException, điều bình thường cần làm là đặt kỳ vọng ngay trước dòng mà bạn mong đợi để ném ngoại lệ. Theo cách đó, nếu một dòng trước đó ném ngoại lệ, nó sẽ không kích hoạt quy tắc và thử nghiệm sẽ thất bại.
Dawood ibn Kareem

213

Như đã trả lời trước đây, có nhiều cách xử lý các trường hợp ngoại lệ trong JUnit. Nhưng với Java 8, có một cách khác: sử dụng Biểu thức Lambda. Với Lambda Expressions, chúng ta có thể đạt được một cú pháp như thế này:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown chấp nhận một giao diện chức năng, có thể tạo các thể hiện với các biểu thức lambda, tham chiếu phương thức hoặc tham chiếu hàm tạo. assertThrown chấp nhận giao diện đó sẽ mong đợi và sẵn sàng xử lý một ngoại lệ.

Đây là kỹ thuật tương đối đơn giản nhưng mạnh mẽ.

Hãy xem bài đăng trên blog này mô tả kỹ thuật này: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Mã nguồn có thể được tìm thấy ở đây: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Tiết lộ: Tôi là tác giả của blog và dự án.


2
Tôi thích giải pháp này nhưng tôi có thể tải xuống từ maven repo không?
Selwyn

@Airduster một triển khai ý tưởng này có sẵn trên Maven là stefanbirkner.github.io/vallado
NamshubWriter

6
@CristianoFontes một phiên bản đơn giản hơn của API này được dự kiến ​​cho JUnit 4.13. Xem github.com/junit-team/junit/commit/ trên
NamshubWriter 21/07/2015

@RafalBorowiec về mặt kỹ thuật, new DummyService()::someMethodlà một MethodHandle, nhưng cách tiếp cận này hoạt động tốt như nhau với các biểu thức lambda.
Andy

@NamshubWriter, có vẻ như Junit 4.13 đã bị bỏ rơi để ủng hộ
Junit

154

trong Junit, có bốn cách để kiểm tra ngoại lệ.

tháng sáu

  • đối với Junit5.x, bạn có thể sử dụng assertThrowsnhư sau

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }
    

tháng sáu

  • đối với junit4.x, hãy sử dụng thuộc tính 'mong đợi' tùy chọn của chú thích Kiểm tra

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • đối với junit4.x, hãy sử dụng quy tắc ExpectedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
    
  • bạn cũng có thể sử dụng cách thử / bắt cổ điển được sử dụng rộng rãi trong khuôn khổ Junit 3

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
    
  • vì thế

    • nếu bạn thích Junit 5, thì bạn nên thích cái thứ nhất
    • cách thứ 2 được sử dụng khi bạn chỉ muốn kiểm tra loại ngoại lệ
    • hai cái đầu tiên và cuối cùng được sử dụng khi bạn muốn kiểm tra thông báo ngoại lệ
    • Nếu bạn sử dụng Junit 3, thì cái thứ 4 được ưu tiên
  • để biết thêm thông tin, bạn có thể đọc tài liệu nàyhướng dẫn sử dụng Junit5 để biết chi tiết.


6
Đối với tôi đây là câu trả lời tốt nhất, nó bao gồm tất cả các cách rất rõ ràng, cảm ơn! Cá nhân tôi tiếp tục sử dụng tùy chọn thứ 3 ngay cả với Junit4 để dễ đọc, để tránh khối bắt trống, bạn cũng có thể bắt loại Ném và khẳng định loại e
Nicolas Cornette

Có thể sử dụng ExpectedException để mong đợi ngoại lệ được kiểm tra không?
miuser

Tất cả nó là sự tích lũy của ba câu trả lời hàng đầu. IMO, câu trả lời này thậm chí không nên được đăng nếu nó không thêm gì mới. Chỉ cần trả lời (một câu hỏi phổ biến) cho đại diện. Khá vô dụng.
Paul Samsotha

chắc chắn, bởi vì bạn có thể truyền bất kỳ loại nào xuất phát từ Trowablephương thức ExpectedException.expect. xin vui lòng xem đó là chữ ký . @miuser
Walsh

116

tl; dr

  • post-JDK8: Sử dụng AssertJ hoặc lambdas tùy chỉnh để khẳng định hành vi đặc biệt .

  • trước JDK8: Tôi sẽ đề nghị tốt cũ try- catchkhối. ( Đừng quên thêm một fail()xác nhận trước catchkhối )

Bất kể Junit 4 hay JUnit 5.

câu chuyện dài

Có thể tự viết một cách tự làm try - catchchặn hoặc sử dụng các công cụ JUnit ( @Test(expected = ...)hoặc @Rule ExpectedExceptiontính năng quy tắc JUnit).

Nhưng những cách này không quá trang nhã và không kết hợp tốt khả năng đọc thông minh với các công cụ khác. Hơn nữa, công cụ JUnit có một số cạm bẫy.

  1. Khối try- catchbạn phải viết khối xung quanh hành vi được kiểm tra và viết xác nhận trong khối bắt, điều đó có thể ổn nhưng nhiều người thấy rằng kiểu này làm gián đoạn luồng đọc của bài kiểm tra. Ngoài ra, bạn cần phải viết một Assert.failở cuối trykhối. Mặt khác, bài kiểm tra có thể bỏ lỡ một mặt của các xác nhận; PMD , findbugs hoặc Sonar sẽ phát hiện ra những vấn đề như vậy.

  2. Các @Test(expected = ...)tính năng được thú vị khi bạn có thể viết mã ít hơn và sau đó viết bài kiểm tra này được cho là ít bị mã hóa sai sót. Nhưng cách tiếp cận này còn thiếu ở một số lĩnh vực.

    • Nếu thử nghiệm cần kiểm tra những thứ bổ sung về ngoại lệ như nguyên nhân hoặc thông báo (thông báo ngoại lệ tốt thực sự quan trọng, có một loại ngoại lệ chính xác có thể không đủ).
    • Cũng như kỳ vọng được đặt xung quanh trong phương thức, tùy thuộc vào cách viết mã được kiểm tra, phần sai của mã kiểm tra có thể đưa ra ngoại lệ, dẫn đến thử nghiệm dương tính giả và tôi không chắc chắn rằng PMD , findbugs hay Sonar sẽ đưa ra gợi ý về mã như vậy.

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
  3. Các ExpectedExceptionquy tắc cũng là một nỗ lực để sửa chữa những hãy cẩn thận trước, nhưng nó cảm thấy một chút khó khăn khi phải sử dụng như là nó sử dụng một phong cách kỳ vọng, EasyMock người dùng biết rất rõ phong cách này. Nó có thể thuận tiện cho một số người, nhưng nếu bạn tuân theo các nguyên tắc Phát triển hướng hành vi (BDD) hoặc Arrange Act Assert (AAA), ExpectedExceptionquy tắc sẽ không phù hợp với phong cách viết đó. Bên cạnh đó, nó có thể bị vấn đề tương tự như @Testcách, tùy thuộc vào nơi bạn đặt kỳ vọng.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }

    Ngay cả ngoại lệ dự kiến ​​được đặt trước câu lệnh kiểm tra, nó sẽ phá vỡ luồng đọc của bạn nếu các bài kiểm tra tuân theo BDD hoặc AAA.

    Ngoài ra, xem vấn đề bình luận này trên JUnit của tác giả ExpectedException. JUnit 4.13-beta-2 thậm chí không dùng cơ chế này:

    Kéo yêu cầu # 1519 : Khấu hao ExpectedException

    Phương thức Assert.assertThrows cung cấp một cách đẹp hơn để xác minh các ngoại lệ. Ngoài ra, việc sử dụng ExpectedException dễ bị lỗi khi được sử dụng với các quy tắc khác như TestWatcher vì thứ tự các quy tắc rất quan trọng trong trường hợp đó.

Vì vậy, các tùy chọn trên có tất cả tải của họ, và rõ ràng không tránh khỏi các lỗi mã hóa.

  1. Có một dự án mà tôi nhận ra sau khi tạo ra câu trả lời này có vẻ đầy hứa hẹn, đó là ngoại lệ bắt .

    Như mô tả của dự án nói, nó cho phép một lập trình viên viết một dòng mã lưu loát bắt ngoại lệ và đưa ra ngoại lệ này cho xác nhận sau. Và bạn có thể sử dụng bất kỳ thư viện khẳng định nào như Hamcrest hoặc AssertJ .

    Một ví dụ nhanh chóng được lấy từ trang chủ:

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();

    Như bạn có thể thấy mã thực sự đơn giản, bạn bắt ngoại lệ trên một dòng cụ thể, thenAPI là bí danh sẽ sử dụng API AssertJ (tương tự như sử dụng assertThat(ex).hasNoCause()...). Tại một số điểm, dự án dựa vào FEST - Khẳng định tổ tiên của AssertJ . EDIT: Có vẻ như dự án đang hỗ trợ Java 8 Lambdas.

    Hiện tại, thư viện này có hai thiếu sót:

    • Tại thời điểm viết bài này, đáng chú ý là thư viện này dựa trên Mockito 1.x vì nó tạo ra một bản nhái của đối tượng được thử nghiệm đằng sau hiện trường. Vì Mockito vẫn chưa được cập nhật nên thư viện này không thể hoạt động với các lớp cuối cùng hoặc các phương thức cuối cùng . Và ngay cả khi nó dựa trên Mockito 2 trong phiên bản hiện tại, điều này sẽ yêu cầu phải khai báo một nhà sản xuất giả toàn cầu ( inline-mock-maker), một cái gì đó có thể không như bạn muốn, vì nhà sản xuất giả này có những nhược điểm khác nhau mà nhà sản xuất giả thông thường.

    • Nó đòi hỏi một phụ thuộc thử nghiệm khác.

    Những vấn đề này sẽ không được áp dụng một khi thư viện hỗ trợ lambdas. Tuy nhiên, chức năng sẽ được nhân đôi bởi bộ công cụ AssertJ.

    Nếu tính tất cả vào tài khoản nếu bạn không muốn sử dụng công cụ ngoại lệ bắt, tôi sẽ đề xuất cách tốt cũ của khối try- catch, ít nhất là cho đến JDK7. Và đối với người dùng JDK 8, bạn có thể thích sử dụng AssertJ vì nó cung cấp nhiều hơn là chỉ khẳng định các ngoại lệ.

  2. Với JDK8, lambdas bước vào cảnh thử nghiệm và chúng đã được chứng minh là một cách thú vị để khẳng định hành vi đặc biệt. AssertJ đã được cập nhật để cung cấp API thông thạo tốt để khẳng định hành vi đặc biệt.

    Và một bài kiểm tra mẫu với AssertJ :

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
  3. Với việc viết lại gần như hoàn chỉnh của JUnit 5, các xác nhận đã được cải thiện một chút, chúng có thể chứng minh sự thú vị như một cách vượt trội để khẳng định ngoại lệ chính xác. Nhưng thực sự API xác nhận vẫn còn hơi kém, không có gì bên ngoài assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }

    Như bạn nhận thấy assertEqualsvẫn đang quay trở lại void, và như vậy không cho phép xác nhận chuỗi như AssertJ.

    Ngoài ra nếu bạn nhớ cuộc đụng độ tên với Matcherhoặc Assert, hãy chuẩn bị để gặp cuộc đụng độ tương tự với Assertions.

Tôi muốn kết luận rằng hôm nay (2017/03/03) Sự dễ sử dụng, API có thể khám phá của AssertJ , tốc độ phát triển nhanh chóng và như một sự phụ thuộc thử nghiệm thực tế là giải pháp tốt nhất với JDK8 bất kể khung thử nghiệm (JUnit hoặc không), các JDK trước đó nên dựa vào try-catch các khối ngay cả khi chúng cảm thấy cồng kềnh.

Câu trả lời này đã được sao chép từ một câu hỏi khác không có cùng tầm nhìn, tôi là cùng một tác giả.


1
Thêm org.junit.jupiter: junit-jupiter-engine: 5.0.0-RC2 phụ thuộc (ngoài Junit: junit: 4.12) đã có thể sử dụng assertThrows có lẽ không phải là giải pháp ưa thích, nhưng không gây ra bất kỳ giải pháp nào vấn đề cho tôi
anre

Tôi là một fan hâm mộ của việc sử dụng quy tắc ExpectedException nhưng nó luôn làm phiền tôi rằng nó vi phạm với AAA. Bạn đã viết một bài viết tuyệt vời để mô tả tất cả các cách tiếp cận khác nhau và bạn chắc chắn đã khuyến khích tôi thử AssertJ :-) Cảm ơn!
Pim Hazebroek

@PimHazebroek cảm ơn. API AssertJ khá phong phú. Theo ý kiến ​​của tôi tốt hơn là những gì JUnit đề xuất ra khỏi hộp.
Brice

64

Bây giờ JUnit 5 và JUnit 4.13 đã được phát hành, tùy chọn tốt nhất sẽ là sử dụng Assertions.assertThrows() (cho JUnit 5) và Assert.assertThrows()(cho JUnit 4.13). Xem Hướng dẫn sử dụng Junit 5 .

Dưới đây là một ví dụ xác minh một ngoại lệ được đưa ra và sử dụng Truth để đưa ra các xác nhận về thông báo ngoại lệ:

public class FooTest {
  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    IndexOutOfBoundsException e = assertThrows(
        IndexOutOfBoundsException.class, foo::doStuff);

    assertThat(e).hasMessageThat().contains("woops!");
  }
}

Những lợi thế của các phương pháp trong các câu trả lời khác là:

  1. Được xây dựng thành JUnit
  2. Bạn nhận được một thông báo ngoại lệ hữu ích nếu mã trong lambda không ném ngoại lệ và ngăn xếp nếu nó ném ngoại lệ khác
  3. Ngắn gọn
  4. Cho phép các bài kiểm tra của bạn tuân theo Arrange-Act-Assert
  5. Bạn có thể chỉ ra chính xác mã nào bạn đang mong đợi để ném ngoại lệ
  6. Bạn không cần liệt kê ngoại lệ dự kiến ​​trong throwsmệnh đề
  7. Bạn có thể sử dụng khung xác nhận mà bạn chọn để đưa ra các xác nhận về ngoại lệ bị bắt

Một phương thức tương tự sẽ được thêm vào org.junit Asserttrong JUnit 4.13.


Cách tiếp cận này rõ ràng, nhưng tôi không thấy cách này cho phép thử nghiệm của chúng tôi tuân theo "Sắp xếp hành động xác nhận", vì chúng tôi phải bọc phần "Đạo luật" trong một "assertThrow", đây là một khẳng định.
Đồng hồ

@Clockwork Lambda là "hành động". Mục tiêu của Arrange-Act-Assert là làm cho mã sạch và đơn giản (và do đó dễ hiểu và duy trì). Như bạn đã nói, cách tiếp cận này là sạch sẽ.
NamshubWriter

Tôi vẫn hy vọng tôi có thể khẳng định cú ném và ngoại lệ vào cuối bài kiểm tra, trong phần "khẳng định". Trong phương pháp này, bạn cần phải bọc hành động trong một xác nhận đầu tiên để bắt trước.
Đồng hồ

Điều đó sẽ đòi hỏi nhiều mã hơn trong mọi thử nghiệm để thực hiện khẳng định. Đó là nhiều mã hơn và sẽ dễ bị lỗi.
NamshubWriter

43

Làm thế nào về điều này: Bắt một ngoại lệ rất chung chung, đảm bảo rằng nó làm cho nó ra khỏi khối bắt, sau đó khẳng định rằng lớp của ngoại lệ đó là những gì bạn mong đợi. Khẳng định này sẽ thất bại nếu a) ngoại lệ thuộc loại sai (ví dụ: nếu bạn có Con trỏ Null thay thế) và b) ngoại lệ chưa từng bị ném.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

  try {
    foo.doStuff();
  } catch (Throwable ex) {
    e = ex;
  }

  assertTrue(e instanceof IndexOutOfBoundsException);
}

3
Ngoài ra, bạn sẽ không thấy loại ngoại lệ nào trong kết quả kiểm tra khi ngày đến khi thử nghiệm thất bại.
jontejj

Điều này có thể được cải thiện một chút bằng cách thay đổi cách bạn khẳng định vào cuối. assertEquals(ExpectedException.class, e.getClass())sẽ cho bạn thấy các giá trị dự kiến ​​và thực tế khi thử nghiệm thất bại.
Cypher

37

Giải pháp phong cách BDD : JUnit 4 + Bắt ngoại lệ + AssertJ

import static com.googlecode.catchexception.apis.BDDCatchException.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(() -> foo.doStuff());

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

Phụ thuộc

eu.codearte.catch-exception:catch-exception:2.0

36

Sử dụng xác nhận AssertJ , có thể được sử dụng cùng với JUnit:

import static org.assertj.core.api.Assertions.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

Nó tốt hơn @Test(expected=IndexOutOfBoundsException.class)bởi vì nó đảm bảo dòng dự kiến ​​trong thử nghiệm đã ném ngoại lệ và cho phép bạn kiểm tra thêm chi tiết về ngoại lệ, như tin nhắn, dễ dàng hơn:

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

Hướng dẫn Maven / Gradle ở đây.


Cách ngắn gọn nhất và không ai đánh giá cao nó, thật lạ .. Tôi chỉ có một vấn đề với thư viện assertJ, khẳng định đó là xung đột tên khôn ngoan với Junit. thêm về assertJ throwby: JUnit: Kiểm tra ngoại lệ với Java 8 và AssertJ 3.0.0 ~ Codeleak.pl
ycomp

@ycomp Vâng, đó là một câu trả lời mới cho một câu hỏi rất cũ, vì vậy sự khác biệt về điểm số là lừa dối.
weston

Đó có lẽ là giải pháp tốt nhất nếu người ta có thể sử dụng Java 8 và AssertJ!
Pierre Henry

@ycomp Tôi nghi ngờ xung đột tên này có thể là do thiết kế: thư viện AssertJ do đó khuyến khích mạnh mẽ bạn không bao giờ sử dụng JUnit assertThat, luôn luôn là AssertJ. Ngoài ra, phương thức JUnit chỉ trả về loại "thông thường", trong khi phương thức AssertJ trả về một AbstractAssertlớp con ... cho phép xâu chuỗi các phương thức như trên (hoặc bất kỳ thuật ngữ kỹ thuật nào dành cho điều này ...).
mike gặm nhấm

@weston thực sự tôi vừa sử dụng kỹ thuật của bạn trong AssertJ 2.0.0. Không có lý do gì để không nâng cấp, không nghi ngờ gì, nhưng mặc dù bạn có thể muốn biết.
mike gặm nhấm

33

Để giải quyết vấn đề tương tự, tôi đã thiết lập một dự án nhỏ: http://code.google.com.vn/p/catch-exception/

Sử dụng người trợ giúp nhỏ này bạn sẽ viết

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

Điều này ít dài dòng hơn so với quy tắc ExpectedException của JUnit 4.7. So với giải pháp được cung cấp bởi skaffman, bạn có thể chỉ định dòng mã nào bạn mong đợi ngoại lệ. Tôi hi vọng cái này giúp được.


Tôi cũng nghĩ về việc làm một cái gì đó như thế này, nhưng cuối cùng phát hiện ra rằng sức mạnh thực sự của ExpectedException là bạn không chỉ có thể chỉ định ngoại lệ dự kiến ​​mà còn có thể chỉ định một số thuộc tính của ngoại lệ như nguyên nhân dự kiến ​​hoặc thông báo dự kiến.
Jason Thompson

Tôi đoán là giải pháp này có một số nhược điểm giống như giả? Ví dụ, nếu foofinalnó sẽ thất bại bởi vì bạn không thể ủy quyền foo?
Tom

Tom, nếu doStuff () là một phần của giao diện, cách tiếp cận proxy sẽ hoạt động. Nếu không phương pháp này sẽ thất bại, bạn đã đúng.
rwitzel

31

Cập nhật: JUnit5 có một cải tiến để kiểm tra ngoại lệ : assertThrows.

ví dụ sau là từ: Hướng dẫn sử dụng Junit 5

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

Câu trả lời gốc bằng JUnit 4.

Có một số cách để kiểm tra rằng một ngoại lệ được ném ra. Tôi cũng đã thảo luận về các tùy chọn bên dưới trong bài viết của mình Cách viết bài kiểm tra đơn vị tuyệt vời với JUnit

Đặt expectedtham số @Test(expected = FileNotFoundException.class).

@Test(expected = FileNotFoundException.class) 
public void testReadFile() { 
    myClass.readFile("test.txt");
}

Sử dụng try catch

public void testReadFile() { 
    try {
        myClass.readFile("test.txt");
        fail("Expected a FileNotFoundException to be thrown");
    } catch (FileNotFoundException e) {
        assertThat(e.getMessage(), is("The file test.txt does not exist!"));
    }

}

Kiểm tra với ExpectedExceptionQuy tắc.

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

@Test
public void testReadFile() throws FileNotFoundException {

    thrown.expect(FileNotFoundException.class);
    thrown.expectMessage(startsWith("The file test.txt"));
    myClass.readFile("test.txt");
}

Bạn có thể đọc thêm về kiểm tra ngoại lệ trong wiki JUnit4 để kiểm tra Ngoại lệbad.robot - Yêu cầu ngoại lệ Quy tắc JUnit .


22

Bạn cũng có thể làm điều này:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}

12
Trong các thử nghiệm JUnit, nó tốt hơn để sử dụng Assert.fail(), không assert, chỉ trong trường hợp xét nghiệm của bạn chạy trong một môi trường mà khẳng định chưa được bật.
NamshubWriter

14

IMHO, cách tốt nhất để kiểm tra ngoại lệ trong JUnit là mẫu thử / bắt / fail / khẳng định:

// this try block should be as small as possible,
// as you want to make sure you only catch exceptions from your code
try {
    sut.doThing();
    fail(); // fail if this does not throw any exception
} catch(MyException e) { // only catch the exception you expect,
                         // otherwise you may catch an exception for a dependency unexpectedly
    // a strong assertion on the message, 
    // in case the exception comes from anywhere an unexpected line of code,
    // especially important if your checking IllegalArgumentExceptions
    assertEquals("the message I get", e.getMessage()); 
}

assertTruethể là một chút mạnh mẽ đối với một số người, vì vậy assertThat(e.getMessage(), containsString("the message");có thể tốt hơn.



13

Câu trả lời linh hoạt và thanh lịch nhất cho Junit 4 tôi tìm thấy trong blog Mkyong . Nó có tính linh hoạt của việc try/catchsử dụng @Rulechú thích. Tôi thích cách tiếp cận này vì bạn có thể đọc các thuộc tính cụ thể của một ngoại lệ tùy chỉnh.

package com.mkyong;

import com.mkyong.examples.CustomerService;
import com.mkyong.examples.exception.NameNotFoundException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasProperty;

public class Exception3Test {

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

    @Test
    public void testNameNotFoundException() throws NameNotFoundException {

        //test specific type of exception
        thrown.expect(NameNotFoundException.class);

        //test message
        thrown.expectMessage(is("Name is empty!"));

        //test detail
        thrown.expect(hasProperty("errCode"));  //make sure getters n setters are defined.
        thrown.expect(hasProperty("errCode", is(666)));

        CustomerService cust = new CustomerService();
        cust.findByName("");

    }

}

12

Tôi đã thử nhiều phương pháp ở đây, nhưng chúng đều phức tạp hoặc không đáp ứng được yêu cầu của tôi. Trong thực tế, người ta có thể viết một phương thức trợ giúp khá đơn giản:

public class ExceptionAssertions {
    public static void assertException(BlastContainer blastContainer ) {
        boolean caughtException = false;
        try {
            blastContainer.test();
        } catch( Exception e ) {
            caughtException = true;
        }
        if( !caughtException ) {
            throw new AssertionFailedError("exception expected to be thrown, but was not");
        }
    }
    public static interface BlastContainer {
        public void test() throws Exception;
    }
}

Sử dụng nó như thế này:

assertException(new BlastContainer() {
    @Override
    public void test() throws Exception {
        doSomethingThatShouldExceptHere();
    }
});

Không phụ thuộc: không cần mockito, không cần powermock; và hoạt động tốt với các lớp cuối cùng.


Thú vị, nhưng không phù hợp với AAA (Arrange Act Assert), nơi bạn muốn thực hiện Đạo luật và bước Khẳng định trong các bước thực sự khác nhau.
bln-tom

1
@ bln-tom Về mặt kỹ thuật, đây là hai bước khác nhau, chúng chỉ không theo thứ tự đó. ; p
Trejkaz

10

Giải pháp Java 8

Nếu bạn muốn một giải pháp:

  • Sử dụng lambdas Java 8
  • không phụ thuộc vào bất kỳ ma thuật JUnit
  • Cho phép bạn kiểm tra nhiều ngoại lệ trong một phương thức kiểm tra
  • Kiểm tra ngoại lệ được ném bởi một bộ dòng cụ thể trong phương thức kiểm tra của bạn thay vì bất kỳ dòng không xác định nào trong toàn bộ phương thức kiểm tra
  • Mang lại đối tượng ngoại lệ thực tế đã được ném để bạn có thể kiểm tra thêm

Đây là một chức năng tiện ích mà tôi đã viết:

public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
    try
    {
        runnable.run();
    }
    catch( Throwable throwable )
    {
        if( throwable instanceof AssertionError && throwable.getCause() != null )
            throwable = throwable.getCause(); //allows "assert x != null : new IllegalArgumentException();"
        assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
        assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
        @SuppressWarnings( "unchecked" )
        T result = (T)throwable;
        return result;
    }
    assert false; //expected exception was not thrown.
    return null; //to keep the compiler happy.
}

( lấy từ blog của tôi )

Sử dụng nó như sau:

@Test
public void testThrows()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            throw new RuntimeException( "fail!" );
        } );
    assert e.getMessage().equals( "fail!" );
}


8

Trong trường hợp của tôi, tôi luôn nhận được RuntimeException từ db, nhưng các thông báo khác nhau. Và ngoại lệ cần phải được xử lý tương ứng. Đây là cách tôi đã thử nghiệm nó:

@Test
public void testThrowsExceptionWhenWrongSku() {

    // Given
    String articleSimpleSku = "999-999";
    int amountOfTransactions = 1;
    Exception exception = null;

    // When
    try {
        createNInboundTransactionsForSku(amountOfTransactions, articleSimpleSku);
    } catch (RuntimeException e) {
        exception = e;
    }

    // Then
    shouldValidateThrowsExceptionWithMessage(exception, MESSAGE_NON_EXISTENT_SKU);
}

private void shouldValidateThrowsExceptionWithMessage(final Exception e, final String message) {
    assertNotNull(e);
    assertTrue(e.getMessage().contains(message));
}

1
trước khi xếp hàng } catch (, bạn nên chènfail("no exception thrown");
Daniel Alder

6

Chỉ cần tạo một Matcher có thể tắt và bật, như thế này:

public class ExceptionMatcher extends BaseMatcher<Throwable> {
    private boolean active = true;
    private Class<? extends Throwable> throwable;

    public ExceptionMatcher(Class<? extends Throwable> throwable) {
        this.throwable = throwable;
    }

    public void on() {
        this.active = true;
    }

    public void off() {
        this.active = false;
    }

    @Override
    public boolean matches(Object object) {
        return active && throwable.isAssignableFrom(object.getClass());
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("not the covered exception type");
    }
}

Để dùng nó:

thêm public ExpectedException exception = ExpectedException.none();, sau đó:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();

6

Trong JUnit 4 trở lên, bạn có thể kiểm tra các ngoại lệ như sau

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


điều này cung cấp rất nhiều tính năng có thể được sử dụng để cải thiện các bài kiểm tra JUnit của chúng tôi.
Nếu bạn thấy ví dụ dưới đây, tôi đang thử nghiệm 3 điều về ngoại lệ.

  1. Loại ngoại lệ ném
  2. Tin nhắn ngoại lệ
  3. Nguyên nhân của ngoại lệ


public class MyTest {

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

    ClassUnderTest classUnderTest;

    @Before
    public void setUp() throws Exception {
        classUnderTest = new ClassUnderTest();
    }

    @Test
    public void testAppleisSweetAndRed() throws Exception {

        exceptions.expect(Exception.class);
        exceptions.expectMessage("this is the exception message");
        exceptions.expectCause(Matchers.<Throwable>equalTo(exceptionCause));

        classUnderTest.methodUnderTest("param1", "param2");
    }

}

6

Chúng ta có thể sử dụng một xác nhận thất bại sau khi phương thức phải trả về một ngoại lệ:

try{
   methodThatThrowMyException();
   Assert.fail("MyException is not thrown !");
} catch (final Exception exception) {
   // Verify if the thrown exception is instance of MyException, otherwise throws an assert failure
   assertTrue(exception instanceof MyException, "An exception other than MyException is thrown !");
   // In case of verifying the error message
   MyException myException = (MyException) exception;
   assertEquals("EXPECTED ERROR MESSAGE", myException.getMessage());
}

3
Thứ hai catchsẽ nuốt dấu vết ngăn xếp nếu một số ngoại lệ khác bị ném, mất thông tin hữu ích
NamshubWriter

5

Ngoài những gì NamShubWriter đã nói, hãy đảm bảo rằng:

  • Ví dụ ExpectedException là công khai ( Câu hỏi liên quan )
  • Phương thức ExpectedException không được khởi tạo trong phương thức @B Before. Bài đăng này giải thích rõ ràng tất cả những điều phức tạp trong lệnh thực hiện của JUnit.

Đừng không làm điều này:

@Rule    
public ExpectedException expectedException;

@Before
public void setup()
{
    expectedException = ExpectedException.none();
}

Cuối cùng, bài đăng trên blog này minh họa rõ ràng làm thế nào để khẳng định rằng một ngoại lệ nhất định được ném.


4

Tôi khuyên bạn nên thư viện assertj-coređể xử lý ngoại lệ trong bài kiểm tra Junit

Trong java 8, như thế này:

//given

//when
Throwable throwable = catchThrowable(() -> anyService.anyMethod(object));

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);

2

Giải pháp Junit4 với Java8 là sử dụng chức năng này:

public Throwable assertThrows(Class<? extends Throwable> expectedException, java.util.concurrent.Callable<?> funky) {
    try {
        funky.call();
    } catch (Throwable e) {
        if (expectedException.isInstance(e)) {
            return e;
        }
        throw new AssertionError(
                String.format("Expected [%s] to be thrown, but was [%s]", expectedException, e));
    }
    throw new AssertionError(
            String.format("Expected [%s] to be thrown, but nothing was thrown.", expectedException));
}

Cách sử dụng là:

    assertThrows(ValidationException.class,
            () -> finalObject.checkSomething(null));

Lưu ý rằng giới hạn duy nhất là sử dụng finaltham chiếu đối tượng trong biểu thức lambda. Giải pháp này cho phép tiếp tục các xác nhận thử nghiệm thay vì mong đợi có thể điều chỉnh được ở cấp phương pháp sử dụng @Test(expected = IndexOutOfBoundsException.class)giải pháp.


1

Lấy ví dụ, bạn muốn viết Junit cho đoạn mã được đề cập bên dưới

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

    throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}

Đoạn mã trên là để kiểm tra một số ngoại lệ không xác định có thể xảy ra và đoạn dưới đây là để xác nhận một số ngoại lệ với thông báo tùy chỉnh.

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

private Demo demo;
@Before
public void setup(){

    demo=new Demo();
}
@Test(expected=ArithmeticException.class)
public void testIfItThrowsAnyException() {

    demo.divideByZeroDemo(5, 0);

}

@Test
public void testExceptionWithMessage(){


    exception.expectMessage("Array is out of bound");
    exception.expect(ArrayIndexOutOfBoundsException.class);
    demo.exceptionWithMessage(new String[]{"This","is","a","demo"});
}

1
    @Test(expectedException=IndexOutOfBoundsException.class) 
    public void  testFooThrowsIndexOutOfBoundsException() throws Exception {
         doThrow(IndexOutOfBoundsException.class).when(foo).doStuff();  
         try {
             foo.doStuff(); 
            } catch (IndexOutOfBoundsException e) {
                       assertEquals(IndexOutOfBoundsException .class, ex.getCause().getClass());
                      throw e;

               }

    }

Đây là một cách khác để kiểm tra phương pháp ném ngoại lệ chính xác hay không.


1

Khung JUnit có assertThrows()phương thức:

ArithmeticException exception = assertThrows(ArithmeticException.class, () ->
    calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());

0

Với Java 8, bạn có thể tạo một phương thức lấy mã để kiểm tra và ngoại lệ dự kiến ​​là tham số:

private void expectException(Runnable r, Class<?> clazz) { 
    try {
      r.run();
      fail("Expected: " + clazz.getSimpleName() + " but not thrown");
    } catch (Exception e) {
      if (!clazz.isInstance(e)) fail("Expected: " + clazz.getSimpleName() + " but " + e.getClass().getSimpleName() + " found", e);
    }
  }

và sau đó trong bài kiểm tra của bạn:

expectException(() -> list.sublist(0, 2).get(2), IndexOutOfBoundsException.class);

Những lợi ích:

  • không dựa vào bất kỳ thư viện
  • kiểm tra cục bộ - chính xác hơn và cho phép có nhiều xác nhận như thế này trong một thử nghiệm nếu cần
  • dễ sử dụng
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.