블로그 이미지

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 테스트 코드 수행 시간 개선하기
    Diary/우아한테크코스 2022. 10. 26. 10:57

    속닥속닥 프로젝트에서는 현재 425개의 테스트가 작성되어 있다. 프로젝트 중반이 넘어가기 전쯤에 DirtiesContext를 덜어내고 EntityManager를 통해 테스트 격리를 해 시간이 굉장히 오래 걸리진 않았지만, 그래도 테스트 코드 작성이 늘어가면서 시간이 늘어나는 것이 보였고 개선할수 있는것은 최대한 개선해보아야 겠다는 생각을 했다.


    Context Caching

    테스트에서 ApplicationContext 가 한번 로드되면, 같은 test suite 에서는 동일한 unique context 설정일 때 후에 진행되는 테스트들에 대해서 context를 재사용한다.

    ApplicationContext는 configuration 파라미터들의 조합에 의해 유니크함이 판별된다. 즉, 유니크한 cofiguration 파라미터들의 조합이 context가 캐싱되는 key가 된다.

    image

    위와 같은 파라미터들이 context 캐싱의 key가 되는데, 속닥속닥 프로젝트는 저중에 @MockBean@SpyBean 을 사용하고 있다.

    현재 프로젝트의 테스트형식은 ApplicationContext 로드가 필요한 각 테스트 종류 (Acceptance, Controller, Service) 를 부모 클래스로 분리해, 테스트 하고자 하는 클래스가 이를 상속하는 방식을 사용하고 있다.

    @DisplayName("회원 관련 인수테스트")
    class MemberAcceptanceTest extends AcceptanceTest {
    
    // ...
    
    }

    테스트가 실행되면서, ApplicationContext가 로드 될 때 모든 빈들을 로드시켜놓기 때문에 개별 클래스에서 그러한 빈 객체들을 필드로 두어도 새롭게 컨텍스트가 로드되지 않지만, 위에서 언급한대로 @MockBean@SpyBean 같은 기능을 사용하는 필드를 두게되면 컨텍스트 key가 달라지기 때문에 새롭게 로드된다.

    Screen Shot 2022-10-26 at 10 27 34 AMScreen Shot 2022-10-26 at 10 28 08 AM

    관리자 인수테스트에서 한번 컨텍스트가 로드 되었지만, 인증 관련 인수테스트에서 또 다시 로드되는 것을 볼 수 있다.

    이렇게 새롭게 컨텍스트를 로드할 경우에는 컨텍스트 로드 시간이 인텔리제이에서 테스트 시간으로 잡히지 않지만, 결국 테스트 시간을 늘게하는 주범이 된다. 성능이 좋은 로컬 랩탑이라면 모르겠지만… 이를 성능이 좋지 않은 EC2 인스턴스의 젠킨스에서 돌린다고 생각하면 시간차가 더더욱 날 것이다.

    컨텍스트를 새로 로드하는 것을 없애기 위해 @MockBean@SpyBean 을 부모 테스트 클래스로 넣어보자! 그 뒤에 테스트를 다시 실행해보도록 하겠다.

    컨텍스트캐싱전시간)컨텍스트캐싱후시간

    컨텍스트 재로드를 없애니 6초라는 시간이 줄어들었다!

    인수테스트의 토큰 재사용

    속닥속닥은 로그인을 해야만 이용할 수 있는 기능들이 많다. 그렇기 때문에 인수테스트에서 로그인을 통해 토큰을 받아오는 횟수가 굉장히 많은데, 원래는 토큰이 필요할 때 마다 로그인 요청을 보낸 뒤에 새로운 토큰을 받아 사용하는 방식이었다. 테스트에서 이 토큰을 항상 새로 발급받아야 되는 것도 아닌데, 그럼 캐싱하면 테스트 속도가 줄지 않을까? 라는 생각으로 캐싱을 진행하게 되었다.

    개선 전

    public class MemberFixture {
    
        //...
            public static String getToken(String username) {
                LoginRequest loginRequest = new LoginRequest(username, TEST_PASSWORD);
                return httpPost(loginRequest, "/login").header(AUTHORIZATION);
            }
    }

    캐싱 적용전에는 토큰을 발급하는 메서드를 사용할 때마다 매번 로그인 요청을 보내 토큰을 받아왔었고, 그 횟수는 다음과 같았다.

    로그인캐싱전토큰카운트

    개선 후

    public class TokenFixture {
    
        private static final Map<String, String> TOKENS = new ConcurrentHashMap<>();
    
        //...    
    
        public static String getToken(String username) {
            return TOKENS.computeIfAbsent(username, ignored -> {
                LoginRequest loginRequest = new LoginRequest(username, "Abcd123!@");
                return httpPost(loginRequest, "/login").header(AUTHORIZATION);
            });
        }
    }

    위와 같이 토큰을 캐싱하였다. username에 해당하는 토큰이 있다면 HashMap 에서 해당 토큰을 돌려주고, 없다면 로그인 요청을 보내 토큰을 받아와 HashMap에 저장뒤 반환하는 방식이다.

    로그인캐싱후토큰카운트

    이렇게 로그인 요청을 보내는 횟수가 174회 가량 대폭으로 줄었다.

    로그인캐싱전테스트시간)로그인캐싱후테스트시간

    테스트 시간은 2초 가량 줄었는데, 결과는 왔다 갔다 하는 편이지만 평균적으로 시간이 줄어든 것은 확실하다!

    테스트 시간을 더 줄일수 있는 방법을 연구해보고 적용해보도록 해야겠다.

    댓글

Designed by Tistory.