[Java] JUnit5 과 AssertJ 로 단위 테스트 작성하기

이상현·2023년 10월 10일
1

Java

목록 보기
5/21
post-thumbnail

단위 테스트란?

유닛 테스트는 컴퓨터 프로그래밍에서 소스 코드의 특정 모듈(함수)이 의도된 대로 정확히 작동하는지 검증하는 절차다. 즉, 모든 함수와 메소드에 대한 테스트 케이스를 작성하는 절차를 말한다.

JUnit5

JUnit은 많이 쓰이는 자바의 테스팅 프레임워크이다.

AssertJ

에러 메세지의 가독성을 높혀주는 라이브러리이다.
JUinit5 - assertEquals() 보다
AssertJ - assertThat().isEqualTo() 가 가독성이 좋아 많이 쓰인다.

예시 코드

import static org.junit.jupiter.api.Assertions.assertEquals;
import example.util.Calculator;
import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {
    private final Calculator calculator = new Calculator();

    @Test
    void addition() {
        assertEquals(2, calculator.add(1, 1));
    }
}

@Test 와 같이 JUnit의 프레임워크를 메소드가 상속하며 알맞은 기능을 수행한다.

@어노테이션

테스트 메소드

테스트 메소드는 abstract이면 안되고, private면 안되고, 리턴값이 있으면 안된다.

  • @Test
    해당 메소드가 테스트 메소드 라는 것을 나타낸다.

  • @ParameterizedTest
    다른 인수로 여러번 작동될 수 있는 테스트로 선언한다.

    @ParameterizedTest
    @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
    void palindromes(String candidate) {
        assertTrue(StringUtils.isPalindrome(candidate));
    }
  • @RepeatedTest(Int i)
    메소드가 i 번 테스트를 반복하는 메소드로 선언한다.

    @RepeatedTest(10)
    void repeatedTest() {
        // ...
    }
  • @TestFactory
    동적 테스트로 선언

라이프사이클 메소드

  • @BeforeEach
    테스트 메소드들이 실행되기 전에 매번 실행되는 하는 메소드로 지정한다.

  • @BeforeAll
    @BeforeEach는 모든 테스트 메소드 마다 실행되지만, 이것은 전체 테스트가 실행되기 전 딱 한번만 실행된다.

  • @AfterEach
    테스트 메소드들이 실행되고 난 후 매번 실행되는 메소드로 지정한다.

  • @AfterAll
    전체 테스트가 끝난 후 한번 실행된다.

기타

  • @DisplayName("1 + 1 = 2 테스트")
    테스트 클래스 또는 메소드가 표시될 이름을 정해준다.
    정하지 않을시 메소드 이름으로 따라간다.

  • @Nested
    test 클래스 안에 Nested 테스트 클래스를 작성할때 사용된다. static이면 안된다. 테스트 인스턴스의 라이프사이클이 per-class로 되어있지 않다면 @BefreAll, @AfterAll이 동작하지 않는다.

  • @Tag
    클래스또는 메소드레벨에서 테스트 필터링을 위해 선언한다.

  • @Disabled
    테스트 클래스나, 메소드의 테스트를 비활성화 한다. (ex 버그 수정 중 임시 조치)

  • @Timeout
    테스트가 지정된 시간안에 끝나지 않으면 실패한다.

  • @ExtendWith
    extension을 등록한다. 이 어노테이션은 상속이 된다.

  • @RegisterExtension
    필드를 통해 extension을 등록한다.

  • @TempDir
    라이프사이클 또는 테스트에서 필드 주입이나 파라미터 주입을 통해 임시 디렉토리를 제공할 때 사용한다.

테스트 클래스

한개 이상의 @Test 가 달린 테스트 메소드가 포함된 클래스이다. private면 안된다.

Assertions

프로그램의 특정 지점에 위치한 어써션은 해당 지점에서 개발자가 반드시 참(true)이어야 한다고 생각하는 사항을 표현한 논리식이다. Assertion이 위반되는 경우(즉, 논리식 결과가 거짓)는 프로그램에 버그나 기타 문제가 있는 것을 암시한다.

JUnit 의 Assertion 은 정적 메소드이고, org.junit.jupiter.api.Assertions 안에 있다.

하지만, JUnit 이 제공하는 Assertion은 부족할 수 있기 때문에 위에서 설명했던 AssertJ를 주로 사용한다.

  • assertThat(결과).isEqualTo(예상결과);
    이 패턴을 가장 많이 사용한다.

    결과값의 유형에 따라
    contains containsExactly containsAll 등 일반적인 Java API 메소드를 사용하면 된다.

  • assertThatThrownBy(() -> 테스트 메소드).isInstanceOf(예상 예외);
    테스트 메소드 실행 시 발생할 예외 잡기

기능 테스트 클래스 예시 코드

테스트 할 메소드: Calculator.java

package com.example.project;

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

테스트 코드: CalculatorTests.java

package org.example;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class CalculatorTests {
    @Test
    @DisplayName("1 + 1 = 2 테스트")
    void addsTwoNumbers() {
        Calculator calculator = new Calculator();
        assertThat(calculator.add(1, 1)).isEqualTo(2);
    }

    @ParameterizedTest(name = "{0} + {1} = {2}")
    @CsvSource({
            "0,    1,   1",
            "1,    2,   3",
            "49,  51, 100",
            "1,  100, 101"
    })
    void add(int first, int second, int expectedResult) {
        Calculator calculator = new Calculator();
        assertThat(calculator.add(first, second)).isEqualTo(expectedResult);
    }
}

Scanner 테스트

  • Scanner로 유저의 입력을 받는 메소드를 테스트 해야 할 때
    스캐너를 사용할때 코드를 살펴보면
    Scanner scanner = new Scanner(System.in);
    System.in 이 눈에 띈다.
    System 클래스에는 "표준" 입력 스트림을 재할당 할 수 있는 메소드가 있다.

  • System.setIn
    System.setIn(new ByteArrayInputStream("사용자 입력".getBytes()));
    다음과 같이 사용하면 System.in 버퍼에 "사용자 입력" 을 넣을 수 있다.

테스트 예시

public boolean inputGameContinue() {
    System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
    Scanner scanner = new Scanner(System.in);
    return scanner.nextInt();
}

위와 같은 메소드가 있을 때...

@Test
void inputGameContinue_재시작_검사() {
    System.setIn(new ByteArrayInputStream("1".getBytes()));
    assertThat(b.inputGameContinue()).isEqualTo("1");

	System.setIn(new ByteArrayInputStream("2".getBytes()));
    assertThat(b.inputGameContinue()).isEqualTo("2");
}

@Test
void 예외_발생_테스트() {
	System.setIn(new ByteArrayInputStream("asd".getBytes()));
    assertThatThrownBy(() -> b.inputGameContinue())
            .isInstanceOf(InputMismatchException.class);
}

예외 테스트 예시 코드

@Test
void 문자열_형식오류_예외발생_테스트() {
    String invalidInput = "ㅇ";
    assertThatThrownBy(() -> {
    	// IllegalArgumentException 발생하는 메소드 호출
		numberBaseballGame.validateString(invalidInput);
    }).isInstanceOf(IllegalArgumentException.class);
}

0개의 댓글