예외가 발생하지 않는지 테스트하는 방법은 무엇입니까?


238

한 가지 방법은 다음과 같습니다.

@Test
public void foo(){
   try{
      //execute code that you expect not to throw Exceptions.
   }
   catch(Exception e){
      fail("Should not have thrown any exception");
   }
}

이 작업을 수행하는 더 깨끗한 방법이 있습니까? (아마 Junit의 @Rule?를 사용하는 것 )


10
JUnit 테스트는 예상 예외 이외의 예외가 발생하면 실패한 것으로 판단됩니다. 일반적으로 예외는 없습니다.
Raedwald

JUnit에서 실패와 오류가 구별되지 않습니까? 첫 번째는 테스트가 실패했음을 의미하고 두 번째는 예기치 않은 문제가 발생했음을 의미합니다.
Vituel

답변:


198

당신은 이것에 잘못 접근하고 있습니다. 기능 만 테스트하십시오. 예외가 발생하면 테스트가 자동으로 실패합니다. 예외가 발생하지 않으면 테스트가 모두 녹색으로 바뀝니다.

나는이 질문이 때때로 관심을 끌고 있음을 알았으므로 조금 확장 할 것이다.

단위 테스트 배경

단위 테스트를 수행 할 때는 작업 단위로 간주되는 사항을 스스로 정의해야합니다. 기본적으로 : 단일 기능을 나타내는 여러 메소드 또는 클래스를 포함하거나 포함하지 않을 수있는 코드베이스 추출.

또는 11 페이지 Roy Osherove의 단위 테스트 기술 제 2 판에 정의 된대로 :

유닛 테스트 후 그 검사 장치의 하나의 최종 결과에 대해 어떤 가정 작업 단위가 테스트중인 호출 한 코드의 자동화 부분이다. 단위 테스트는 거의 항상 단위 테스트 프레임 워크를 사용하여 작성됩니다. 쉽게 작성하고 빠르게 실행할 수 있습니다. 신뢰할 수 있고 읽기 쉽고 유지 관리가 가능합니다. 프로덕션 코드가 변경되지 않는 한 결과가 일관성이 있습니다.

알아야 할 중요한 점은 한 작업 단위가 일반적으로 하나의 방법이 아니라 가장 기본적인 수준에서 하나의 방법이며 그 후에 다른 작업 단위로 캡슐화된다는 것입니다.

여기에 이미지 설명을 입력하십시오

이상적으로는 각각의 개별 작업 단위에 대한 테스트 방법이 있어야 상황이 잘못되는 경우를 즉시 볼 수 있습니다. 이 예제에는 기본 메소드가 있습니다.getUserById() 사용자를 리턴 가 있으며 총 3 개의 작업 단위가 있습니다.

첫 번째 작업 단위는 유효하고 유효하지 않은 입력의 경우 유효 사용자가 리턴되는지 여부를 테스트해야합니다.
데이터 소스에 의해 발생되는 예외는 여기에서 처리해야합니다. 사용자가 없으면 사용자를 찾을 수 없을 때 예외가 발생했음을 보여주는 테스트가 있어야합니다. 이것의 샘플은 주석 IllegalArgumentException과 함께 잡힐 수 있습니다 @Test(expected = IllegalArgumentException.class).

이 기본 작업 단위에 대한 모든 사용 사례를 처리 한 후에는 레벨을 올립니다. 여기서는 정확히 동일하지만 현재 예외 바로 아래 수준에서 발생하는 예외 만 처리합니다. 이를 통해 테스트 코드를 체계적으로 구성 할 수 있으며 아키텍처 전체를 신속하게 실행하여 문제가 발생한 곳을 찾을 수 있습니다.

테스트의 유효하고 잘못된 입력 처리

이 시점에서 이러한 예외를 어떻게 처리 할 것인지 분명해야합니다. 입력에는 두 가지 유형이 있습니다 : 유효한 입력과 결함 입력 (입력은 엄격한 의미에서는 유효하지만 올바르지 않습니다).

유효한 작업을 할 때 입력으로 작성하는 모든 테스트가 작동 할 암시 적 기대치를 설정합니다.

이러한 메소드 호출은 다음과 같습니다. existingUserById_ShouldReturn_UserObject . 이 방법이 실패하면 (예 : 예외가 발생) 무언가 잘못되었다는 것을 알고 파기를 시작할 수 있습니다.

잘못된 입력 nonExistingUserById_ShouldThrow_IllegalArgumentException을 사용하고 예외를 예상하는 다른 테스트 ( )를 추가 하면 메소드가 잘못된 입력으로 수행해야하는 작업을 수행하는지 확인할 수 있습니다.

TL; DR

테스트에서 두 가지 작업을 수행하려고했습니다. 입력이 유효한지 확인하십시오. 이것을 각각 한 가지 작업을 수행하는 두 가지 방법으로 나누면 훨씬 더 명확한 테스트와 문제가 발생한 위치에 대한 더 나은 개요를 얻을 수 있습니다.

계층화 된 작업 단위를 염두에두면 하위 계층에서 잘못되었을 수있는 모든 항목을 설명 할 필요가 없으므로 계층 구조에서 상위 계층에 필요한 테스트 양을 줄일 수도 있습니다. 현재 계층 아래의 계층은 종속성이 작동한다는 사실을 보증하며 무언가 잘못되면 현재 계층에 있습니다 (하위 계층이 자체적으로 오류를 발생시키지 않는다고 가정).


2
문제는 TDD를 시도하고 있으며 사용하는 공동 작업자 중 하나가 예외를 던지고 있다는 것입니다. 따라서 공동 작업자가 던진 예외를 사용하고 있다는 사실을 테스트해야합니다.
Ankit Dhingra

6
귀하의 기능이 예외 처리에 달려 있다고 말하는가? 코드 냄새입니다. 예외적으로 우아하게 문제를 포착 할 수 있습니다. 흐름 제어에는 사용되지 않습니다. 예외가 발생해야하는 시나리오를 테스트하려면 expected주석을 사용해야합니다 . 코드가 실패하는 시나리오를 테스트하고 오류가 올바르게 처리 expected되는지 확인하려면 assert를 사용하여 문제가 해결되었는지 확인하십시오.
Jeroen Vannevel

문제는 공동 작업자에서 발생한 예외에서 복구 할 수 없으며 log.debug ( "Error message")를 사용하여 문제를 기록하기 만하면됩니다. 따라서 내가 주장 할 수있는 catch 블록의 일부로 발생하는 부작용은 없습니다.
Ankit Dhingra

5
@JeroenVannevel 예외를 발생시키는 오류 상황이 올바르게 처리되는지 테스트하는 것은 완벽하게 유효합니다.
Thorbjørn Ravn Andersen

1
@dpk 예 가능합니다. throws IllegalArgumentException테스트에 추가 합니다. 결국 원하는 것은 예외가 있으면 테스트가 빨간색으로 바뀌는 것입니다. 뭐라고 생각해? 쓸 필요가 없습니다 fail(). @Jeroen Vannevel은 다음과 같이 썼습니다. "예외가 발생하면 테스트는 자동으로 실패합니다."
Amedee Van Gasse

132

SonarQube의 규칙 "squid : S2699"로 인해이 문제가 발생했습니다. "이 테스트 사례에 하나 이상의 어설 션을 추가하십시오."

나는 예외를 던지지 않고 통과하는 유일한 목표를 가진 간단한 테스트를 받았다.

이 간단한 코드를 고려하십시오.

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}

이 방법을 테스트하기 위해 어떤 종류의 어설 션을 추가 할 수 있습니까? 물론, 당신은 그 주위에 try-catch를 만들 수 있지만 그것은 코드 팽창 일뿐입니다.

솔루션은 JUnit 자체에서 제공됩니다.

예외가 발생하지 않고이 동작을 명시 적으로 설명 expected하려면 다음 예제와 같이 추가하십시오 .

@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
    Printer.printLine("line");
}

Test.None.class 예상 값의 기본값입니다.


30
이것이 가장 좋은 대답이라고 생각합니다. 대답은 훌륭하고 저자는 코드 냄새를 지적하는 것이 옳습니다. 그러나 그는 특정 질문에 실제로 대답하지 않았습니다.
HellishHeat

4
예상되는 기본값은 None이므로 @Test로 메소드에 주석을 달기 만하면됩니다.
oziomajnr


41

JUnit 5 (Jupiter)는 예외 유무를 확인하는 세 가지 기능을 제공합니다.

assertAll​()

주장 하는 것이 모든 공급 executables
  예외를 발생하지 않습니다.

assertDoesNotThrow​()

주장 의 실행이
  공급을 executable/ supplier
throw하지 않습니다 모든 종류의 예외를 .

  이 기능은 JUnit 5.2.0 (2018 년 4 월 29 일)
  부터 사용할 수 있습니다 .

assertThrows​()

주장 제공된의 실행이 executable
발생 의 예외 expectedType
  및 반환 예외를 .

package test.mycompany.myapp.mymodule;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class MyClassTest {

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw() {
        String myString = "this string has been constructed";
        assertAll(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() {
        String myString = "this string has been constructed";
        assertDoesNotThrow(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_is_null_then_myFunction_throws_IllegalArgumentException() {
        String myString = null;
        assertThrows(
            IllegalArgumentException.class,
            () -> MyClass.myFunction(myString));
    }

}

1
이것이 가장 좋은 대답입니다. 다른 답변은 이전 버전의 JUnit
Tejesh Raut

29

Java 8을 사용하면이 작업이 훨씬 쉬워지고 Kotlin / Scala는 두 배가됩니다.

우리는 작은 유틸리티 클래스를 작성할 수 있습니다

class MyAssertions{
  public static void assertDoesNotThrow(FailingRunnable action){
    try{
      action.run()
    }
    catch(Exception ex){
      throw new Error("expected action not to throw, but it did!", ex)
    }
  }
}

@FunctionalInterface interface FailingRunnable { void run() throws Exception }

그러면 코드가 간단 해집니다.

@Test
public void foo(){
  MyAssertions.assertDoesNotThrow(() -> {
    //execute code that you expect not to throw Exceptions.
  }
}

Java-8에 액세스 할 수 없다면 고통스럽게 오래된 Java 기능을 사용합니다. 임의 코드 블록 및 간단한 주석

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}

그리고 마지막으로, 최근에 사랑에 빠진 언어 인 kotlin과 함께 :

fun (() -> Any?).shouldNotThrow() 
    = try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) }

@Test fun `when foo happens should not throw`(){

  //...

  { /*code that shouldn't throw*/ }.shouldNotThrow()
}

이것을 어떻게 표현하고 싶은지를 정확하게 피할 수있는 여지가 많지만, 나는 항상 유창한 주장 의 팬이었다 .


에 관해서

당신은 이것에 잘못 접근하고 있습니다. 기능 만 테스트하십시오. 예외가 발생하면 테스트가 자동으로 실패합니다. 예외가 발생하지 않으면 테스트가 모두 녹색으로 바뀝니다.

이것은 원칙적으로는 정확하지만 결론에는 맞지 않습니다.

Java는 제어 흐름에 대한 예외를 허용합니다. 이는 Double.parseDoublea NumberFormatException및 a 를 Paths.get통한 API와 같은 API에서 JRE 런타임 자체에 의해 수행됩니다 .InvalidPathException .

Number 문자열의 유효성을 검사하는 구성 요소를 Double.ParseDouble작성했거나 Regex를 사용하거나 손으로 쓴 파서를 사용하거나 double 범위를 특정 항목으로 제한하는 다른 도메인 규칙을 포함하는 항목을 작성했다면이 를 테스트하는 가장 좋은 방법 구성 요소? 결과 문자열이 구문 분석 될 때 예외가 발생하지 않는다고 주장하는 확실한 테스트가 될 것이라고 생각합니다. 위 assertDoesNotThrow또는 /*comment*/{code}블록을 사용하여 해당 테스트를 작성합니다 . 같은 것

@Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){
  //setup
  String input = "12.34E+26" //a string double with domain significance

  //act
  boolean isValid = component.validate(input)

  //assert -- using the library 'assertJ', my personal favourite 
  assertThat(isValid).describedAs(input + " was considered valid by component").isTrue();
  assertDoesNotThrow(() -> Double.parseDouble(input));
}

또한이 테스트를 input사용하여 Theories또는 Parameterized다른 입력에이 테스트를 더 쉽게 재사용 할 수 있도록 매개 변수화하는 것이 좋습니다 . 또는 이국적으로 가고 싶다면 테스트 생성 도구 를 사용 하십시오. )를 사용할 수 있습니다. TestNG는 매개 변수화 된 테스트를 더 잘 지원합니다.

내가 특히 동의하지 않는 것은 사용을 권장하는 것입니다 @Test(expectedException=IllegalArgumentException.class). 이 예외는 광범위하게 위험 합니다. 테스트중인 구성 요소의 코드가 변경되어 테스트에서 if(constructorArgument <= 0) throw IllegalArgumentException()해당 인수에 대해 0을 제공하는 것이 편리했기 때문에 테스트가 잘 이루어지고 테스트 데이터를 생성하는 것이 놀랍도록 어려운 문제이므로 매우 일반적입니다. 아무것도 테스트하지 않아도 녹색 막대가됩니다. 그러한 시험은 쓸모없는 것보다 더 나쁩니다.


2
(예상 예외 사용과 관련하여) JUnit 4.13부터 Assert.assertThrows일부 코드에서 예외가 발생하는지 확인할 수 있습니다 .
MageWind

22

운이 좋지 않으면 코드의 모든 오류를 잡을 수 있습니다. 멍청하게 할 수있어

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThrowError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}

1
테스트하기 전에 작은 제안 만해 Exception ex= null;합니다.
Denees

4
이것은 훌륭한 해결책이 아닙니다. 예외를 발생시키지 않아야하는 메소드가 발생하면 유용한 오류 메시지가 표시되지 않습니다. 예외를 발생시키지 않아야하는 메소드를 호출하고 반환 값 (또는 예외 로깅과 같은 부작용)을 테스트하십시오. 나중에 예기치 않게 예외가 발생하면 테스트가 실패합니다.
NamshubWriter

3
또는 Assert.fail ()을 더 쉽고 더 예쁘게 잡을 수 있습니다.
isaac.hazan

그래 나도 너와 같은 생각이야. 또 다른 방법은 @Test 메소드 위에 주석을 추가하는 것입니다 (예상 = InvalidRequestException.class)
Ben Tennyson

잘못된 철자가 혼동됩니다 : thisShouldThroughError- > thisShouldThrowError .
Oscar Bravo


7

그러나이 게시물은 6 년이되었지만 Junit 세계에서 많은 변화가있었습니다. Junit5를 사용하면 이제

org.junit.jupiter.api.Assertions.assertDoesNotThrow()

전의:

public void thisMethodDoesNotThrowException(){
   System.out.println("Hello There");
}

@Test
public void test_thisMethodDoesNotThrowException(){
  org.junit.jupiter.api.Assertions.assertDoesNotThrow(
      ()-> thisMethodDoesNotThrowException()
    );
}

최신 버전의 Junit5를 사용하는 사람들에게 도움이되기를 바랍니다.


여기에 구체적인 예외 클래스를 지정하는 방법이 있었으면 좋겠습니다. 나는이 내부를해야 할 AwaitilityuntilAsserted(ThrowingRunnable assertion). 테스트중인 시스템이 현재 제공하는 ThrowingRunnable에 대해 특정 예외를 던지고 있지만 중단 될 때까지 약간의 시간을주고 싶습니다. 그러나 다른 예외가 발생하면 테스트가 즉시 실패하고 싶습니다.
Ubeogesh

1

테스트 대상이 예외를 소비하는지 여부를 테스트하려는 경우. 테스트를 그대로 두십시오 (jMock2를 사용하는 모의 공동 작업자).

@Test
public void consumesAndLogsExceptions() throws Exception {

    context.checking(new Expectations() {
        {
            oneOf(collaborator).doSth();
            will(throwException(new NullPointerException()));
        }
    });

    target.doSth();
 }

대상이 예외를 소비하면 테스트가 통과되고 그렇지 않으면 테스트가 실패합니다.

예외 소비 로직을 테스트하려는 경우 상황이 더 복잡해집니다. 모의 할 수있는 공동 작업자에게 소비를 위임하는 것이 좋습니다. 따라서 테스트는 다음과 같습니다.

@Test
public void consumesAndLogsExceptions() throws Exception {
    Exception e = new NullPointerException();
    context.checking(new Expectations() {
        {
            allowing(collaborator).doSth();
            will(throwException(e));

            oneOf(consumer).consume(e);
        }
    });

    target.doSth();
 }

그러나 때로는 로그를 남기고 싶다면 과도하게 설계되었습니다. 이 경우이 기사 ( http://java.dzone.com/articles/monitoring-declarative-transac , http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there 이 경우 tdd를 고집하면 -an-easy-way / )가 도움이 될 수 있습니다.


1

사용 assertNull (...)

@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}

6
나는 이것이 오도라고 말하고 싶습니다. 캐치 블록에 도달하지 않으므로이 블록도 assertNull실행되지 않습니다. 그러나 빠른 독자는 비 투사 사례를 실제로 검증하는 주장이 이루어 졌다는 인상을받습니다. 즉, catch 블록에 도달하면 예외는 항상 null이 아니므로 simple로 대체 할 수 있습니다 fail.
Andreas

실제로 오해의 소지가 .....하지만, 대기, ... 오 봐요 ... assertNull(e)실패로 언급 한 시험을보고 e하지 않을 수 nullcatch... 마이크이 그냥 이상한 프로그램입니다 블록 : - /. .. 예 fail()Andreas의 말처럼 적어도 사용
Julien

1

규칙을 작성하여 예외가 발생하지 않을 것으로 예상 할 수 있습니다.

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

ExpectedExceptions는 throw 된 예외를 주장하는 데 사용됩니다. 제공하는 코드는 규칙을 초기화하여 어설 션에 대한 요구 사항을 추가 할 수 있습니다. 이 코드 자체는 전혀 가치를 추가하지 않습니다. "/ ** * 예외가 발생하지 않을 것으로 예상되는 {@linkplain TestRule 규칙}을 반환합니다 (이 규칙이없는 동작과 동일). .
Pim Hazebroek

나는 당신에게 동의하고, 그런 식으로 사용하지 않을 것이지만, 예외가 발생하지 않았다고 주장하는 것이 가능합니다. 테스트가 통과되면 예외가 발생하지 않았다고 말할만큼 충분해야하지만, 질문이있는 경우에는 예외가 필요합니다. 그리고 드물지만 여전히 눈에 보이는 것이 좋습니다. 코드와 환경이 변경되어 특정 에지 사례에 대한 테스트가없는 경우 어떻게됩니까?
LazerBanana

예상 한 예외를 제외하고 어떻게 주장하는지 궁금합니다. 그리고 예, 요구 사항이 변경되고 특정 모서리 케이스에 대한 테스트가 없다면 나사로 고정됩니다. ;-) 항상 모든 코너 케이스를 덮습니다.
Pim Hazebroek

무슨 소리 야? 당신은 그것에 대해 주장하지 않습니다, 당신은 그것을 기대합니다. 이 경우 예외는 없습니다. 당신이 무엇에 대해 잘 모르겠습니다.
LazerBanana

0

이것이 최선의 방법은 아니지만 테스트중인 코드 블록에서 예외가 발생하지 않도록해야합니다.

import org.assertj.core.api.Assertions;
import org.junit.Test;

public class AssertionExample {

    @Test
    public void testNoException(){
        assertNoException();
    }    

    private void assertException(){
        Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class);
    }

    private void assertNoException(){
        Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class);
    }

    private void doNotThrowException(){
        //This method will never throw exception
    }
}

0

@Rule을 사용하여 다음과 같이 reportMissingExceptionWithMessage 메소드를 호출하여이를 수행 할 수 있습니다. 이것은 스칼라 코드입니다.

여기에 이미지 설명을 입력하십시오


1
private val? 이 언어는 무엇입니까? 분명히 Java가 아닙니다 .p 그리고 스크린 샷으로 코드를 제공하지 마십시오. 환영하지 않습니다.
Andremoniy

스칼라라고 언급했지만 Java 로 쉽게 할 수 있다고 말하는 것은 강력한 논쟁이 아닙니다. 죄송합니다.
Andremoniy

당신을 괴롭힌 부분을 제거했습니다. 나는 또한 이미지를 바꾸려고 노력할 것이다. 아직 코드를 추가하는 방법을
찾지

-1

다음은 모든 예외에 대한 테스트에 실패했는지 (확인되었거나 선택하지 않은)입니다.

@Test
public void testMyCode() {

    try {
        runMyTestCode();
    } catch (Throwable t) {
        throw new Error("fail!");
    }
}

-1

junit의 어설 션을 기반으로 모든 종류의 어설 션을 만들 수 있습니다.

static void assertDoesNotThrow(Executable executable) {
    assertDoesNotThrow(executable, "must not throw");
}
static void assertDoesNotThrow(Executable executable, String message) {
    try {
        executable.execute();
    } catch (Throwable err) {
        fail(message);
    }
}

그리고 테스트 :

//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "fail with specific message: facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "Fail: must not trow");

일반적으로 모든 시나리오에서 테스트가 의미있는 장소에서 즉시 테스트를 실패 ( "bla bla bla") 할 가능성이 있습니다. 예를 들어, 테스트 케이스에서 무언가가 발생하면 try / catch 블록에서이를 사용하여 실패하십시오.

try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
//or
try{methodMustNotThrow(1);}catch(Throwable e){Assertions.fail("must not throw");}

이것은 우리가 테스트하는 방법의 샘플이며 특정 상황에서 실패해서는 안되지만 실패 할 수있는 방법이 있다고 가정합니다.

void methodMustNotThrow(int x) throws Exception{
    if (x == 1) return;
    throw new Exception();
}

위의 방법은 간단한 샘플입니다. 그러나 이것은 실패가 분명하지 않은 복잡한 상황에서 작동합니다. 수입품이 있습니다 :

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.*;

커스텀 코드 생성과 관련이없는 어설 션이 발생하지 않았는지 확인하는 더 나은 옵션이 있습니다. @Rule은 그들 중 하나입니다
Vargan

@Vargan 필자는 JUnit이 설계 한 방식으로 자신의 어설 션을 만드는 방법으로 자신의 어설 션을 만드는 방법을 지적했습니다. JUnit은 의도적으로 자체 규칙을 작성하고 아직 구현되지 않은 어설 션으로 JUnit의 동작을 확장하기 위해 의도적으로 해당 목적을 위해 제공합니다. 이 세상에서 모든 것이 구현되는 것은 아니기 때문에 이러한 어설 션은 JUnit 어설 션이 통과 또는 실패 및 실패보고 측면에서 작동하는 것과 동일하게 작동합니다.
armagedescu
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.