JUnit / 단위 테스트

jungseo·2023년 6월 28일
1

Spring

목록 보기
13/23
post-thumbnail

1. 단위 테스트

  • 기능 테스트 : 애플리케이션을 사용하는 사용자 입장에서 애플리케이션이 제공하는 기능이 올바르게 동작하는지를 테스트
  • 통합 테스트 : Controller의 API를 호출하는 테스트 코드를 작성한 후 서비스 계층과 데이터 액세스 계층을 거쳐 DB에 실제로 접속해서 기대했던 대로 동작 하는지를 테스트
  • 슬라이스 테스트 : 계층별 테스트
  • 단위 테스트
    • 메서드 단위 테스트
    • 주로 서비스 계층의 비즈니스 로직을 테스트

1) 단위 테스트를 위한 F.I.R.S.T 원칙

  • Fast(빠르게)
    • 작성된 테스트 케이스는 빨라야 한다.
  • Independent(독립적으로)
    • 실행되는 테스트 케이스의 순서에 상관없이 정상적인 실행이 보장 되어야 한다.
  • Repeatable(반복 가능하도록)
    • 어떤 환경에서 실행하든 반복해서 같은 결과가 나와야 한다.
  • Self-validating(스스로 검증 되도록)
    • 테스트 케이스 스스로 결과가 성공인지 실패인지 보여주어야 한다.
  • Timely(시기적절하게)
    • 테스트하려는 기능 구현을 하기 직전에 작성해야 한다.

2) given - when - then

  • given
    • 테스트에 필요한 전제 조건
    • 테스트 대상에 전달되는 값 포함
  • when
    • 테스트 대상 지정
  • then
    • 결과 검증
    • 예상 값과 테스트 대상 메서드 실행 결과를 비교

2. JUnit

  • Java 언어로 만들어진 애플리케이션을 테스트하기 위한 오픈 소스 테스트 프레임워크
  • Spring Boot Intializr를 이용해서 프로젝트를 생성하면 기본적으로
    testImplementation>'org.springframework.boot:spring-boot-starter-test' 스타터가 포함되며, JUnit도 포함

1) Assertion

(1) assertEquals()

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

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

public class HelloJUnitTest {

    @Test
    @DisplayName("AssertionEqual() Test") // (1)
    public void assertionTest() {
        String expected = "Hello";
        String actual = "Hello";

        assertEquals(expected, actual); // (2)
    }
}
  • (1)DisplayName("") : 테스트 케이스 실행 시 실행 결과 창에 표시되는 이름 지정
  • (2) 기대값과 실제 결과값이 일치하는지 검증

(2) assertNotNull() : null 여부 테스트

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

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

public class HelloJUnitTest {

    @Test
    @DisplayName("AssertionNull() Test")
    public void assertionNotNullTest() {

        String currencyName = CryptoCurrency.map.get("ETH");

// 		  (1)
        assertNotNull(currencyName, "should be not null");
    }
}

// 테스트를 위한 CryptoCurrency 클래스
public class CryptoCurrency {
    public static Map<String, String> map = new HashMap<>();

    static {
        map.put("BTC", "Bitcoin");
        map.put("ETH", "Ethereum");
        map.put("ADA", "ADA");
        map.put("POT", "Polkadot");
    }
}
  • (1) 대상 객체가 null인지 테스트
  • (테스트 대상 객체, "테스트 실패 시 표시할 메세지")

(3) assertThrows() : 예외 테스트

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

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

public class HelloJUnitTest {

    @Test
    @DisplayName("throws NullPointerException")
    public void assertionExceptionTest() {

//		  (1)
        assertThrows(NullPointerException.class, () ->
                CryptoCurrency.map.get("XRD").toUpperCase());
//        assertThrows(RuntimeException.class, () ->           // passed
//                CryptoCurrency.map.get("XRD").toUpperCase());
//        assertThrows(IllegalStateException.class, () ->      // failed
//                CryptoCurrency.map.get("XRD").toUpperCase()); 
    }
}

// 테스트를 위한 CryptoCurrency 클래스
public class CryptoCurrency {
    public static Map<String, String> map = new HashMap<>();

    static {
        map.put("BTC", "Bitcoin");
        map.put("ETH", "Ethereum");
        map.put("ADA", "ADA");
        map.put("POT", "Polkadot");
    }
}
  • (1) 첫 번째 파라미터에 발생이 기대되는 예외 클래스를 입력하고, 두 번째 파라미터인 람다 표현식에서는 테스트 대상 메서드를 호출
  • 해당 예외 발생시 성공

(4) @BeforeEach : 전처리

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

public class BeforeEachTest1 {
    @BeforeEach
    public void init() {
        System.out.println("pre-processing before each test case");
    }

    @Test
    @DisplayName("Test1")
    public void test1() {
        System.out.println("test1");
    }
    
    @Test
    @DisplayName("Test2")
    public void test2() {
        System.out.println("test2");
    }
}

/*
* 출력
* pre-processing before each test case
* test1
* pre-processing before each test case
* test2
*/
  • @BeforeEach가 붙은 메서드는 테스트 케이스가 각각 실행될때마다 먼저 실행

(5) @BeforeAll : 전처리

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

import java.util.HashMap;
import java.util.Map;

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

public class BeforeAllTest {

    private static Map<String, String> map;

    @BeforeAll // static 메서드여야함
    public static void init() {
        map = new HashMap<>();
        map.put("BTC", "Bitcoin");
        map.put("ETH", "Ethereum");
        map.put("ADA", "ADA");
        map.put("POT", "Polkadot");
        map.put("XRP", "Ripple");

        System.out.println("initialize Crypto Currency map");
    }

    @Test
    @DisplayName("Test1")
    public void test1() {
        assertDoesNotThrow(() -> map.get("XRP").toUpperCase());
    }

    @Test
    @DisplayName("Test2")
    public void test2() {
        assertDoesNotThrow(() -> map.get("ADA").toUpperCase());
    }
}
  • 테스트 케이스가 실행되기 전 한번만 초기화 작업 실행
  • 테스트 케이스들에서 초기화한 값 사용 가능

2) Assumption

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

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

public class AssumptionTest {

    @Test
    @DisplayName("Assumption Test")
    public void assumptionTest() {
//		  (1)
        assumeTrue(System.getProperty("os.name").startsWith("Windows"));
//        assumeTrue(System.getProperty("os.name").startsWith("Linux")); // (2)
        System.out.println("execute?");
        assertTrue(processOnlyWindowsTask());
    }
    private boolean processOnlyWindowsTask() {
        return true;
    }
}
  • 특정 환경에만 테스트 케이스 실행
  • (1) assumpTrue() 파라미터의 값이 true이면 나머지 아래 로직 실행

3) Hamcrest

  • JUnit 기반의 단위 테스트에서 사용할 수 있는 Assertion Framework
  • 자연스러운 Matcher로 가독성 향상
  • 위에서 작성한 JUnit Assertion을 사용한 단위 테스트에 적용
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.hamcrest.MatcherAssert.assertThat;

public class HelloJUnitTest {

    @Test
    @DisplayName("AssertionEqual() Test")
    public void assertionTest() {
        String expected = "Hello";
        String actual = "Hell";

        assertEquals(expected, actual);
//        (1)
        assertThat(actual, is(equalTo(expected)));

    }

    @Test
    @DisplayName("AssertionNull() Test")
    public void assertionNotNullTest() {

        String currencyName = CryptoCurrency.map.get("ETH");

        assertNotNull(currencyName, "should be not null");
//        (2)
        assertThat(currencyName, is(notNullValue()));
        assertThat(currencyName, is(nullValue()));
    }

    @Test
    @DisplayName("throws NullPointerException")
    public void assertionExceptionTest() {

//        (3)
        Throwable actualException = assertThrows(NullPointerException.class,
                () -> CryptoCurrency.map.get("XRP").toUpperCase());

        assertThat(actualException.getClass(), is(NullPointerException.class));
    }
}

0개의 댓글