java - 확인 - throw exception test




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

어떻게 JUnit4를 관용적으로 사용하여 일부 코드가 예외를 throw하는지 테스트 할 수 있습니까?

나는 확실히 이런 일을 할 수 있지만 :

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

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

  assertTrue(thrown);
}

주석이나 Assert.xyz가 있거나 이러한 종류의 상황에 대해 JUnit의 훨씬 덜 불쾌하고 정신이 훨씬 더 깊은 것을 기억합니다.


Java 8 솔루션

다음과 같은 솔루션을 원한다면 :

  • Java 8 lambdas 활용
  • JUnit 마법에 의존 하지 않습니다.
  • 단일 테스트 방법 내에서 여러 예외를 확인할 수 있습니다.
  • 전체 테스트 메소드에서 알 수없는 라인 대신 테스트 메소드 내의 특정 라인 세트에 의해 예외가 던져 지는지 점검합니다.
  • 더 이상 검사 할 수 있도록 throw 된 실제 예외 객체를 나타냅니다.

다음은 내가 작성한 유틸리티 함수입니다.

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

( 내 블로그에서 가져온 )

다음과 같이 사용하십시오.

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

JUnit 5 솔루션

@Test
void testFooThrowsIndexOutOfBoundsException() {    
  Throwable exception = expectThrows( IndexOutOfBoundsException.class, foo::doStuff );

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

JUnit 5에 대한 자세한 정보는 improved


IMHO, JUnit에서 예외를 확인하는 가장 좋은 방법은 try / catch / fail / assert 패턴입니다.

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

assertTrue 는 일부 사람들에게는 약간 assertTrue 수 있으므로 assertThat(e.getMessage(), containsString("the message"); 이 바람직 할 수 있습니다.


JUnit 4는 다음을 지원합니다.

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

참조 : https://junit.org/junit4/faq.html#atests_7


JUnit과 함께 사용할 수있는 AssertJ 정문 사용 :

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

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

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

그것은 테스트에서 예상되는 라인이 예외를 던 @Test(expected=IndexOutOfBoundsException.class) 을 보장하고 message와 같은 예외에 대한 더 자세한 정보를 확인할 수 있기 때문에 @Test(expected=IndexOutOfBoundsException.class) 보다 좋습니다.

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

Maven / Gradle 지침은 여기에 있습니다.



같은 문제를 해결하기 위해 작은 프로젝트를 설정했습니다. http://code.google.com/p/catch-exception/

이 작은 조력자를 쓰면 쓸 것입니다.

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

이것은 JUnit 4.7의 ExpectedException 규칙보다 간략합니다. skaffman에서 제공하는 솔루션과 비교하여 예외를 예상하는 코드 행을 지정할 수 있습니다. 이게 도움이 되길 바란다.


다음과 같이 할 수도 있습니다.

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

여기에서 많은 방법을 시도했지만 복잡했거나 제 요구 사항을 제대로 충족하지 못했습니다. 사실, 도우미 메서드를 매우 간단하게 작성할 수 있습니다.

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

다음과 같이 사용하십시오.

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

0 종족 : mockito 필요 없음, powermock 필요 없음; 최종 수업에서 잘 작동합니다.


예상 된 예외를 사용하면 테스트에서 특정 코드 행이 아닌 해당 예외가 발생했다고 주장하기 때문에 조심하십시오.

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

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

판단을 적용하십시오.


Junky 4에 대한 가장 유연하고 우아한 대답은 Mkyong 블로그 에서 발견되었습니다. @Rule 어노테이션을 사용하여 try/catch 의 유연성을 제공합니다. 사용자 정의 된 예외의 특정 속성을 읽을 수 있기 때문에이 방법이 마음에 들다.

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

    }

}

tl; dr

  • pre-JDK8 : 나는 오래된 좋은 try - catch 블록을 추천 할 것이다.

  • post-JDK8 : AssertJ 또는 사용자 정의 람다를 사용하여 예외적 인 동작을 어설 션하십시오.

Junit 4 또는 JUnit 5에 관계없이.

긴 이야기

catch 블록을 사용하거나 JUnit 도구 ( @Test(expected = ...) 또는 @Rule ExpectedException JUnit 규칙 기능)를 직접 try .

그러나이 방법은 너무 우아하지 않고 다른 도구와 함께 가독성을 현명하게 혼합하지 않습니다.

  1. try - catch 블록은 테스트 된 동작 주위에 블록을 작성하고 catch 블록에 어설 션을 작성해야합니다. 그러나이 스타일은 테스트의 읽기 흐름을 방해합니다. 또한 try 블록의 끝에 Assert.fail 을 작성해야합니다. 그렇지 않으면 테스트에서 어설 션의 한 쪽을 놓칠 수 있습니다. 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 ( Arrange Act Assert ) 원칙을 따르는 경우 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를 따를 경우 읽기 흐름이 중단됩니다.

    또한 ExpectedException 의 저자 인 JUnit에 대한이 comment 문제를 참조하십시오.

따라서 위의 옵션은 모두주의해야 할로드가 있으며 코더 오류에 영향을받지 않습니다.

  1. 유망 해 보이는이 대답을 만든 후에 내가 알게 된 프로젝트가 있는데, 그것은 catch-exception 입니다.

    프로젝트에 대한 설명에서 말하듯이 코더는 유창한 코드 행을 작성하여 예외를 포착하고 나중에이 주장에 대한 예외를 제공 할 수 있습니다. 그리고 여러분은 HamcrestAssertJ 와 같은 어설 션 라이브러리를 사용할 수 있습니다.

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

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

    코드가 정말 간단하다는 것을 알 수 있듯이 특정 라인에서 예외를 잡으면 API는 assertThat(ex).hasNoCause()... 를 사용하는 것과 비슷한 AssertJ API를 사용할 별칭입니다. 어떤 시점에서 프로젝트는 FEST에 의존했습니다 . AssertJ의 조상을 주장합니다 . 편집 : 그것은 프로젝트가 자바 8 람다 지원을 양조하는 것으로 보인다.

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

    • 이 글을 쓰는 시점에서이 라이브러리가 Mockito 1.x를 기반으로한다는 것은 주목할만한 사실입니다. Mockito는 여전히 업데이트되지 않으므로이 라이브러리는 최종 클래스 또는 최종 메서드에서 작동하지 않습니다 . 현재 버전의 mockito 2를 기반으로 한 경우에도 글로벌 mock maker ( inline-mock-maker 모크 inline-mock-maker )를 선언해야합니다.이 mockmaker에는 일반적인 mockmaker와는 다른 단점이 있기 때문에 원하는 것은 아닐 수도 있습니다.

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

    라이브러리가 람다를 지원하면 이러한 문제는 적용되지 않지만 기능은 AssertJ 도구 세트로 복제됩니다.

    catch-exception 도구를 사용하고 싶지 않다면 모든 것을 고려하여 적어도 JDK7까지는 try - catch 블록의 오래된 좋은 방법을 추천 할 것이다. 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가 거의 완벽하게 재 작성되어 어설 션이 조금 improved 되었지만 예외적 인 예외를 어설 션하는 방법으로 흥미로울 수 있습니다. 하지만 실제로 assertion 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 또는 JDK)에 관계없이 JDK8에서 최상의 솔루션이라는 결론을 내리고 싶습니다. 이전의 JDK는 clunky라고해도 try - catch 블록에 의존해야합니다.

이 답변은 동일한 가시성이없는 다른 질문 에서 복사 한 것 입니다. 저는 같은 저자입니다.


편집 이제 JUnit5가 출시되었습니다. 가장 좋은 방법은 Assertions.assertThrows() 를 사용하는 것입니다 ( 다른 답변 참조).

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

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

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

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

foo.doStuff() 보다 먼저 IndexOutOfBoundsException 이 발생하면 테스트가 실패하기 때문에 @Test(expected=IndexOutOfBoundsException.class) 보다 훨씬 낫습니다.

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


JUnit 4 이상에서는 다음과 같이 예외를 테스트 할 수 있습니다.

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


JUnit 테스트를 개선하는 데 사용할 수있는 많은 기능을 제공합니다.
아래 예제를 보면 예외적으로 세 가지를 테스트하고 있습니다.

  1. Throw 된 예외의 형태
  2. 예외 메시지
  3. 예외의 원인


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

}

Java8을 사용하는 Junit4 솔루션은이 기능을 사용합니다.

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

사용법은 다음과 같습니다.

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

유일한 제한은 final람다 식에서 객체 참조 를 사용하는 것 입니다. 이 솔루션은 솔루션을 사용하여 메서드 수준에서 테스트 할 수있는 대신 테스트 어설 션을 계속할 수 있습니다 @Test(expected = IndexOutOfBoundsException.class).


가 말한 것에 을 확인하십시오 :

  • ExpectedException 인스턴스가 public ( 관련 질문 )
  • ExpectedException @Before 메서드에서 인스턴스화 되지 않습니다 . 이 post 은 JUnit의 실행 명령의 모든 복잡함을 명확하게 설명합니다.

이것을 하지 마십시오 :

@Rule    
public ExpectedException expectedException;

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

마지막 this 블로그 게시물은 특정 예외가 발생했다고 주장하는 방법을 명확하게 보여줍니다.


예를 들어, 아래에 언급 된 코드 조각에 대해 Junit을 작성하려고합니다.

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

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

위 코드는 발생할 수있는 알 수없는 예외를 테스트하는 것이며, 아래 코드는 사용자 지정 메시지에 대해 일부 예외를 선언하는 것입니다.

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

예외를 리턴해야하는 메소드 다음에 어설 션 실패를 사용할 수 있습니다.

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

테스트 케이스 작성에는 두 가지 방법이 있습니다.

  1. 메소드에 의해 던져지는 예외로 주석을 달아라. 이 같은@Test(expected = IndexOutOfBoundsException.class)
  2. try catch 블록을 사용하여 테스트 클래스에서 예외를 catch하고 테스트 클래스의 메서드에서 throw 된 메시지에 대해 단정 할 수 있습니다.

    try{
    }
    catch(exception to be thrown from method e)
    {
         assertEquals("message", e.getmessage());
    }
    

이 질문에 행복하게 답변 해 주시기 바랍니다.


assertj-corejunit 테스트에서 예외 처리 라이브러리 권장

자바 8, 이런 :

//given

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

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




assert