들어가며
테스트를 작성할때 통합테스트, 단위테스트를 나눠서 관리하고 싶었다. 단위 테스트와 통합테스트는 테스트 소요 시간이 차이가 나고, 단위테스트에서 필요한 의존성과 통합 테스트에서 필요한 의존성이 다른 경우가 많다. Gradle 에서 통합테스트는 통합 테스트끼리, 단위 테스트는 단위 테스트끼리 실행하고 싶을 경우 별도 모듈로 나누거나, 별도 패키지로 나눠서 관리할 수 있다. 이 글은 패키지로 나누는 경우에 대한 글이다.
아이디어
sourceSets는 Gradle에서 소스 코드와 리소스 파일의 경로, 클래스 경로 등을 정의하는 구조다. Gradle에서 java(또는 java-library) 플러그인을 추가하면 main, test 두 개의 sourceSet 들이 정의된다. main 에는 application 코드, test 에는 테스트코드가 위치한다.
- main
- source: src/main/java 아래에 있는 파일들
- resource: src/main/resources ""
- output: build/classes/java/main 아래에 컴파일 된 코드 생성
- test
- source: src/test/java 아래에 있는 파일들
- resource: src/main/resources ""
- output: build/classes/java/test 아래에 컴파일 된 코드 생성
여기에 추가로 integration-test 라는 sourceSet 을 만들고 이를 인텔리제이에서 test sourceSet 처럼 인식할 수 있도록 만들면 될 것이다.
SourceSet 추가
기본적으로 test sourceSet의 compileClasspath 와 runtimeClasspath는 main sourceSet의 의존성을 상속받는다.
sourceSets {
test {
compileClasspath += main.output
runtimeClasspath += main.output
}
}
- compileClasspath: 코드를 컴파일하는 데 필요한 모든 클래스 및 리소스
- runtimeClasspath: 코드를 실행하는 데 필요한 모든 클래스 및 리소스
새로 만들 integrationTest 의 sourceSet 구성이다.
sourceSets {
integrationTest {
java.srcDir "$projectDir/src/integration-test/java"
resources.srcDir "$projectDir/src/integration-test/resources"
compileClasspath += main.output
runtimeClasspath += main.output
}
}
별도 sourceSet 을 추가하려면 경로를 지정해줘야 한다. java 와 resources 경로에 대해 srcDir 로 추가한다. compileClasspath 와 runtimeClasspath 에는 애플리케이션 코드를 참조하기 위해 main.output 을 추가한다.
- java.srcDir: integrationTest의 소스 코드를 Gradle이 인식하려면 필요
- resources.srcDir: 통합 테스트에서 resources 를 사용하려면 필요
- compileClasspath += main.output: 통합 테스트 컴파일시 애플리케이션 코드를 참조하려면 필요
- runtimeClasspath += main.output: 통합 테스트 실행 시 애플리케이션 코드를 참조하려면 필요
만약 test 하위에 필요한 테스트 유틸리티나 클래스 등이 있다면 compileClasspath, runtimeClasspath 에 test.output 을 추가하면 된다. 상황과 필요에 의해 선택하면 될 것 같다.
integrationTest Task 추가
Gradle Task 를 추가하면 되는데 필자는 이름을 integrationTest 로 지정했다.
tasks.register('integrationTest', Test) {
group 'verification'
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
useJUnitPlatform()
}
Gradle의 Test 태스크를 Base Class로 상속받아 테스트 실행에 필요한 설정을 추가한다.
testClassesDirs: Gradle이 integrationTest 소스 세트의 컴파일된 테스트 클래스를 찾아서 실행할 수 있도록 지정하는 설정이다. Gradle 이 기본적으로 test sourceSet만 실행대상으로 간주하므로 sourceSets.integrationTest.output.classesDirs를 지정한다.
classpath: classpath는JVM에서 프로그램이 실행될 때 사용할 클래스파일과 리소스를 찾는 경로다. 간단히 말해서, JVM이 컴파일된 클래스파일, 외부라이브러리(ex Jar 파일)를 어디에서 찾아야 할지 정의하는 설정이다. Gradle에서 Test 태스크는 런타임 환경에서 실행되므로 integrationTest 의 runtimeClasspath를 추가해 실행 시 필요한 클래스와 리소스를 포함시켜준다.
useJUnitPlatform(): Junit5 를 사용하므로 을 추가한다.
마지막으로 빌드시 integrationTest 와 test 를 실행하도록 설정한다.
Gradle build 태스크는 check 태스크를 의존하고 check 태스크는 test 태스크에 의존한다. 여기서 integrationTest 도 check 태스크에 함께 의존하도록 추가해준다.
check {
dependsOn integrationTest
}
integrationTask 태스크가 추가되었는지 확인해본다. group 을 verification 으로 지정했기 때문에 verification 그룹을 보면 된다.
- verification 그룹: 코드 품질 검증과 관련된 태스크를 포함하는 기본 그룹
빌드 후 test-result 를 확인하면 정상적으로 추가된 것을 볼 수 있다.
의존성 설정
의존성은 기본적으로 test 의존성을 상속받도록 구성했다. configurations 에서 extendsFrom 을 이용해 testImplementation, testRuntimeOnly 의존성을 받아 integrationTest 에서도 사용할 수 있도록 구성했다.
configurations {
integrationTestImplementation.extendsFrom testImplementation
integrationTestRuntimeOnly.extendsFrom testRuntimeOnly
}
integrationTest에서만 사용할 의존성은 integrationTestImplementation 으로 추가해 별도로 관리하도록 구성했다.
// rest assured
integrationTestImplementation 'io.rest-assured:rest-assured:5.3.1'
integrationTestImplementation 'io.rest-assured:json-path:5.3.1'
// testcontainers
integrationTestImplementation 'org.testcontainers:testcontainers:1.20.1'
integrationTestImplementation 'org.testcontainers:junit-jupiter:1.20.1'
integrationTestImplementation "org.testcontainers:mysql:1.20.1"
integrationTestImplementation "com.redis:testcontainers-redis:2.2.2"
Intellij 에서 test 디렉터리로 인식되도록 설정
여기까지 설정하면 test 폴더로 인식되지 않고 source 폴더로 인식된다. 따라서 테스트로 이동이나 리팩토링시 패키지경로 미변경 등 불편함이 있었고, gradle 의 idea 플러그인을 이용해 해결할 수 있었다.
plugins {
id 'idea'
}
idea {
module {
testSources.from(sourceSets.integrationTest.java.srcDirs)
testResources.from(sourceSets.integrationTest.resources.srcDirs)
}
}
적용하니 정상적으로 테스트폴더로 인식되었다.