Test

[kerdy] 테스트 시간 최적화 및 sourceSets를 사용하여 테스트 분리

자바생 2023. 8. 29. 01:23
728x90

글을 쓰게 된 이유

 

레벨 3을 마무리하고 나니 커디에 약 300개의 테스트가 있었습니다.

 

application layer에서는 @SpringBootTest를 통한 통합 테스트

presentation layer에서는 @WebMvcTest 슬라이스 테스트

JpaRepository에서는 @DataJpaTest 슬라이스 테스트를 진행했습니다.

 

 

이때 문제가 있었습니다.

 

rest docs가 잘 만들어지는 테스트 하려면 모든 테스트를 실행해야 하고,

service에서 기능을 하나 만들고, 해당 기능뿐만 아니라 전체 테스트를 실행할 때 rest docs 테스트까지 실행해야 합니다.

 

왜냐하면 service에서는 해당 테스트가 다른 테스트에 영향을 주는지 확인해야 하기 때문입니다.

 

테스트 자체가 내가 작성한 기능의 동작여부와 다른 기능의 사이드이펙트가 있는지 실시간으로 피드백받기 위해 작성하는데 시간이 너무 길기 때문에 실시간에서 멀어졌습니다,,

 

그래서 rest docs(@WebMvcTest)를 위한 테스트, 통합 테스트들을 분리하고,

최대한 ApplicationContext를 재활용하도록 테스트 최적화를 해보려고 합니다.

 

 

 

 

 

목표

 

gradlew test → @SpringBootTest, @DataJpaTest

 

gradlew documentTest → @WebMvcTest

 

 

디렉터리를 분리해서 통합 테스트와 문서화 테스트를 분리

 

 

 

@WebMvcTest 분리

 

 

sourceSets과 Tag

 

 

테스트를 분리하는 방법에는 sourceSets과 Tag가 있습니다.

 

 

저번에는 Tag를 사용했었으나 이번에는 sourceSets를 사용해보려 합니다.

그 이유는 Helper 클래스를 상속하지 않으면 해당 테스트 클래스에 모두 @Tag를 붙여줘야 하기 때문입니다.

 

 

기존에는 @SpringBootTest를 진행했을 때, 빈 관련 설정이 모두 똑같아서 하나의 Helper 클래스에서 테스트가 진행됐었습니다.

이번에는 알림 관련 서드파티로 인해 빈 설정이 다르게 되어 Helper 클래스를 상속하지 않는 테스트 클래스가 존재하게 됩니다.

그러면 해당 테스트 클래스에서 @Tag를 붙여줘야 하고, 만약 붙여주지 않으면 테스트가 실행이 안 되는 일이 발생할 수 있습니다.

 

 

이렇게 매번 빈 설정이 다른 클래스마다 태그를 붙여주게 되면 관리가 힘들어질 것 같아서

디렉터리로 크게 분리함으로써 변경점을 좁혀 documentTest와 test를 분리해보려고 합니다.

 

 

 

sourceSets

 

기본적으로 main, test 두 개의 sourceSets이 제공됩니다.

 

src/{sourceSets}/java , src/{sourceSets}/resources 처럼요.

 

 

sourceSets {
    documentTest {
        compileClasspath += sourceSets.main.output + sourceSets.test.output
        runtimeClasspath += sourceSets.main.output + sourceSets.test.output
    }
}

 

새로운 documentTest를 만들기 때문에 src/documentTest/java가 존재할 것이므로

documentTest라는 sourceSets을 만들기 위해 정의합니다.

 

sourceSets.main.output , sourceSets.test.output은

main, test의 컴파일된 결과물을 의미하고 build/classes/java/main or test에 해당됩니다.

 

 

즉, documentTest라는 새로운 sourceSet을 정의하고 나서 main에서 컴파일된 클래스와 리소스를 documentTest에서 접근할 수 있도록 합니다. 왜냐하면 테스트 관련 클래스들이 전부 main에 있기 때문입니다.

 

 

 

documentTest에 관한 dependency를 설정하는 것입니다.

 

기존에 있던 dependency를 가져오기 위해서는 extendsFrom을 사용하면 됩니다.

즉, documentTestImplementation은 기존에 있는 implementation, testImplementation 의존성을 가져와서 src/documentTest/java에서도 사용할 수 있습니다.

 

 

대표적으로 testImplementation 'org.springframework.boot:spring-boot-starter-test' 가 있습니다.

 

 

 

 

 

tasks.register('createDocument', Test) {
    testClassesDirs = sourceSets.documentTest.output.classesDirs
    classpath = sourceSets.documentTest.runtimeClasspath
    useJUnitPlatform()

    outputs.dir snippetsDir
}

 

이제 createDocument task를 하나 만들어줍니다.

이 부분은 api docs를 만들어주는 부분이기 때문에 createDocument라 했습니다.

메서드 분리하는 것과 똑같다고 생각해 주시면 됩니다.

 

 

기존에 존재했던 documentTest sourceSets과 유사하기 때문에 넘어가도록 하겠습니다.

 

 

 

 

이전 build 할 때, copyDocument → asciidoctor → test(통합테스트)로 의존하고 있었습니다.

하지만 이번엔 통합 테스트와 문서화 테스트를 분리하고, build 할 때는 두 테스트를 실행하도록 하려고 합니다.

 

 

그래서 build를 하게 되면 copyDocument, test를 합니다.

 

 

documentTest도 존재하지만, documentTest는 copyDocument를 dependsOn 하고 있으므로 build 할 때는 copyDocument를 실행하도록 했습니다.

 

 

 

기존에 restdocs.mockMvc dependency는 src/test/java에 있었습니다.

 

하지만 restdocs.mockMvc는 @WebMvcTest를 하는 api 들에게만 필요한 것이므로, 굳이 src/test에 필요 없기 때문에 documentTest로 의존성을 넣어주었습니다.

 

 

어차피 main에 다 넣고 상속을 하면 사용할 수 있지만, 이것 또한 분리해 두면 좋지 않을까라는 생각이었습니다.

 

 

tasks.register('documentTest', Test) {
    dependsOn copyDocument
}

 

최종적으로 documentTest → copyDocument → asciidoctor → createDocument로 의존하고 있습니다.

해당 task를 만든 이유는 rest docs 테스트를 하면 자동으로 문서가 생성하도록 하기 위함입니다.

 

 

테스트 최적화

 

테스트 최적화에서 가장 중요한 점은 ApplicationContext 재활용입니다.

 

스프링에서는 매 테스트마다 ApplicationContext를 새로 만드는 것은 비효율적이라 생각하여 Context Caching을 제공합니다.

하지만 이때 configuration이 달라지면 다시 ApplicationContext를 만들게 됩니다.

 

즉, 우리는 configuration을 같게 해 줘야 스프링 테스트 자체에서는 configuration이 같기 때문에 ApplicationContext를 재활용합니다.

 

@MockBean을 사용할 때 이 cache key가 달라지게 되므로 하나의 ApplicationContext에 @MockBean을 한 번에 모킹함으로써 cache key가 같도록 유지해야 합니다.

 

 

rest docs @WebMvcTest를 진행할 때 @MockBean을 사용하므로 Helper 클래스에서 @MockBean을 모두 넣어주게 되면 ApplicationContext를 재활용합니다.

 

 

 

테스트 분리 및 최적화 결과

 

 

 

 

아래는 제가 테스트 최적화 하기 전(왼쪽), 후(오른쪽) 영상을 통해 비교해 보았습니다.

 

오른쪽 테스트는 영상에서 2분 48초에 끝이 났고, 왼쪽 테스트는 3분 27초에 끝났습니다.

 

약 39초가량 최적화한 것을 알 수 있습니다.

 

https://youtu.be/96olqCIln-E

 

 

결론

 

 

이번 글을 통해서는 gradle 명령어를 사용하여 디렉터리로 테스트를 분리해 보았습니다.

그리고 ApplicationContext를 재활용하기 위한 방법들을 학습했고요.

 

기존 문서화 테스트를 할 때는 @WebMvcTest, @SpringBootTest, @DataJpaTest를 모두 실행해야 rest docs 문서가 생성됐습니다.

rest docs 테스트를 실행하고 문서가 만들어지기까지 약 3분 30초가량의 시간이 필요했습니다.

하지만 디렉터리 분리, 테스트 최적화를 통해 약 47초까지 rest docs 테스트를 최적화할 수 있었습니다.

@SpringBootTest, @DataJpaTest는 약 39초가량 최적화됐습니다.

 

 

./gradlew build

  • service 통테, 도메인 테스트 및 rest docs test

./gradlew documentTest

  • rest docs test

./gradlew test

  • service 통테 및 도메인 테스트

 

와 같이 커멘드를 통해 테스트를 따로 수행할 수 있습니다.

 

 

 

REFERENCES

 

https://www.baeldung.com/spring-tests

https://www.youtube.com/watch?v=N06UeRrWFdY&t=120s

 

 

 

 

728x90