[java] JUnit 4 테스트에서 특정 예외가 발생했다고 어떻게 주장합니까?

JUnit4를 관용적으로 사용하여 일부 코드에서 예외가 발생하는지 테스트하려면 어떻게해야합니까?

나는 확실히 이와 같은 것을 할 수 있지만 :

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

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

  assertTrue(thrown);
}

이러한 종류의 상황에 대해 주석이나 Assert.xyz 또는 JUnit의 훨씬 덜 혼란스럽고 훨씬 더 정신적 인 것이 있다는 것을 기억합니다 .



답변

JUnit 버전과 사용하는 어설 션 라이브러리에 따라 다릅니다.

원래 답변 JUnit <= 4.12은 다음과 같습니다.

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

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

}

답변 https://stackoverflow.com/a/31826781/2986984 에는 JUnit <= 4.12에 대한 추가 옵션이 있습니다.

참고 :


답변

편집 : 이제 JUnit 5 및 JUnit 4.13이 릴리스되었으므로 가장 좋은 옵션은 Assertions.assertThrows() (JUnit 5의 경우) 및 Assert.assertThrows()(JUnit 4.13의 경우)를 사용하는 것입니다. 자세한 내용은 다른 답변 을 참조하십시오.

JUnit 5로 마이그레이션하지 않았지만 JUnit 4.7을 사용할 수있는 경우 ExpectedException규칙을 사용할 수 있습니다 .

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

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

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

전에 던져 @Test(expected=IndexOutOfBoundsException.class)지면 테스트가 실패하기 때문에 이것은 훨씬 낫습니다.IndexOutOfBoundsExceptionfoo.doStuff()

자세한 내용은 이 기사 를 참조하십시오


답변

메소드 가 테스트에서 특정 코드 줄이 아니라 해당 예외를 발생 시켰다고 주장하기 때문에 예상되는 예외를 사용하는 데주의하십시오 .

이러한 방법은 일반적으로 매우 간단하지만 더 복잡한 테스트가 더 잘 제공 될 수 있기 때문에 매개 변수 유효성 검사 테스트에 사용하는 경향이 있습니다.

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

판단을 적용하십시오.


답변

이전에 대답했듯이 JUnit에는 예외를 처리하는 여러 가지 방법이 있습니다. 그러나 Java 8에는 Lambda Expressions 사용이 있습니다. Lambda Expressions를 사용하면 다음과 같은 구문을 얻을 수 있습니다.

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

assertThrown은 람다 식, 메서드 참조 또는 생성자 참조로 인스턴스를 만들 수있는 기능적 인터페이스를 허용합니다. 해당 인터페이스를 승인하면 assertThrown이 예외를 처리하고 처리 할 준비가됩니다.

이것은 비교적 간단하지만 강력한 기술입니다.

이 기술을 설명하는이 블로그 게시물을 살펴보십시오. http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

소스 코드는 https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8 에서 찾을 수 있습니다.

공개 : 저는 블로그와 프로젝트의 저자입니다.


답변

junit에는 예외를 테스트하는 네 가지 방법이 있습니다.

junit5.x

  • junit5.x의 경우 assertThrows다음과 같이 사용할 수 있습니다

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

junit4.x

  • junit4.x의 경우, 선택 테스트 테스트의 ‘예상’속성을 사용하십시오.

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
  • junit4.x의 경우 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();
        }
    }
  • junit 3 프레임 워크에서 널리 사용되는 고전적인 try / catch 방식을 사용할 수도 있습니다

    @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.
        }
    }
  • 그래서

    • junit 5를 좋아한다면 첫 번째 것을 좋아해야합니다
    • 두 번째 방법은 예외 유형 만 테스트하려는 경우에 사용됩니다.
    • 첫 번째와 마지막 두 개는 테스트 예외 메시지를 추가로 원할 때 사용됩니다.
    • junit 3을 사용하면 네 번째 것이 선호됩니다.
  • 자세한 내용은 이 문서 와 자세한 내용은 junit5 사용 설명서 를 참조하십시오.


답변

tl; dr

  • post-JDK8 : AssertJ 또는 커스텀 람다를 사용 하여 뛰어난 동작을 확인하십시오.

  • pre-JDK8 : 오래된 good trycatchblock을 추천합니다 . ( 블록 앞에 어설 션 을 추가하는 것을 잊지 마십시오fail()catch )

Junit 4 또는 JUnit 5에 상관없이

긴 이야기

자신을 작성할 수 있습니다 그것을 스스로 할 trycatch블록 또는 JUnit을 도구 (사용 @Test(expected = ...)또는 @Rule ExpectedExceptionJUnit을 규칙 기능).

그러나 이러한 방법은 그다지 우아하지 않으며 다른 도구와 잘 가독성 이 좋지 않습니다 . 또한 JUnit 툴링에는 몇 가지 함정이 있습니다.

  1. trycatch당신이 테스트 문제를 해결 블록을 작성하고 catch 블록의 주장을 작성해야 블록, 즉 미세하지만 많은 발견 할 수있다이 스타일은 인터럽트 테스트의 읽기 흐름을 그. 또한, 당신은 쓸 필요 Assert.fail의 끝 부분에 try블록. 그렇지 않으면 테스트에서 어설 션의 한쪽이 누락 될 수 있습니다. PMD , findbugs 또는 Sonar 는 이러한 문제를 발견합니다.

  2. @Test(expected = ...)적은 코드를 작성하고이 테스트를 작성하는 것은 오류를 코딩 가정으로 적은 경향 수있는 기능은 흥미 롭다. 그러나 일부 지역에서는 이러한 접근 방식이 부족합니다.

    • 테스트에서 원인 또는 메시지와 같은 예외에 대한 추가 사항을 확인해야하는 경우 (예외 메시지가 중요하면 정확한 예외 유형이 충분하지 않을 수 있음)
    • 또한 테스트 코드 작성 방법에 따라 테스트 코드의 잘못된 부분에서 예외가 발생하여 거짓 양성 테스트가 발생할 수 있으며 PMD , findbugs 또는 Sonar 확신하지 못합니다 그러한 코드에 대한 힌트를 줄 것입니다.

      @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. ExpectedException규칙은 또한 이전 경고를 해결하려는 시도이지만, 예상 스타일을 사용하기 때문에 사용하기가 약간 어색하다고 생각합니다. EasyMock 사용자는이 스타일을 매우 잘 알고 있습니다. 일부에게는 편리 할 수 ​​있지만 BDD ( behavior Driven Development ) 또는 AAA ( Action Actassert ) 원칙을 따르는 경우 ExpectedException규칙이 해당 작성 스타일에 맞지 않습니다. 그 외에도 @Test예상 위치에 따라 방법 과 동일한 문제가 발생할 수 있습니다 .

    @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
    }

    예상되는 예외도 테스트 문 앞에 놓여지며 테스트가 BDD 또는 AAA를 따르는 경우 읽기 흐름이 중단됩니다.

    또한 의 작성자 JUnit에 대한 이 주석 문제를 참조하십시오 ExpectedException. JUnit 4.13-beta-2 는이 메커니즘을 더 이상 사용하지 않습니다.

    풀 요청 # 1519 : Deprecate ExpectedException

    Assert.assertThrows 메소드는 예외를 확인하는 더 좋은 방법을 제공합니다. 또한 규칙의 순서가 중요하기 때문에 TestWatcher와 같은 다른 규칙과 함께 사용하면 ExpectedException을 사용하는 것이 오류가 발생하기 쉽습니다.

따라서 위의 옵션에는 모든주의 사항이 있으며 코더 오류에 영향을 미치지 않습니다.

  1. 유망한 것으로 보이는이 답변을 만든 후에 내가 알게 된 프로젝트가 있습니다. 그것은 예외 입니다.

    프로젝트에 대한 설명에서 알 수 있듯이 코더는 유창한 코드 줄을 작성하여 예외를 포착하고 후자의 주장에 대해이 예외를 제공 할 수 있습니다. 그리고 Hamcrest 또는 AssertJ 와 같은 어설 션 라이브러리를 사용할 수 있습니다 .

    홈페이지에서 가져온 빠른 예 :

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

    코드가 정말 간단 then하다는 것을 알 수 있듯이 특정 라인에서 예외를 포착하면 API는 AssertJ API를 사용하는 별칭입니다 (과 유사 assertThat(ex).hasNoCause()...). 어느 시점에서 프로젝트는 AssertJ의 조상 FEST-Assert에 의존했습니다 . 편집 : 프로젝트가 Java 8 Lambdas 지원을 양조하고있는 것 같습니다.

    현재이 라이브러리에는 두 가지 단점이 있습니다.

    • 이 글을 쓰는 시점에서이 라이브러리는 Mockito 1.x를 기반으로하며, 테스트 된 객체 뒤에 장면을 모방 한 것입니다. Mockito가 여전히 업데이트되지 않았으므로이 라이브러리는 최종 클래스 또는 최종 메소드와 함께 작동 할 수 없습니다 . 그리고 현재 버전의 Mockito 2를 기반으로 했더라도 글로벌 모의 메이커 ( inline-mock-maker) 를 선언해야합니다. 이 모의 메이커는 일반 모의 메이커와는 다른 단점이 있기 때문에 원치 않을 수도 있습니다.

    • 또 다른 테스트 종속성이 필요합니다.

    라이브러리가 람다를 지원하면 이러한 문제는 적용되지 않습니다. 그러나이 기능은 AssertJ 툴셋에 의해 복제됩니다.

    catch-exception 도구를 사용하지 않으려면 모든 것을 고려 하여 적어도 JDK7까지 trycatch블록 의 오래된 좋은 방법을 권장합니다 . JDK 8 사용자의 경우 AssertJ를 사용하는 것이 좋습니다. 예외를 주장하는 것 이상을 제공 할 수도 있습니다.

  2. JDK8을 사용하면 람다는 테스트 현장에 들어가고 뛰어난 행동을 나타내는 흥미로운 방법으로 판명되었습니다. AssertJ는 유창한 유창한 API를 제공하여 예외적 인 행동을하도록 업데이트되었습니다.

    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. JUnit 5를 거의 완전히 다시 작성하면 어설 션이 약간 개선 되어 제대로 예외를 주장하는 즉시 사용 가능한 방법으로 흥미로울 수 있습니다. 그러나 실제로 어설 션 API는 여전히 약간 나쁘지만 외부에는 아무것도 없습니다 assertThrows.

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

    알다시피 assertEquals여전히을 반환 void하고 AssertJ와 같은 체인 어설 션을 허용하지 않습니다.

    당신이 이름 충돌을 기억한다면 또한 Matcher또는 Assert와 같은 충돌을 만날 준비 Assertions.

오늘 (2017-03-03) AssertJ 의 사용 편의성, 검색 가능한 API, 빠른 개발 속도 및 사실상 테스트 종속성은 테스트 프레임 워크 (JUnit)에 관계없이 JDK8을 사용하는 가장 좋은 솔루션이라고 결론 내리고 싶습니다 여부), 이전의 JDK 대신에 의존한다 trycatch 그들이 투박한 느낌이 경우에도 블록.

이 답변은 동일한 가시성을 가지고 있지 않은 다른 질문 에서 복사되었습니다 . 저는 같은 저자입니다.


답변

JUnit 5 및 JUnit 4.13이 릴리스되었으므로 가장 좋은 옵션은 Assertions.assertThrows() (JUnit 5의 경우) 및 Assert.assertThrows()(JUnit 4.13의 경우)를 사용하는 것입니다. Junit 5 사용자 안내서를 참조하십시오 .

다음은 예외가 발생했는지 확인하고 Truth 를 사용 하여 예외 메시지에 대한 어설 션을 만드는 예입니다 .

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

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

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

다른 답변의 접근법보다 장점은 다음과 같습니다.

  1. JUnit에 내장
  2. 람다의 코드에서 예외가 발생하지 않으면 유용한 예외 메시지가 표시되고 다른 예외가 발생하면 스택 추적이 표시됩니다.
  3. 간결한
  4. 테스트가 Arrange-Act-Assert를 따르도록 허용
  5. 예외를 던질 것으로 예상되는 코드를 정확하게 나타낼 수 있습니다
  6. throws절에 예상되는 예외를 나열 할 필요는 없습니다.
  7. 선택한 어설 션 프레임 워크를 사용하여 포착 된 예외에 대한 어설 션을 만들 수 있습니다.

org.junit AssertJUnit 4.13 에도 비슷한 방법이 추가 될 것 입니다.