저는 실무에서 테스트 코드를 작성하고있고, 안정적인 유지보수를 위해서는 당연히 해야한다고 생각합니다.
하지만 스스로를 되돌아보니 테스트란 뭔지, 왜 해야하는지를 그저 어렴풋이만 알고있다는걸 깨달았습니다.
그래서 테스트의 본질에 대해 고민해보고, 테스트의 장점과 그게 정말인지에 대해 생각해봤습니다.
학습해 나가는 중에 정리한 내용이라 혹여나 잘못된 내용이 있다면, 피드백 주시면 감사하겠습니다.
테스트는 뭘까?
여러 글과 책에서 말하는 테스트의 정의를 제 나름대로 요약해보면 다음과 같습니다.
시스템등이 예상한대로 동작하는지 확인하는 행위
이러한 맥락에서 볼때 세상의 모든 개발자들은 테스트를 하고있습니다.
- 시스템 전체를 로드해서 사용하며 확인
- 시스템의 일부분을 로드해서 사용하며 확인
- 테스트 코드를 통해 확인
많은 개발자들이 알고 실천하고 있다시피, 시스템을 로드해서 사용해보며 확인하는건 번거로운 일입니다.
그래서 ‘테스트 코드’를 통해 더 효율적으로 동작을 확인합니다.
테스트 코드는 왜 작성할까?
앞서 말했듯 번거로운 테스트 방식을 대체하고, 더 효율적으로 동작을 확인하기 위해 테스트 코드를 작성하는데요.
결국 우리가 테스트를 작성하는 이유를 정리해보면 다음과 같습니다.
- 기능이 예상한대로 동작하는지 확인할 수 있음
- 리팩토링이 수월해짐
그런데, 정말 위와 같은 효과가 있는건지 의문이 들었습니다.
기능이 예상한대로 동작하는지 확인할 수 있음
테스트 코드는 일반적으로 다음과 같은 흐름으로 작성됩니다.
- 이런
조건
에서, 이행위
를 했을때,결과
는 이렇게 나와야 한다.
이렇게 조건들을 정의하고 그에 따라 나와야하는 결과를 확인하며, 예상한 대로 동작하는지 확인할 수 있는데요.
하지만 과연 이렇게 확신할 수 있을까요?
만약, 테스트 코드에 결함이 있다면?
테스트 코드에 결함이 있다면, 예상한대로 동작하지 않음에도 인지하지 못하고 넘어갈 수 있습니다.
이러면 테스트 코드가 있느니만 못하게 되는데요.
(테스트 코드를 테스트하는 테스트 코드를 짤 수 도 없는 노릇이고 말이죠..)
이를 시스템 상으로 방지하는건 힘들겠지만, 대신 실천할 수 있는 방안이 있습니다.
- 요구사항에 대해 정확히 이해한 상태에서 테스트 코드를 작성
- 테스트 코드를 명확하고, 간결하게 작성
- Given-When-Then 패턴, 혹은 AAA 패턴 등을 이용하는것도 하나의 방법
- 코드 리뷰 혹은 페어 프로그래밍을 이용하는 방법
이런 실천 방안과 더불어 가장 중요한 점은, 테스트 코드도 지속적인 리팩토링 대상이라는 점 입니다.
테스트 코드가 우리를 도와주는지, 발목을 잡는지 항상 인식하고 개선하려는 노력이 필요합니다.
리팩토링이 수월해짐
이렇게 기능이 예상한대로 동작하는 테스트 코드들이 만들어지면, 이를 바탕으로 로직을 리팩토링할 수 있죠.
변경 후에도 원래 제공하던 기능들을 동일하게 제공하는게 확인되면, 변경에 대한 심리적 안정감을 느낄 수 있습니다.
그런데 만약, 구현 로직이 바뀌어 많은 테스트 코드가 실패하게되어 수정해야 한다면?
예를들어 Service 의 특정 기능은 Repository 의 findAll(), saveAll() 을 사용했다고 가정하겠습니다.
그래서 테스트 코드는 finalAll(), saveAll() 메서드를 mocking 하여 작성했는데요.
만약 리팩토링을 하며 findBy(), save() 만 사용하도록 변경했을때, 관련 테스트하는 코드는 모두 깨지게 됩니다.
그러면 이를 모두 다 수정해주어야 하는데, 이런 상황은 과연 리팩토링이 수월한게 맞을까요?
아직 저도 학습해 나가는 과정에라 이에 대한 명확안 답은 찾지 못했지만, 현재로서 내린 결론은 다음과 같습니다.
구현을 일부 수정했는데 대부분의 테스트가 깨진다면,
냄새나는 코드가 있을 가능성
이 높다.
위와 같이 Service 가 Repository 의 특정 기능(findAll, saveAll)에 너무 의존하고 있는데요.
이 두 기능이 항상 함께 동작하는 기능이라면 추상화하는건 어떨까 생각됩니다.
Service 에서는 추상화한 기능만 사용하고, 그 구현체에서 finaAll, saveAll 을 사용하여 기능을 구현합니다.
테스트는 안정감을 줄 수 있는가?
이에 대한 저의 대답은 '(조건부로) 그렇다' 입니다.
위에서 우리는 테스트 코드를 작성하면서 발생할 수 있는 불안들을 확인할 수 있었습니다.
- 테스트 코드에 결함이 있다면?
- 구현로직이 바뀌는 바람에 테스트가 모두 깨진다면?
만약 이런 부분들을 타개할 방법을 생각하지 못한채로 테스트를 작성하고 있다면, 정말 이 테스트들이 안정감을 주고 있는걸까?
라는 부분을 다시한번 생각해보면 어떨까 싶습니다.
그래서 불안감이 생길만한 부분을 사전에 잘 이해한 상태에서, 작성한다면 테스트를 통해 어느정도 안정감을 줄 수 있다고 생각합니다.
하지만, 테스트는 만능이 아닌 만큼 계속해서 의문을 가지고 개선해 나가는 노력이 필요할 것 같습니다.
번외로 저는 아직까지 저는 하나의 의문이 해결이 되지 않은 상태인데요.
만약, 테스트 대상이 의존하는 모듈에서 문제를 발생시킨다면?
예를 들어, Service 를 테스트할때 Repository 는 mocking 하여 테스트 했고, 성공함.
하지만, 실제 환경에서 Repository 의 구현에 문제가 있어 장애가 발생함.
이를 방지하기 위해선 어떻게 해야햘까?
그러면 단위 테스트와 더불어 통합 테스트도 해야하나?
(한 대상을 검증하기 위해 테스를 중복으로 하는건 비효율 적으로 보이는데..)
만약 H2 DB 붙여서 했는데, 실서비스 환경 MySQL에서는 실패한다면?
(TestContainer 같은 서비스를 이용해서 MySQL 컨테이너를 띄워서 해야하는게 맞나..?)
이런 생각이 들며, 저는 이런 상황에서 어떻게 테스트로 안정감을 줄 수 있을지 고민되는데요.
좋은 의견 있으신 분들은 댓글로 남겨주시면 감사하겠습니다.