개발자 이진우
단위 테스트

단위 테스트

블라디미르 코리코프

링크 ↗

이 책을 읽게 된 계기

대면 코드리뷰를 하던 중이었다. 어떤 코드에 대역을 쓰는 게 좋을지, 실물 객체를 쓰는 게 좋을지 이야기가 오갔다. 그러다 한 팀원이 우리 팀의 테스트 코드를 어떻게 생각하는지 물었고, 나는 제대로 답하지 못했다. 그 순간 깨달았다. 나는 우리 팀의 테스트 코드 구조가 정말 좋은 것인지, 아니면 단지 그 방식에 익숙해진 것뿐인지조차 구분하지 못하고 있다는 것이다.

고민을 이어가던 중 같은 팀원에게 이 책을 추천받았다. 마침 풀고 싶은 질문도 분명했다. 어떤 경우에 대역을 쓰고 어떤 경우에 실물을 써야 하는가, 그리고 "내가 생각하는 좋은 테스트 코드란 무엇인가"에 스스로 확답하고 싶었다.

이 책이 말하는 좋은 단위 테스트

책은 좋은 단위 테스트를 네 가지 요소로 설명한다. 회귀 방지, 리팩터링 내성, 빠른 피드백, 유지보수성이다. 이 중 나에게 가장 크게 남은 것은 회귀 방지와 리팩터링 내성이었다. 테스트가 결국 지켜줘야 하는 것은 "코드를 바꿔도 기존 동작이 깨지지 않는다"는 확신이고, 그 확신을 떠받치는 두 축이 바로 이 둘이라고 느꼈다.

무엇을 테스트할 것인가에 대한 관점도 새로웠다. 책은 테스트의 단위를 메서드가 아니라 행위로 봐야 한다고 말한다. 클래스 내부의 구현 세부가 아니라 밖으로 드러나는 동작을 검증하라는 것이고, private 메서드를 직접 테스트하지 말라는 조언도 같은 맥락이다.

계기에서 품었던 대역과 실물의 선택 문제에도 책은 명확한 기준을 준다. 우리가 제어할 수 없는 외부 의존성에는 대역을 쓰고, 그 외에는 실물을 쓰는 것이다. 오래 답을 내리지 못하던 질문이 한 문장으로 정리되는 느낌이었다.

런던파와 고전파에 대해서는, 개인적으로 어느 한쪽을 지지하기보다 상황에 맞게 적절히 선택하는 편이 낫다고 생각한다. 다만 어느 쪽을 택하든 내가 우선에 두는 기준은 분명하다. 나는 테스트의 가장 큰 가치가 빠른 피드백보다 회귀 방지와 리팩터링 내성에 있다고 본다. 게다가 최근에는 하드웨어와 기술이 좋아지면서 테스트를 위해 실물 객체(예를 들어 DB)를 실제로 띄우는 부담도 크게 줄었다. 그만큼 실물을 활용해 더 견고한 테스트를 만들 여지도 넓어졌다고 생각한다.

읽고 나서 바뀐 생각

책을 읽고 나서 가장 먼저 한 일은 우리 팀의 테스트 코드를 다시 들여다보는 것이었다. 어떤 영역을 테스트가 커버하고 있고, 어떤 영역은 비어 있는지 이제는 나름의 기준을 가지고 구분해볼 수 있었다. 그 과정에서 새삼 느낀 것은, 우리 팀은 내가 합류하기 전부터 오랜 고민을 거쳐 상당히 확고한 기준으로 테스트를 작성해오고 있었다는 점이다. 이게 정말 좋은 구조인지, 아니면 익숙한 것뿐인지. 계기에서 답하지 못했던 이 질문에 이제는 나름의 판단이 선다.

관점 자체도 달라졌다. 이전 사이드 프로젝트에서는 소나큐브로 커버리지를 채우는 식으로 테스트를 작성한 적이 있다. 그때는 숫자가 올라가는 걸 보며 테스트를 잘 짜고 있다고 여겼다. 하지만 지금 돌아보면 커버리지는 테스트를 잘 작성하고 있다고 착각하기 딱 좋은 지표였다. 커버리지를 채우는 것과, 회귀를 막고 리팩터링을 견디는 테스트를 만드는 것은 전혀 다른 이야기였다. 코드가 실행되었다는 사실과, 그 동작이 의미 있게 검증되었다는 사실 사이에는 생각보다 큰 간극이 있었다.

무엇보다 좋았던 것은 테스트 코드에 대한 나만의 주관이 생겼다는 점이다. 예전에는 "테스트는 있어야 하니까 짠다"에 가까웠다면, 이제는 무엇을 왜 테스트하는지 스스로 설명할 수 있게 됐다. 덕분에 앞으로 업무에서 팀원들과 한층 높은 수준의 대화를 나눌 수 있겠다는 기대도 생겼다. 그동안 일부 외부 연동 API에 대한 테스트 코드가 누락된 채로 있었는데, 이번 기회에 추가해보기도 하였다.

나에게 있어서 테스트 코드란?

나에게 테스트 코드란 명세다. 이 코드가 무엇을 보장하고 어떤 상황에서 어떻게 동작해야 하는지를, 문서가 아니라 실행 가능한 형태로 적어둔 명세라고 생각한다. 주석이나 문서는 코드와 함께 낡아가지만, 테스트는 명세가 어긋나는 순간 실패로 그 사실을 알려준다. 그래서 나는 테스트를 가장 믿을 수 있는 명세라고 본다.

이 명세는 미래의 나를 포함한 팀원들을 위한 것이다. 지금 내가 이해한 동작을 테스트로 남겨두면, 나중에 이 코드를 다시 만질 사람이 무엇을 지켜야 하는지 알 수 있고 그만큼 안심하고 바꿀 수 있다. 앞에서 가장 중요하다고 말한 회귀 방지와 리팩터링 내성도, 이런 명세가 있을 때 비로소 가능한 것이라고 생각한다.

테스트하기 어렵다면, 나는 그것을 테스트의 문제가 아니라 설계 이상의 신호로 볼 수도 있다. 강한 결합이나 숨은 의존성처럼, 코드가 테스트를 거부하는 데에는 대개 이유가 있다. 그런 의미에서 테스트는 명세이면서, 내 설계를 다시 돌아보게 하는 도구이기도 하다.