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


Làm thế nào tôi có thể sử dụng JUnit4 idiomatically để kiểm tra rằng một số mã ném một 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ì đó đó là ít kludgy và xa hơn trong tinh thần của JUnit cho các loại tình huống.


1624
2017-10-01 06:56


gốc


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 thử nghiệm khi ngoại lệ đã được ném. Tôi, mặt khác, thường vẫn muốn gọi org.mockito.Mockito.verify với các thông số khác nhau để đảm bảo rằng một số điều đã xảy ra (chẳng hạn như dịch vụ logger được gọi với thông số chính xác) trước khi ngoại lệ được ném. - ZeroOne
Bạn có thể xem cách kiểm tra ngoại lệ trong trang wiki của JUnit github.com/junit-team/junit/wiki/Exception-testing - PhoneixS
@ZeroOne - 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 tương tác với mô hình 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ác câu trả lời:


JUnit 4 có hỗ trợ cho việc này:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

Tài liệu tham khảo: https://junit.org/junit4/faq.html#atests_7


1945
2017-10-01 07:12



Đ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
@skaffman Điều này sẽ không hoạt động với org.junit.experimental.theories.Theory được quản lý bởi org.junit.experimental.theories.Theories - Artem Oboturov
Roy Osherove không khuyến khích loại thử nghiệm Ngoại lệ này Nghệ thuật thử nghiệm đơn vị, vì Ngoại lệ có thể ở bất cứ nơi nào bên trong bài kiểm tra và không chỉ trong đơn vị được kiểm tra. - Kevin Wittek
Tôi không đồng ý với @ Kiview / Roy Osherove. Theo quan điểm của tôi, các xét nghiệm 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ể ném một lỗi, bạn đang buộc các bài kiểm tra của bạn trực tiếp thực hiện. Tôi cho rằng thử nghiệm trong phương pháp được trình bày ở trên cung cấp một thử nghiệm có giá trị hơn. Tôi sẽ nói thêm rằng 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ệ mà tôi thực sự muốn. - nickbdyer
Cũng không. Tôi muốn kiểm tra hành vi của lớp. Điều gì là quan trọng, là nếu tôi cố gắng lấy một cái gì đó mà không có ở đó, tôi nhận được một ngoại lệ. Thực tế là cấu trúc dữ liệu là ArrayList phản hồi get() 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 phải được ẩn, để kiểm tra có thể tập trung vào hành vi của lớp học. - nickbdyer


Chỉnh sửa Bây giờ JUnit5 đã phát hành, lựa chọn tốt nhất sẽ là sử dụng Assertions.assertThrows() (xem câu trả lời khác của tôi).

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 ExpectedException Qui định:

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 @Test(expected=IndexOutOfBoundsException.class) bởi vì thử nghiệm sẽ thất bại nếu IndexOutOfBoundsException được ném trước foo.doStuff()

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


1166
2018-05-29 17:16



@skaffman - Nếu tôi đã hiểu điều này một cách chính xác, có vẻ như exception.expect đang được áp dụng chỉ trong một bài kiểm tra, không phải toàn bộ lớp. - bacar
Nếu ngoại lệ chúng tôi mong đợi được ném là ngoại lệ được kiểm tra, chúng tôi có nên thêm lần ném hoặc thử bắt hoặc thử nghiệm tình huống này theo cách khác không? - MJafar Mash
@MartinTrummer Không có mã nào được 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 trong một cuối cùng) là không hữu ích anyway vì nó sẽ không bao giờ được thực thi nếu ngoại lệ được ném. - Jason Thompson
Đây là cách tiếp cận tốt nhất. Có hai ưu điểm ở đây, so với giải pháp của skaffman. Thứ nhất, ExpectedException class có cách kết hợp thông điệp của exception, hoặc thậm chí viết matcher của riêng bạn mà phụ thuộc vào class exception. Thứ hai, bạn có thể đặt kỳ vọng của bạn ngay trước dòng mã mà bạn mong đợi để ném ngoại lệ - có nghĩa là kiểm tra 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 nghệ sĩ. - Dawood ibn Kareem
@MJafarMash nếu ngoại lệ bạn mong đợi để ném được kiểm tra, sau đó bạn sẽ thêm ngoại lệ đó vào mệnh đề ném của phương pháp thử nghiệm. 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 một ngoại lệ đã 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


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

Tôi có xu hướng sử dụng điều này để kiểm tra xác thực thông số, bởi vì các phương pháp 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 quyết.


409
2017-10-01 09:31



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 cung cấp cho tôi một nơi để kiểm tra ngoại lệ: đôi khi tôi có ngoại lệ với getters cho các giá trị nhất định hoặc tôi có thể chỉ cần tìm một giá trị cụ thể trong thông báo (ví dụ: tìm kiếm "xyz" trong thông báo "mã không được công nhận 'xyz' "). - Rodney Gitzel
Tôi nghĩ cách tiếp cận của NamshubWriter mang lại cho bạn những điều tốt đẹp nhất của cả hai thế giới. - Eddie
+1 hữu ích trong một số trường hợp mà dự kiến ​​= xx không khớp với yêu cầu. - Oh Chin Boon
Sử dụng ExpectedException bạn có thể gọi N exception.expect cho mỗi phương thức để kiểm tra như thế này exception.expect (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 (); - user1154664
@ user1154664 Trên thực tế, bạn không thể. Sử dụng ExpectedException bạn chỉ có thể thử nghiệm một phương thức ném một ngoại lệ, bởi vì khi phương thức đó được gọi, kiểm tra sẽ ngừng thực thi vì nó đã ném ngoại lệ dự kiến! - NamshubWriter


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

@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ác cá thể của nó có thể được tạo ra với các biểu thức lambda, các tham chiếu phương thức hoặc các tham chiếu hàm tạo. assertTrown 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.


189
2017-07-07 22:35



Tôi thích giải pháp này nhưng tôi có thể tải về từ một repo maven? - Selwyn
@Airduster một triển khai ý tưởng này có sẵn trên Maven stefanbirkner.github.io/vallado - NamshubWriter
Họ nên gửi điều này như một yêu cầu kéo đến JUnit ... - Cristiano Fontes
@CristianoFontes 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/… - NamshubWriter
@RafalBorowiec về mặt kỹ thuật, new DummyService()::someMethod là 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


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

  • cho junit4.x, sử dụng thuộc tính 'mong đợi' tùy chọn của phụ lục Kiểm tra

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • cho junit4.x, 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ử / nắm 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.
        }
    }
    
  • cuối cùng, đối với junit5.x, bạn cũng có thể sử dụng assertThrows như sau

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

    • cách thứ nhất được sử dụng khi bạn chỉ muốn kiểm tra loại ngoại lệ
    • ba cách khác được sử dụng khi bạn muốn kiểm tra thông báo ngoại lệ thêm
    • nếu bạn sử dụng junit 3, thì thứ ba được ưu tiên
    • nếu bạn thích junit 5, thì bạn nên thích cái thứ 4
  • để biết thêm thông tin, bạn có thể đọc tài liệu này và junit5 hướng dẫn sử dụng để biết chi tiết.


84
2017-08-05 08:05



Đối với tôi đây là câu trả lời hay 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 catch trống, bạn cũng có thể bắt Throwable và khẳng định kiểu e - Nicolas Cornette
Có thể sử dụng ExpectedException để mong đợi ngoại lệ đã kiểm tra không? - miuser
Tất cả nó là một 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ỉ 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ể vượt qua bất kỳ loại nào có nguồn gốc từ Trowable phương pháp ExpectedException.expect. xin vui lòng xem đó là chữ ký. @miuser - walsh


tl; dr

  • pre-JDK8: Tôi sẽ khuyên bạn nên mua hàng cũ try- -catch khối.

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

Bất kể Junit 4 hoặc JUnit 5.

câu chuyện dài

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

Nhưng những cách này không thanh lịch và không pha trộn tốt khả năng đọc khôn ngoan với các công cụ khác.

  1. Các try- -catch khối bạn phải viết các khối xung quanh hành vi thử nghiệm, và viết khẳng định trong khối catch, có thể được tốt nhưng nhiều người tìm thấy phong cách này ngắt dòng chảy đọc của một bài kiểm tra. Ngoài ra bạn cần phải viết một Assert.fail ở cuối của try nếu không thì thử nghiệm có thể bỏ lỡ một bên của các xác nhận; PMD, tìm kiếm hoặc là Sonar sẽ phát hiện những vấn đề như vậy.

  2. Các @Test(expected = ...) tính năng thú vị khi bạn có thể viết ít mã hơn và sau đó viết thử nghiệm này được cho là ít bị lỗi mã hóa hơn. Nhưng phương pháp tiếp cận ths thiếu một số khu vực.

    • Nếu kiểm tra cần kiểm tra những điều bổ sung về ngoại lệ như nguyên nhân hoặc thông báo (các 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 mã được thử nghiệm được viết thì phần sai của mã thử nghiệm có thể ném ngoại lệ, dẫn đến kiểm tra dương tính giả và tôi không chắc chắn rằng PMD, tìm kiếm hoặc là 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 ExpectedException quy tắc cũng là một nỗ lực để khắc phục các cảnh báo trước đó, nhưng nó cảm thấy hơi khó xử khi sử dụng vì nó sử dụng kiểu mong đợi, 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ố, nhưng nếu bạn làm theo Behavior Driven Development (BDD) hoặc Sắp xếp luật Assert (AAA) nguyên tắc ExpectedException quy tắc sẽ không phù hợp với phong cách viết đó. Bên cạnh đó nó có thể bị cùng một vấn đề như là @Test cá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ệ được mong đợi cũng đượ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 theo BDD hoặc AAA.

    Xem thêm bình luận vấn đề về JUnit của tác giả ExpectedException.

Vì vậy, các tùy chọn ở trên có tất cả tải của họ về sự cẩn trọng, và rõ ràng không miễn dịch với các lỗi lập trình viên.

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

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

    Một ví dụ nhanh đượ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ể, then API là một 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-Assert tổ tiên của AssertJ. CHỈNH SỬA: Dường như dự án đang sản xuất một 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, điều đáng lưu ý là thư viện này dựa trên Mockito 1.x vì nó tạo ra một mô hình của đối tượng thử nghiệm đằng sau hiện trường. Vì Mockito vẫn chưa được cập nhật thư viện này không thể làm việc 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ó được dựa trên mockito 2 trong phiên bản hiện tại, điều này sẽ yêu cầu tuyên bố một nhà sản xuất giả lập toàn cầu (inline-mock-maker), cái gì đó có thể không phải những gì bạn muốn, như mockmaker này có những hạn chế khác nhau mà các mockmaker thường xuyên.

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

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

    Cân nhắc tất cả nếu bạn không muốn sử dụng công cụ bắt ngoại lệ, tôi sẽ đề xuất cách tốt cũ của try- -catch khối, ít nhất là lên đến JDK7. Và đối với người dùng JDK 8, bạn có thể thích sử dụng AssertJ hơn 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à họ đã 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 một API thông thạo tốt đẹp để khẳng định hành vi đặc biệt.

    Và một thử nghiệm 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 hoàn toàn gần JUnit 5, các xác nhận đã được được cải tiến một chút, họ có thể chứng minh thú vị như là một trong những cách hộp để khẳng định ngoại lệ đúng. Nhưng thực sự API khẳng định vẫn còn hơi nghèo nàn, 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());
    }
    

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

    Ngoài ra nếu bạn nhớ tên xung đột Matcher hoặc là Assert, hãy chuẩn bị để đáp ứng cùng một cuộc đụng độ với Assertions.

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

Câu trả lời này đã được sao chép 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ả.


58
2017-12-07 14:19



Thêm org.junit.jupiter: junit-jupiter-engine: phụ thuộc 5.0.0-RC2 (ngoài junit đã có: junit: 4.12) để có thể sử dụng assertThrows có lẽ không phải là giải pháp ưu tiên, nhưng không gây ra bất kỳ vấn đề cho tôi. - anre
Tôi là một fan hâm mộ của việc sử dụng các quy tắc ExpectedException nhưng nó luôn luôn làm phiền tôi rằng nó phá vỡ với AAA. Bạn đã viết một bài viết tuyệt vời để mô tả tất cả các phương pháp 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ú. Tốt hơn là tôi nghĩ rằng những gì JUnit đề xuất ra khỏi hộp. - Brice


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

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

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

}

Mã nguồn

Phụ thuộc

eu.codearte.catch-exception:catch-exception:1.3.3

31
2017-11-15 19:17