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

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

@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?를 사용하는 것 )



답변

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

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

단위 테스트 배경

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

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

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

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

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

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

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

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

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

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

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

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

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

TL; DR

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

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


답변

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 예상 값의 기본값입니다.


답변

AssertJ 유창 주장 3.7.0를 :

Assertions.assertThatCode(() -> toTest.method())
    .doesNotThrowAnyException();


답변

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

}


답변

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을 제공하는 것이 편리했기 때문에 테스트가 잘 이루어지고 테스트 데이터를 생성하는 것이 놀랍도록 어려운 문제이므로 매우 일반적입니다. 아무것도 테스트하지 않아도 녹색 막대가됩니다. 그러한 시험은 쓸모없는 것보다 더 나쁩니다.


답변

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

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


답변

JUnit5는이 정확한 목적을 위해 assertAll () 메소드를 추가합니다.

assertAll( () -> foo() )

출처 : JUnit 5 API