본문 바로가기
Test

테스트 가능한 설계

by oncerun 2022. 12. 15.
반응형

최근 데이터베이스 설계부터 애플리케이션 아키텍처, 도메인 설계, API, API 문서화, 사용자 유즈 케이스, 배포 전략, 서버

설정, 서버 튜닝, JVM 옵션 등등 정말 A~Z까지 혼자 다 하려니 일이 미친 듯이 많았다. 

아직 Elasticsearch, kibana로 실제 데이터 분석 및 통계하고 로깅도 해야 하고,  스트레스 테스트도 남아있는 걸 보면 죽을 맛이긴 하다.  batch도 필요할 것 같은데, 공부해본 거 다 써먹는 중이다.

 

사실 요즘 거의 미친 듯이 테스트 케이스를 재작성 중이다.  이는 기존 TDD를 몰랐을 때와 공부해가는 과정에서 생각보다 TDD로 개발하는 과정 자체가 기존 방식보다 더 효율적이고 생산적이라는 생각이 들었고, 마치 DMZ 망처럼 안전장치가 생긴 것처럼 자신감을 더 가지게 된다는 것이다. 

 

그러던 중 다음과 같은 테스트 상황이 존재했는데 특정 부분이다.

 

관리자 용 웹 애플리케이션에서 앱에서 사용되는 특정 파일이 갱신되었을 때 서버에서 해당 파일을 S3 버킷에 업로드 후 그 파일에 대한 버전 관리를 위해 DB에 update or insert 하는 과정 후에 모바일 앱에 특정 응답을 내려줘야 하는데

특정 파일에 대한 최신 버전의 Timestamp 값을 주어야 모바일 앱에서 해당 요청을 통해 파일을 새로 다운로드하는 로직이 있다. 

 

로직이 긴데, 일단 테스트에서 중요한 점은 특정 파일 버전을 검증하는 과정이다. 

 

테스트 코드에서 이전 파일 버전 시간을 LocalDateTime.now()로 생성하고  새로운 버전의 파일도 LocalDateTime.now()로 생성해서 테스트하는데 순차적인 흐름으로 몇 초라도 느리기 때문에 isBefore() 값으로 비교해서 통과됐다고 생각했다.

 

그런데 전체 테스트를 돌리는 도중 LocalDateTime.now() 사용하는 테스트 코드에서 빨간 불이 들어왔다.

 

일단 스레드를 1초 재우는 걸로 입 막음을 하고 해당 코드에 대해 고민하고 있었다. 

 

그리고 이와 관련된 글을 읽고 정리한다. 

 

시간이나 임의 값 생성 기능 분리

 

테스트 대상이 시간이나 임의 값을 사용하면 테스트 시점에 따라 테스트 결과가 달라진다. 

 

이 경우 테스트 대상이 사용하는 시간이나 임의 값을 제공하는 기능을 별도로 분리해서 테스트 가능성을 높일 수 있다.

 

이는 구현 코드에서 시간과 같은 값을 대체 가능하지 못하게 코드를 작성하면 테스트가 매우 어려워 짐을 말한다. 

 

그래서 생각한 것은 실제 파일이 변경된 시점에 접근하는 함수를 만들어서 테스트 시 대역을 통해 쉽게 사용하려고 한다.

 

책을 보면서 느낀 건데 의존성이 관련된 테스트를 쉽게 하기 위해서는 스텁, 스파이, 모의 객체를 만들어서 테스트를 진행하는 것 같다.

 

그렇다면 TDD로 애플리케이션을 만들어가는 과정에서 자연스레 인터페이스가 많아지고 구현체도 많아지고 클래스나 구조가 복잡해지지 않을까?라는 걱정이 들긴 한다. 

 

여기서 더 고민해야하는 부분이 있는데 보통 JPA를 사용해 createDate, updateDate 같은 경우 추상화시킨 클래스를 상속받아사용 하는 auditing 기능을 사용한다. 

 

테스트 입장에서는 이는 테스트할 수 없는 구조이다. 이를 위해서 자식 엔티티는 부모의 updateDate 필드를 갱신할 수 있는 setter 메서드를 우선적으로 만들어 줘야 했다. 

 

이후 테스트 시 엔티티에 임의 시간값을 넣어서 테스트를 진행했다. 

 

여기서 한번 더 고민하게 되는점은 service layer를 단위 테스트로 구성할 때 실제 엔티티를 만들어서 테스트를 해야 하는 가에 대한 고민이다. 

 

단위 테스트는 통합 테스트와 달리 로직의 검증에 목표를 맞추어 빠르게 테스트하는 것이 장점인데, 왜 실제 사용되는 엔티티를 만들어야하는 가에 고민이 되었다. 

 

엔티티를 mock으로 만들어서 테스트를 할 수는 없을까?  실제 관련 자료를 검색해봐도 mock으로 생성하지 말고 실제 만들어서 테스트하라는 답변이다. 

 

DTO도 마찬가지이다.  우선 명확한 생산성을 가진 답을 찾기전에 엔티티와 DTO를 빌더 패턴으로 만들어 테스트 코드를 조금 더 깔끔하게 만들어 테스트를 진행하자. 

 

mock으로 만들면 테스트가 매우 힘든이유가 mock 객체의 필드 값은 유지되지 않는다. 

Mockito의 when으로 리턴 값으로 mock을 지정하여 테스트를 진행해도 해당 반환 값에는 빈 필드의 값만 있기 때문이다. 

 

이 때문에 실제 테스트에는 실제 DTO나 엔티티를 만들어서 사용했다. 더 좋은 방법을 찾아보기로 하자.

 

 

추가적으로 외부 라이브러리가 정적 메서드를 제공하는 경우 테스트 방안에 대해 알아보자.

 

외부 라이브러리가 정적 메서드를 제공한다면 이는 대역으로 대체하기가 어렵다. 이런 경우 외부 라이브러리와 연동하기 위한 타입을 따로 만든다. 

 

그리고 테스트 대상은 이렇게 분리한 타입을 사용하게 바꾼다. 테스트 대상 코드는 새로 분리한 타입을 사용함으로써 외부 연동이 필요한 기능을 쉽게 대역으로 변경 가능하다.  이는 final 클래스 거나 호출 메서드가 final이어서 대역으로 재정의 불가능한 경우에도 테스트가 가능하게 만든다.

 

즉 래핑하여 사용하라는 것인데, 이도 TDD로 테스트 -> 구현 시 무조건 외부 라이브러리는 래핑 하여 설계하라는 말 밖에 더 되지 않는가?

 

 

 

 

반응형

'Test' 카테고리의 다른 글

IntelliJ Test 목록  (0) 2023.07.04
테스트 범위와 종류  (0) 2022.12.15
Spy  (0) 2022.12.14
Junit 5  (0) 2022.12.12
테스트 코드에 작성 순서가 있다고?  (0) 2022.12.10

댓글