Junit5 - 7. 단위테스트 성능 개선기 (Feat. 회사 코드에 적용하기)

겔로그·2023년 8월 5일
0
post-thumbnail

이전 시간 Preview

이전 글에서 단위테스트와 통합테스트를 분리해 local 환경에서의 수행시간 단축을 완료하였으나 단위테스트 소요시간은 다음과 같았습니다.

프로젝트테스트 수행시간 (개선 전)
api1m 4s
web17s
batch2m 43s
smtp31s
util10s
common9s

단위테스트가 400~500개씩 있다고는 하지만.. 프로젝트별로 수행시간이 너무 상이해 해당 부분에 대한 원인을 분석하고 개선하는 시간을 가져보았습니다.

단위테스트 개선 과정

1. 원인 분석 (Feat.gradle)

Gradle에서 JUnitPlatform을 사용해 테스트를 수행하니 다음과 같은 신기한 파일이 생성되었습니다.

수행한 테스트의 수행 결과를 html 파일로 뿌려주고, 각 클래스, 메소드별로 수행시간이 얼마나 소요되었는지 확인할 수 있었습니다.

이 파일을 기반으로 수행시간을 지연시키는 요소들을 파악하였습니다.

2. 수행시간 지연 클래스/메소드 분석

클래스 네임은 임의로 변경해 공유드리겠습니다.

  • API
    • ATest (20.059s)
      • 발생 원인: InetAddress.getLocalHost().getHostName() 호출 시 네트워크 호출로 인한 테스트 수행시간 지연 발생
      • 변경안: InetAddress.getLocalHost().getHostName()를 클래스로 래핑하여 사용 (기존에 사용하던 클래스로 이관)
      • 변경 후: (1.499s)
  • BATCH
    • BTest (20.801s)
      • 발생 원인: SMTPMessage의 saveChanges(); 호출시 세션을 업데이트하는 것으로 추정, 이 때 발생
      • 변경안: SMTPMessage의 saveChanges();가 발송 이전에 이뤄지도록 로직 순서를 변경, 통합테스트에서 실행되도록 수정
      • 변경 후: (1.499s)
    • CTest (1m50.78s)
      • 발생 원인: InetAddress.getLocalHost().getHostName() 호출 시 네트워크 호출로 인한 테스트 수행시간 지연 발생
      • 변경안: InetAddress.getLocalHost().getHostName()를 클래스로 래핑하여 사용
      • 변경 후: (4.348s)
    • DTest (11.962s)
      • 발생 원인: mock으로 주입했으나, 1200만건의 테스트를 수행하여 수행시간이 지연 발생
      • 변경안: IntegrationTest로 임시 이동 후 테스트 수정
      • 변경 후: 0s
    • ETest (3.662s)
      • 발생 원인: EmailConverter.mimeMessageToEmail에서 테스트 수행시간 지연 발생
      • 변경 후: 567ms
  • SMTP
    • FTest (10.516s)
      • 발생 원인: SMTPMessage 메세지 헤더 변경 과정에서 테스트 수행시간 지연 발생
      • 변경안: 로직 수정, 테스트코드 추가
      • 변경 후: (6.307s)
    • GTest (12.073s)
      • 발생 원인: InetAddress.getLocalHost().getHostName()에서 테스트 수행시간 지연 발생
      • 변경안: InetAddress.getLocalHost().getHostName()를 클래스로 래핑하여 사용
      • 변경 후: (567ms , 1.686s)

3. 서비스 로직 및 테스트 코드 수정

  1. 서비스 로직 내에서 SRP (단일 책임 원칙)을 위배하는 부분의 메소드를 분리
  2. 네트워크 통신을 하는 InetAddress.getLocalHost().getHostName() 메소드를 별도의 클래스 HostResolver로 분리하여 관리하도록 개선
  3. 변경 영향도가 큰 테스트 코드는 단위테스트여도 일단 IntegrationTest로 분리하고 추후 변경 진행

개선 후

프로젝트테스트 수행시간 (개선 전)테스트 수행시간 (개선 후)
api1m 4s30s
web17s16s
batch2m 43s42s
smtp31s20s
util10s10s
common9s9s

결론

이번 테스트 지연 원인은 크게 다음과 같습니다.

  1. static 메소드로 호출되던 InetAddress.getLocalHost().getHostName() 가 네트워크 통신을 해 한 메소드마다 5초 소요

  2. 메소드의 분리가 정상적으로 진행되지 않아 불필요한 테스트 수행시간이 포함되었습니다.

  3. 통합테스트로 진행할만한, 혹은 더 내부에서 진행해야 될만한 테스트들이 하나로 합쳐져서 통합테스트 같은 단위테스트가 진행되었습니다.

다음 문제점을 개선하기 위해 아래 내용을 수정하였습니다.

  1. 서비스 로직 내에서 SRP (단일 책임 원칙)을 위배하는 부분의 메소드를 분리
  2. 네트워크 통신을 하는 InetAddress.getLocalHost().getHostName() 메소드를 별도의 클래스 HostResolver로 분리하여 관리하도록 개선
  3. 변경 영향도가 큰 테스트 코드는 단위테스트여도 일단 IntegrationTest로 분리하고 추후 변경 진행

개선할 점

회사 프로젝트의 단위/통합테스트를 분리해본 결과 5:5~6:4 정도의 비율로 테스트 코드가 작성된 것을 확인할 수 있었습니다.

로컬 환경에서 테스트 코드를 동작할 경우 기존에 비해 1/2의 테스트만 동작하기 때문에 기능 수정간 영향도 파악이 정상적으로 될까 싶은 생각이 들어 단위 테스트를 추가하는 공수를 조금씩이라도 들여야 될 것 같다는 생각이 들어 천천히 추가해보려고 합니다.

또한 아무래도 통합 테스트가 로컬 환경이 아닌 Jenkins 에서만 진행되다보니 발생할 수 있는 문제점으로는 충분하지 않은 단위 테스트로 인한 문제 발생이 가장 걱정이 됩니다.

이러한 걱정을 조금이나마 덜기 위해선 개발자의 테스트 작성이 좀 더 세분화되어야 된다고 생각하는데 단위 테스트와 통합 테스트를 작성하는 범위/기준을 개인에게 맡겨도 될지에 대한 의문이 들었습니다. 팀 컨벤션이 있으면 좋긴 하겠지만 테스트 작성간 좀 더 끔찍해질것 같습니다.

일단 저는 다음과 같이 진행하고자 합니다.

  1. 단위 테스트는 가능한 한 모든 경우의 수를 테스트할 수 있도록 만든다.
    • 이렇게 하기 위해선 기능 단위가 매우 작아져야 된다고 생각하는데 한 번 진행해보려 합니다..
  2. 기능 추가 개발건의 경우 통합테스트는 꼭 추가한다.
    • 기능 개선건, 수정건의 경우엔 기존 테스트 코드로 충분?하다고 판단해 다음의 경우에는 깨지는 코드에 한하여 수정을 진행합니다.
    • 기능 개발건은 필수적으로 통합테스트를 추가합니다.
  3. 기존 테스트 코드에서 부족한 단위테스트를 보완합니다.
  • 시간 날 때마다 최대한... 작성할 예정입니다.

읽어주셔서 감사합니다.

느낀점

보통 테스트 코드를 작성할 때 테스트 수행시간에 대한 고민을 전혀 하지 않았었는데 테스트가 하나하나 쌓이다보니 테스트 수행간 발생하는 cost가 증가해 업무에 지장이 올 정도로 커졌던 것 같습니다.

테스트 작성간에도 테스트 수행시간을 적절히 고려해 코드를 구현해야겠다는 생각이 들었고 수시로 테스트 수행시간을 확인해가며 서비스 로직 및 테스트 코드를 개선해야겠다는 생각을 가지게 되었습니다.

profile
Gelog 나쁜 것만 드려요~

1개의 댓글

comment-user-thumbnail
2023년 8월 5일

유익한 글이었습니다.

답글 달기