Gradle을 이용해 하나의 클래스 및 함수만 테스트 하기

하나의 클래스 테스트하기

Spring Boot 2 프로젝트를 진행하면서, JUnit 5을 이용해 테스트 주도 개발을 하고 있다. 그러던 중 한 가지 문제에 봉착하게 되었다. 테스트 규모가 커지기 시작하면서 테스트 실행 시, 모든 테스트를 실행하는 데에 부담이 생기기 시작한 것이다. 따라서 테스트 할 때, 클래스 혹은 함수 단위로 테스트를 할 필요가 생긴 것이다. 정말 많은 테스트 클래스 파일이 존재하지만, 아래의 클래스 하나만 테스트 하고 싶다고 가정해보자.

package com.example.demo

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

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

class ExampleTest {

    @Test
    @DisplayName("Test A")
    void testA() {
        assertTrue(true);
    }
    
    @Test
    @DisplayName("Test B")
    void testB() {
        assertTrue(true);
    }
}
ExampleTest.java

Gradle(이하 그레이들) 에서는 --tests 옵션을 이용해 테스트 할 클래스 혹은 함수를 제한할 수 있다. 해당 옵션을 사용하면 아래와 같이 ExampleTest만 테스트 하도록 할 수 있다.

./gradlew test --tests ExampleTest
# 혹은
./gradlew test --tests com.example.demo.ExampleTest
💡
gradlew란?
gradlew는 Gradle Wrapper의 약자로 그레이들 빌드를 실행하는데 권장되는 방법이다. gradlew를 사용하면 이미 존재하는 프로젝트를 그레이들을 설치하지 않더라도 새로운 환경에서 바로 빌드가 가능하다. 스프링 부트에서는 그레이들 기반의 프로젝트를 생성하면 루트 디렉토리에 이 gradlew 스크립트가 생성된다.

그레이들에서 테스트를 실행하면 build/classes/test 디렉토리 내부에 있는 클래스 파일을 자동으로 탐색하기 때문에, 클래스 파일 명만 적어줘도 테스트가 실행된다. 다만, 그레이들의 테스트는 아주 탐욕스럽게 동작한다. 이 때문에 클래스 파일 명만 적는 경우, 서로 다른 디렉토리에 있는 같은 이름의 클래스 파일들을 '전부' 테스트한다는 특징이 있다. 특정 클래스 파일만 테스트하려고 한다면 패키지 경로까지 같이 기술해주어야 자신이 원하는 대로 테스트가 된다.


하나의 함수 테스트하기

이번에는 ExampleTest 클래스 내부의 testA 함수만 테스트 해보고 싶다고 가정해보자. 이 경우에는 다음과 같은 명령어를 실행하면 된다.

# ExampleTest 클래스의 testA 함수를 테스트한다
./gradlew test --tests ExampleTest.testA

함수 테스트의 경우에는 클래스 테스트와 달리 함수명만 입력해서 테스트 할 수 없다. 그레이들에서는 기본적으로 단독 이름을 클래스로 인식하기 때문이다.

# 이 테스트는 실패한다
./gradlew test --tests testA

와일드 카드 사용하기

--tests 옵션의 인자는 와일드 카드를 지원한다. 와일드 카드는 클래스와 함수 구분 없이 모두 사용이 가능하다.

# Example로 시작하는 모든 클래스 테스트
./gradlew test --tests Example*

# 모든 클래스 내부에 존재하는 test 함수 테스트
./gradlew test --tests *.test

# 클래스 함수 모두 Test로 끝날 경우 테스트
./gradlew test --tests *Test

번외 - 중첩 클래스 테스트하기

테스트 케이스를 작성하다 보면, 언젠가 가독성이나 함수명의 중복 문제에 직면하게 된다. JUnit 5 부터는 @Nested 어노테이션을 지원하면서 부터, 중첩 클래스를 사용할 수 있도록 변경되었다. 이 중첩 클래스를 이용할 수 있게 되면서부터 개발자는 테스트 코드를 작성할 때, 가독성과 함수명 중복 문제를 해결할 수 있게 되었다.

package com.example.demo

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

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

class ExampleTest {

    @Nested
    @DisplayName("TEST")
    class Test {
    
    	@Test
        @DisplayName("SUCCESS!")
        void success() {
            assertTrue(true);
        }

        @Test
        @DisplayName("FAIL!")
        void fail() {
            assertFalse(false);
        }
    }
}
ExampleTest.java

그런데, IDE를 사용하고 있다면 상관없겠지만 명령어를 이용해 테스트할 때는 어떻게 테스트해야 할지 의문이다. IDE 에서는 Test 클래스의 경로가 com.example.demo.ExampleTest.Test로 표시되는데, 그냥 다음과 같이 입력하면 될까?

# 이 테스트는 실패한다
./gradlew test --tests ExampleTest.Test

그런데 실제로 실행해보면 알겠지만 이 명령어는 실행되지 않는다. 그러다 build 디렉토리를 보니, 중첩 클래스는 <외부 클래스 명>$<내부 클래스 명>.class 파일로 빌드가 된다는 사실을 알게 되었다. 즉, Test 클래스는 ExampleTest$Test.class라는 파일로 빌드된다. 그래서 나는 "이거 혹시 달러 기호로 적어주면 되는 건가?" 라는 생각을 했다.

./gradlew test --tests ExampleTest$Test

소 뒷걸음치다 쥐 잡은 격이지만, 위의 명령어를 입력했더니 정상적으로 실행이 되었다! 그런데 이 방법은 몇 가지 문제가 있었다. 일단 첫 번째로 ExampleTest 클래스 내부의 관련 없는 다른 중첩 클래스까지 모두 테스트가 실행 된 것이다. 두 번째로는 중첩 클래스 내부의 함수는 지정해서 테스트가 불가능 하다는 것이었다.

알고 보니 이건 $ 표시가 Shell에서 의미 있는 특수 기호로 쓰이기 때문에 생기는 문제였었다. 참고로 이건 Windows용인 PowerShell에서도 마찬가지이다. 즉, 나는 중첩 클래스를 실행할 목적으로 ExampleTest$Test란 명령어를 입력했지만 사실은 ExampleTest만 실행되고 있었던 것이다. 마찬가지로 중첩 클래스 내부의 함수를 지정해도 테스트가 불가능 했던 이유도 $ 이후로는 의미가 없는 명령어였기 때문이다.

위의 문제를 해결하려면 ExampleTest$Test를 작은따옴표(')로 감싸주면 된다. Shell에서 작은따옴표로 감싼 값들은 모두 문자열로 취급되기 때문에, $가 의미 있는 특수기호가 아닌 그냥 문자 $로 인식된다.

./gradlew test --tests 'ExampleTest$Test'

위의 명령어를 입력하면 정상적으로 ExampleTest 클래스 내부의 중첩 클래스인 Test가 정상적으로 테스트 되는 것을 확인할 수 있을 것이다.


정리하며

JUnit 5를 이용한 테스트 주도 개발을 하면서, 이번 기회에 그레이들을 이용한 단위 테스트 방식에 대해 정리해 보았다. 생각보다 그레이들을 이용한 단위 테스트 방법에 대해 적힌 글이 적었던 까닭에 이 글이 다른 사람에게 도움이 되길 바란다.


참고한 문헌 및 블로그 글

  1. How to run only specific tests with Gradle and JUnit 5?