Kotlin에서 테스트를 짜며 어려웠던 점들

문지은·2021년 11월 9일
1

이번에 Kotlin Spring Boot 프로젝트를 진행하면서 테스트 코드를 짤 때 정ㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇ말 많은 어려움을 느꼈다. 같은 실수를 반복하지 않기 위해서 여기에 정리를 해볼까 한다.
특히 이번에 느낀 가장 큰 점은 아예 새로운 걸 짜야 할 땐 Github에서 Best Practice 코드를 찾아본 다음에 하자! 많은 시간을 아낄 수 있다.

코틀린 test best practice
이번엔 위의 링크에서 많은 도움을 받았다.
처음부터 봤다면 참 행복했을텐데,,

Mock할 오브젝트에서 DI 방식

DI 방식은 크게 3가지를 들 수 있다.

  1. 필드 주입
@Service
class SignService{
    @Autowired
    private lateinit var jwtProvider: JwtProvider

    @Autowired
    private lateinit var signRepository: SignRepository
  1. 생성자 주입
@Service
class SignService(
    @Autowired
    val jwtProvider: JwtProvider,

    @Autowired
    val signRepository: SignRepository  
){
  1. setter 주입
@Service
class SignService{
    private var jwtProvider: JwtProvider
    private var signRepository: SignRepository
    
    @Autowired 
   fun SignService(
   	jwtProvider: JwtProvider,
	signRepository: SignRepository
   ) { 
      this.jwtProvider = jwtProvider
      this.signRepository = signRepository
   } 

현재 다니고 있는 회사에서는 setter 주입을 쓰고 있다.
일단 딱 봐도 제일 번거로운 방식은 생성자 주입이다. 새로운 빈을 주입할 때마다 생성자의 파라미터를 하나씩 늘려야 하기 때문에 이를 사용하는 코드도 모두 수정해야 한다. OCP를 위반한다고 볼 수 있다. 오히려 필드 주입, setter 주입은 런타임시에 의존 관계가 결정되기 때문에 느슨한 결합도로 인해서 유연한 구조를 가질 수 있다. 하지만 치명적인 단점이 있다.

주입이 필요한 객체가 생성되지 않아도 객체가 생성된다.

이로 인해서 발생하는 오류가 컴파일 시에는 발견이 되지 않지만 런타임시에 발견된다는 단점이다. 비슷한 맥락으로 순환 참조도 미리 발견할 수 있다.

순환 참조란?
서로가 서로의 객체를 호출하여 무한 루프 발생. 스택이 계속 쌓여서 StackOverflow 에러가 발생하게 된다.

위의 사실을 알게 되고 생성자 주입 방식을 사용했는데 여기서 테스트할 때 문제가 발생했다.
테스트 할 때마다 initialize되지 않아서 에러가 발생했고 여기에 jwtProvider와 signRepository 인스턴스를 생성해서 넘겨줘야 했다. 하지만 kotlin에서 롬복을 쓰는 경우는 흔치 않았기에 @NoArgsConstructor를 직접 구현해야 했고 이건 너무나도 번거로웠다... 결국 필드 주입 방식으로 변경하고 테스트 코드에서 해당 빈들을 autowired해서 해결할 수 있었다.

Spy

이번에 @Spy를 사용하면서 테스트가 한결 더 편안해졌다! 먼저 이 어노테이션이 무엇인지 한마디로 설명하자면 진짜 객체를 사용할 수 있게 도와주는 것이다. 그런데 왜 이름이 Spy일까? 가짜인 객체들도 숨어있기 때문이다. 그래서 mock설정을 해준 것만 mocking 할 수 있고 나머지 객체들은 실제 객체들을 사용할 수 있다.

@Spy
lateinit var jwtProvider: JwtProvider

@Mock
lateinit var signRepository: SignRepository
    
@Spy
@InjectMocks
lateinit var signService: SignService

위에 처럼 사용하면 signService를 실제로 사용할 수 있게 된다! 특히 나는 signService에 jwtProvider를 DI했기 때문에 해당 빈도 가져와야 했는데 이 때 @Spy를 사용해서 jwtProvider를 가져와서 편안하게 쓸 수 있었다.

lateinit var가 선언되어 있는 빈을 @Autowired하면 생성자 에러가 나는 것 같다. 내 추측으로는 호출될 때까지 초기화되지 않기 때문에 가져오지 못하는 것으로 추측된다. 이럴 땐 @Spy를 쓰자.

static method를 mock하지 말자

powerMockito라는 라이브러리를 사용하면 가능한 것 같기는 한데,, 해당 문서를 찾아보면서 해보다가 나는 결국 다른 방식을 선택했다. static method를 mock하려는 시도는 위험한 것 같다. 차라리 다른 방법을 찾아보는게 더 안전하고 빠르다.
나는 하나의 빈을 선언한 뒤에 해당 빈으로 static method를 감쌌다. 그리고 해당 빈을 mock해서 처리했다. 불필요한 클래스가 하나 더 늘었다고 생각할 수 있지만 해당 클래스를 통해서 테스트가 한층 편해졌기 때문에 불필요하다고 생각하지 않는다.

sql 제약 사항 수정 후 DB drop하자

계속 sql 에러가 나는 테스트가 있었다. 해당 오류가 발생하지 않도록 데이터 베이스 제약 사항을 수정했음에도 불구하고 계속 에러가 났다. 알고보니 이미 데이터베이스가 생성되었기 때문에 수정 사항이 반영되지 않은 것이었다.
결국 database를 drop하고 다시 create한 뒤 실행하자 테스트가 통과했다.


이번에 소스 코드 짜는 시간이 1이라면 5정도를 테스트 코드 작성에 쓴 것 같다. 그래도 초반에 정립해놓으면 나중에는 시간이 단축될 것이라는 생각과 테스트 코드의 중요성을 생각하며 코드를 작성했다. 테스트 정말 쉽지 않다...!!

profile
백엔드 개발자입니다.

0개의 댓글