모든 글

첫 테스트는 서툴러도 된다, 나중에 리팩터링하라

지키고 싶은 행위를 검증하는 테스트부터 만들고, 실행에 필요한 만큼 목을 쓰며, 반복과 리뷰를 통해 테스트 설계를 개선하는 방법입니다.

출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다. gpt-5.6-sol 모델을 사용해 생성·편집했습니다.

처음부터 좋은 테스트를 쓰고 싶어서 첫 테스트를 미루는 사람이 많다. 나도 처음부터 테스트에 익숙한 개발자는 아니었다. 빠져나오는 방법은 테스트를 첫 시도부터 완벽해야 하는 특별한 결과물로 보지 않는 것이었다.

지키고 싶은 행위를 하나 고르고 테스트를 만든 뒤 일단 돌아가게 한다. 다시 나오면 안 되는 버그가 있다면 그 버그를 재현하고 올바른 결과를 검증한다. 첫 버전에 설정 코드가 많고 픽스처가 서툴러도 쓸모는 있다.

하나의 구체적인 문제를 검증한다

‘테스트를 배운다’는 목표는 너무 넓다. ‘주문을 저장하면 기대한 주문 키가 만들어지는지 확인한다’는 목표는 작업할 수 있다. 어떤 객체를 호출하고, 무슨 데이터를 준비하며, 어느 결과를 봐야 하는지가 나온다.

의존성이 많은 클래스는 시작을 어렵게 만든다. 아직 익숙해지는 단계라면 원하는 행위까지 도달하는 데 필요한 협력 객체를 목으로 만들어도 괜찮다. 직접 설정하고 테스트를 돌린 다음 무엇 때문에 어려웠는지 살핀다. 어떤 의존성은 목이 필요 없을 수 있다. 반복되는 설정은 빌더나 픽스처로 모을 수 있다. 격리하기 지나치게 힘든 클래스라면 운영 코드 설계의 문제를 알려주는 신호일 수도 있다.

이런 개선점은 테스트가 존재한 뒤에 더 잘 보인다. 이상적인 목 경계를 미리 찾으려고 기다리면 테스트 자체가 생기지 않는 경우가 많다.

테스트 코드도 계속 바뀌는 코드다

한 번 통과한 테스트가 영원히 끝난 것은 아니다. 운영 코드처럼 리뷰하고, 이름을 고치고, 구조를 정리하고, 리팩터링해야 한다. 첫 버전은 관심사를 검증하고, 이후 버전은 그 검증을 더 읽기 쉽고 관리하기 싸게 만든다.

학습할 때는 다음 흐름이 실용적이다.

  1. 지키려는 행위나 회귀 버그를 말로 정한다.
  2. 테스트가 실행될 만큼 설정한다.
  3. 올바른 이유로 통과하는지 확인한다.
  4. 불필요한 목과 반복 설정을 줄인다.
  5. 가치가 확인된 테스트를 팀의 정상 테스트 묶음에 넣는다.

탐색 중인 로컬 테스트를 잠시 CI 실행 대상에서 제외할 수도 있다. 안정적이고 유용해지면 평소 신뢰하는 테스트 묶음으로 옮겨야 한다. 연습을 독려하기 위한 구분이 중요한 검증을 보이지 않게 방치하는 장소가 되어서는 안 된다.

테스트를 먼저 썼다고 곧바로 TDD는 아니다

버그나 행위를 고치기 전에 테스트부터 만든 사실만으로 완전한 테스트 주도 개발이 되는 것은 아니다. TDD는 반복해서 익히는 개발 사이클이다. 완벽한 ‘TDD 환경’을 먼저 구성한다고 생기는 것이 아니라 계속 시도하면서 익숙해진다.

목킹도 같다. 목 설정은 기법이지 TDD의 정의가 아니다. 협력 객체의 동작을 통제해야 할 때 쓰고, 실제 동작을 그대로 두는 편이 낫다면 억지로 바꾸지 않는다.

처음 세울 기준은 소박해도 된다. 알 가치가 있는 행위를 테스트가 보여주고, 그 행위가 깨졌을 때 실패해야 한다. 그다음 판단력은 많이 쓰고 고치는 과정에서 생긴다.

서툰 초기 테스트를 건너뛰는 지름길은 없다. 같은 버그가 한 번 더 생기지 않게 만드는 테스트부터 쓰자. 그 테스트가 모르는 부분을 드러내게 두고, 근거가 생기면 테스트와 주변 운영 설계를 함께 개선하면 된다.