JUnit5 와 AssertJ의 관계, AssertJ 사용방법

yeahdy_:)·2024년 1월 21일

Test Code

목록 보기
1/4
post-thumbnail

📌서론

Spring boot를 통해 테스트 코드를 작성할 때 다른 사람들의 테스트코드를 참고하면 대부분 JUnit5와 AssertJ를 같이 사용하는 것을 볼 수 있다.
코드를 따라 작성하면 테스트는 가능하지만, 이 두개가 어떤 차이점이 있고 어떻게 다르게 사용되는지 몰랐다.
그래서 테스트코드를 제대로 사용하기 위해 이 둘의 상관관계와 문법, 주로 많이 사용하는 BDD 테스트 구조에 대해 작성 해 보겠다.


📌JUnit5 와 AssertJ의 상관관계

  • JUnit5는 테스트 구조와 실행을 다루는데 중점을 두며, AssertJ는 테스트 중에 단언문을 작성하고 테스트 결과를 검증하는데 사용된다.
단언문: 테스트 코드에서 사용되는 문장으로, 특정 조건이 참(true)인지 거짓(false)인지를 확인/검증하는 역할
  • 일반적으로 JUnit 5를 사용하여 테스트 메소드를 정의하고 실행하며, AssertJ를 사용하여 테스트 메소드 내 코드 작성을 통해 객체나 데이터의 검증을 수행한다.
  • JUnit 5는 @Test, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll과 같은 어노테이션을 사용하여 테스트 메소드와 테스트 라이프사이클을 관리한다.
  • AssertJ는 다양한 자료형(문자열, 컬렉션, 객체 등)에 대한 참/거짓을 증명하는 단언 메소드를 포함하고, 커스텀 단언문도 작성 가능하여 유연한 테스트가 가능하다.

📌JUnit5 기본 문법

애노테이션(Annotations)대상설명
@Test메서드테스트 메서드임을 나타낸다.
@ParameterizedTest메서드파라미터를 가진 테스트 메소드를 작성할 수 있다.
@RepeatedTest메서드반복 테스트를 위한 테스트 템플릿임을 나타낸다.
@DisplayName클래스, 메서드테스트 클래스 또는 테스트 메서드에 대한 표시 이름을 지정한다.
@BeforeEach메서드현재 클래스의 각 테스트 메서드를 실행하기 전에 실행되어야 하는 메서드임을 나타낸다.
@AfterEach메서드현재 클래스의 각 테스트 메서드를 실행한 후에 실행되어야 하는 메서드임을 나타낸다.
@BeforeAll메서드현재 클래스의 모든 테스트 메서드를 실행하기 전에 실행되어야 하는 메서드임을 나타낸다.
@AfterAll메서드현재 클래스의 모든 테스트 메서드를 실행한 후에 실행되어야 하는 메서드임을 나타낸다.
@Tag클래스, 메서드필터링 테스트를 위한 태그를 선언하는데 사용한다.
@Disabled클래스, 메서드해당 테스트 클래스 또는 테스트 메서드를 비활성화 하는데 사용한다.
@ParameterizedTest
@ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE})	// value로 설정된 숫자를 하나씩 검증한다 (총 검증횟수: 6번)
void isOdd_ShouldReturnTrueForOddNumbers(int number) {
	// value의 숫자가 홀수인지 검증
    assertTrue(Numbers.isOdd(number));
}

📌AssertJ 기본 문법

Code설명
.isNotEmpty()값이 비어있지 않는지 판별
.contains("Nice")파라미터를 포함하고 있는지
.doesNotContain("ZZZ")파라미터를 포함하지 않는지 판별
.startsWith("Hell")파라미터로 시작하는지 판별
.endsWith("u.")파라미터로 끝나는지 판별
.isPositive()양수인지 판별
.isGreaterThan(3)파라미터 보다 큰지 판별
.isLessThan(4)파라미터 보다 작은지 판별
assertThat(RandomNumberGenerator.generateRandomNumber()).isBetween(0, 9)랜덤 숫자가 0부터 9까지의 숫자인지 판별


📌Static 메소드 Test

when() 메소드에서 정적 메소드를 사용할 수 없는 이유?

JUnit 5의 Mocking 프레임워크인 Mockito의 제한 때문이다. Mockito는 객체의 실제 인스턴스를 만들지 않고 가짜 객체를 만들어 사용하기 때문에 정적 메소드나 final 메소드와 같이 변경이 불가능한 메소드의 경우 사용할 수 없다.

예제 설명 참고: 벨둥의 Static Methods With Mockito , Mockito 사용한 static method Test 예제

그럼 리턴타입이 void인 메소드에 대한 테스트는 어떻게 해야하지?

모의 객체(Mock) 또는 스텁(Stub)을 사용하여 메소드의 동작을 검증하고 상태를 간접적으로 확인하는 Test Double 방법을 통해 실제 객체를 대체하여 테스트 할 수 있다.
void 메소드에 대한 스텁 처리를 위해서는 doAnswer() 또는 doNothing() 등을 사용해야 한다.

doNothing()

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.*;

public class MyClassTest {

    @Test
    @DisplayName("void 메소드를 1번 실행한다.")
    void test_void_method_with_stub() {
        MyClass mockMyClass = mock(MyClass.class);
        
        // void 메소드에 대한 스텁 처리
        doNothing().when(mockMyClass).voidMethod();

        // void 메소드 호출
        mockMyClass.voidMethod();

        // void 메소드가 1번 호출되었는지 확인
        verify(mockMyClass, times(1)).voidMethod(); 
    }
}

doAnswer()

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.*;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class MyClassTest {

    @Test
    @DisplayName("void 메소드를 1번 실행하면 상태는 완료이다.")
    void test_void_method_DoAnswer() {
        MyClass myObject = new MyClass();
        MyDependency mockDependency = mock(MyDependency.class);

        // void 메소드에 대한 스텁 처리
        doAnswer(new Answer<Void>() {
            public Void answer(InvocationOnMock invocation) {
                // 이어서 실행할 메소드
                myObject.setState("COMPLETE");
                return null;
            }
        }).when(mockDependency).voidMethod();

        myObject.setDependency(mockDependency);

        // void 메소드 호출
        myObject.voidMethod();

        // void 메소드가 1번 호출되었는지 확인
        verify(mockDependency, times(1)).voidMethod(); 
        // AssertJ를 통해 상태 검증
        assertThat(myObject.getState()).isEqualTo("COMPLETE");
    }
}

위 코드는 doAnswer()를 사용하여 voidMethod에 대한 스텁 처리를 하고 있다.
doAnswer()Answer 인터페이스를 구현하는 익명 클래스를 사용하여 void 메소드 voidMethod()의 호출에 대한 동작을 정의한다.
익명메소드인 answer 내에서 원하는 동작을 수행하고, 필요한 경우 객체의 상태를 변경하는 등 원하는 동작을 추가하여 검증이 가능하다.


📌결론

맨 처음에 테스트코드를 작성할 때는 내가 무엇을 테스트 하고 싶은건지 구체적으로 생각하는게 익숙하지 않아서 테스트를 하기 위한 테스트코드 작성 시간이 굉장히 오래걸렸다.
테스트 코드를 작성하기 위해서는 내가 무엇을,왜 테스트 해야하는지 요구사항을 명확하게 알아야 했고, 정확한 기대 결과를 검증하기 위해서 어떤 흐름으로 코드를 작성 해야 하는지 생각해야 했다.

지금은 실무에서도 기능 구현을 하면 꼭 JUnit5와 AsserJ를 통해 테스트코드를 작성하고 있는데 한번 테스트 코드를 작성 해 놓으니 유지보수할 때 많은 편리함을 느끼고 있다.
이전에는 모든 케이스를 일일히 주석으로 메모하고, 테스트를 완료하면 지우기고 다른 케이스를 진행하기를 반복했었는데 테스트 케이스를 통해 모두 기록이 되기 때문에 코드가 변경되어도 쉽게 테스트가 가능하게 되었다.

아직 Mock객체나 JWT 인증을 해야 사용 가능한 메소드에 대한 테스트코드 작성은 이해가 안되는 부분이 있어서 실무에서 만들어 볼 예정이다. (추후 블로그 업로드할지도!)

profile
기억하기 위해 기록하고 있습니다. 포스트 중 잘못된 정보가 있다면 코멘트 남겨주세요🐰

0개의 댓글