tools & libs/단위테스트(junit, spock)

[junit] jupiter 2. assertion

  • -
반응형

단위테스트의 목적이 "동작 결과가 내가 예상하는 값과 동일한가?"를 테스트하는 것이기 때문에 assertion 즉 단정문을 통한 결과 검증은 필수적인 항목이다. 

junit 5에서는 org.junit.jupiter.api.Assertions 클래스에 다양한 assertion 메서드들을 static 하게 준비해 두었다.

다음 클래스의 메서드가 잘 동작하는지 테스트해보자.

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int divide(int a, int b) {
        return a / b;
    }
}

 

일반적인 assertion

 

standard assertion

대부분의 assertion 메서드들은 긍정과 부정이 쌍으로 존재한다. 예를 들어 둘의 결과가 동일한지 검증하기 위해서는 assertEquals, 동일하지 않은지 검증하기 위해서는 assertNotEquals가 사용된다. 유사하게 assertNull vs assertNotNull, assertTrue vs assertFalse등의 메서드가 파라미터의 타입에 따라서 어마어마하게 overloading 되어있다.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import com.doding.mvntest.Calculator;

public class AssertionTest {
  private static Calculator target;

  @BeforeAll
  static void setup() {
    target = new Calculator();
  }

  @Test
  @DisplayName("standard assertion: 하나가 실패하면 이후의 검증은 취소된다.")
  public void standardAssertionTest() {
    assertNotNull(target);
    assertEquals(target.divide(10, 5), 2);
    assertNotEquals(target.divide(10, 5), 3);

    assertTrue(target.divide(10, 5) == 2);
  }

}

 

equals vs same

assertEquals 계열의 검증은 객체의 equals를 이용해서 내용을 검증한다. 반면 두 객체가 동일한 객체인지(하나의 객체 참조)를 파악하기 위해서는  assertSame을 사용할 수 있다.

@Test
@DisplayName("같은 객체를 참조하는가? 같은 내용인가")
public void assertEqualsSameTest() {
    String str1 = new String("Hello");
    String str2 = new String("Hello");
    assertEquals(str1, str2);
    assertNotSame(str1, str2);
}

 

배열 내용의 검증

assertEquals 계열은 객체의 equals 메서드를 이용해서 검증하는데 배열은 equals가 재정의 되지 않았기 때문에 내용을 비교할 수가 없다. JUnit에서는 배열의 내용 검증을 위해 assertArrayEquals 메서드들이 제공된다.

@Test
@DisplayName("배열의 내용이 같은가?")
public void assertArray() {
  char[] chars1 = "HelloJUnit".toCharArray();
  char[] chars2 = "HelloJUnit".toCharArray();
  assertNotEquals(chars1, chars2);
  assertArrayEquals(chars1, chars2);
}

 

예외에 대한 assertion

 

assertThrows

테스트는 10/5를 했을 때 2가 나오는 경우처럼 정상적인 상황뿐만 아니라 문제 상황에서 적절한 예외가 잘 발생하는지도 검증 되어야 한다. 즉, 10/0처럼 정수를 0으로 나누는 상황에서는 당연히 ArithmeticException이 발생해야 정상이다. 이런 상황을 처리하기 위해서는 assertThrows 메서드를 사용할 수 있다.

@Test
@DisplayName("예외 상황에 대한 assertion")
public void assertThrowsTest() {
  ArithmeticException e = assertThrows(ArithmeticException.class, () -> target.divide(10, 0));
  assertEquals(e.getMessage(), "/ by zero");
}

 

즉 위와 같은 상황에서는 오히려 예외가 발생하지 않으면 테스트 실패이다. assertThrows의 반환 타입은 Exception 이기 때문에 만약 예외 객체의 메시지를 단정하고 싶다면 추가로 assertEquals를 사용해서 단정할 수 있다.

 

기타

 

grouped assertion

일반적으로 하나의 단위테스트에서는 하나의 단정문을 사용하는 것이 권장되지만 유사한 테스트 환경에 의해 중복 코드가 발생하거나 테스트 환경 구성 비용 때문에 성능 저하가 발생하는 경우가 있다. 이런 경우 assertAll을 통해서 여러 검증을 하나로 묶어서 효과적으로 처리할 수 있다.

// import static org.junit.jupiter.api.Assertions.assertAll;

@Test
@DisplayName("grouped assertion: 하나가 실패하더라도 모든 검증을 실행한다.")
public void groupedAssertionTest() {
  assertAll(() -> {
    assertEquals(target.divide(10, 5), 100);  // 결과는 2인데 100?
  }, () -> {
    assertNotEquals(target.divide(10, 5), 2); // 2가 맞는데 아니라고?
  }, () -> {
    assertNotNull(target);                    // 앞에서 테스트 실패했지만 진행
  }, () -> {
    assertTrue(target.divide(10, 5) == 2);
  });
}

즉 위와 같은 경우 1번째와 2번째 단정문 모두 실패하는데 만약 standard assertion에서 이런 일이 발생하면 첫 번째 실패에서 더 이상 다음 assertion을 진행하지 않는다. 하지만 assertAll로 묶어서 처리하는 경우 첫 번째에서 실패하더라도 다음 assertion을 계속 실행해서 오류 내용을 보고한다. 

MultipleFailure에 2개의 실패가 보고되고 있다.

 

시간 기반의 검증

어떤 메서드의 동작이 특정 시간 이내에 끝나야 하거나 또는 특정 시간보다 더 오래 걸려야 하는 경우를 테스트할 때에는 assertTimeout이 사용된다. assertTimeout은 첫 번째 파라미터로 java.time 패키지의 Duration 타입을 받아서 작업이 끝나야 하는 시간을 지정한다.

@Test
@DisplayName("시간 내에 끝나야 하는 작업")
public void assertTimeoutTest() {
    assertTimeout(Duration.ofMillis(10), ()->{
        System.out.println("10ms 내에 짧게 끝나는 작업");
    });
}

즉 다음과 같이 지정된 10ms를 훌쩍 넘겨서 200ms 걸리는 작업이 있다면 테스트에 실패하게 된다.

@Test
@DisplayName("시간 내에 끝나야 하는 작업 - 테스트 실패")
public void assertTimeoutFailTest() {
    assertTimeout(Duration.ofMillis(10), ()->{
        Thread.sleep(200);
    });
}

 

만약 메서드의 반환값까지 검증하기 위해서는 예외 상횡에서와 마찬가지로 반환값을 이용해서 다시 단정문을 작성한다.

@Test
@DisplayName("시간 내에 끝내고 반환값이 일치해야 하는 경우")
public void assertTimeoutWithResultTest() {
    int result = assertTimeout(Duration.ofSeconds(10), ()->target.divide(10, 5));
    assertEquals(result, 2);
}

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.