모든 글

테스트 편의 때문에 운영 코드의 의미를 바꾸지 마라

운영 행위가 더 명확해지거나 레거시에 테스트 경계가 필요할 때만 코드를 바꾸고, 테스트만 쓰는 값과 메서드를 억지로 노출하지 않는 기준입니다.

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

테스트를 쓰다 보면 운영 코드가 사용하기 어렵다는 사실이 드러난다. 그렇다고 테스트를 편하게 만드는 모든 변경이 운영 설계를 개선하는 것은 아니다. 변경이 정당한 행위를 표현하는지, 테스트만 소비하는 인위적인 표면을 만드는지 구분해야 한다.

검증기가 테스트에서 확인할 값을 내놓기 위해서만 정수를 반환한다면 메서드의 의미가 비틀렸을 가능성이 높다. 호출자도 관심을 가지는 예외나 결과, 상호작용처럼 실제 검증 행위를 테스트하는 편이 낫다.

반환값이 오퍼레이션에 속해야 한다

반환값이 없던 업데이터가 자신이 갱신한 객체의 ID를 돌려주는 것은 타당할 수 있다. 운영 호출자에게도 쓸모 있고 메서드의 의미가 더 분명해진다면 테스트만을 위한 변경이 아니다.

내부 값을 노출하려고 UpdateResult를 억지로 만든다면 다르다. 어떤 운영 호출자도 그 결과를 읽지 않는다면 타입은 행위보다 관찰 편의를 위해 존재한다. 테스트 편의가 서비스 계약 안으로 새어 들어온 것이다.

접근 범위도 같은 기준을 쓴다. 프라이빗 메서드를 테스트에서 직접 호출하려고 퍼블릭으로 바꾸면 운영상 필요 없는 API가 늘어난다. 공개 행위를 통해 검증하는 편이 낫다. 반환값보다 협력 객체와의 상호작용이 관심사라면 목을 활용하는 방법도 있다.

테스트 가능성을 위한 리팩터링 자체를 금지하는 것은 아니다. 테스트의 압력이 너무 많은 책임을 가진 클래스나 숨겨진 의존성을 보여줄 수 있다. 운영 설계를 더 정직하게 고친 뒤 테스트가 그 이익을 함께 얻도록 해야 한다.

심한 레거시에서는 계산이 달라진다

새 시스템과 오래된 무테스트 시스템에는 같은 선택지가 없다. 레거시가 필드 주입으로 가득하고 순환 의존성을 가지며 한 메서드에 수천 줄이 들어 있을 수 있다. 경계를 만들지 않으면 필요한 변경조차 안전하게 확인할 수 없다.

이 경우 통제력을 얻기 위한 운영 코드 수정은 타당할 수 있다. 가능하면 생성자를 추가한다. 생성자 주입으로 바꾸는 순간 기존 순환 의존성이 드러난다면, 코드를 풀어내는 동안 임시 세터 주입으로 테스트에서 협력 객체를 넣을 수도 있다. 거대한 메서드는 로직을 바꾸지 않은 채 구간별로 추출하고, 추출한 행위를 특성 테스트로 고정한다.

다른 방법이 없어 안전망을 만들기 위해 임시 결과 객체나 접근 범위 변경을 쓸 수도 있다. 단, 팀이 이를 비계로 인식해야 한다. 레거시 영역을 장악한 뒤 제거하거나 더 나은 구조로 고쳐야지, 우회책을 새 설계 표준으로 남기면 안 된다.

레거시의 수명만큼 에너지를 쓴다

교체될 레거시도 있고 큰 정리 계획 없이 계속 돌아가야 하는 레거시도 있다. 어느 쪽이든 다음 작은 수정의 장애를 줄이는 소박한 특성 테스트가 아름다운 재설계보다 가치 있을 수 있다.

조건에 따라 기준을 나눈다.

  • 새 코드나 건강한 코드에는 테스트 전용 결과물을 운영 계약에 넣지 않는다.
  • 테스트하기 어렵다는 사실이 실제 설계 결함을 보여주면 운영 이유로 결함을 고친다.
  • 심한 레거시에는 필요한 변경을 관찰할 수 있는 최소 경계를 만든다.
  • 임시 타협을 표시하고 목표 구조와 혼동하지 않는다.

운영 코드는 운영 행위를 설명해야 하고 테스트는 그 행위를 검증해야 한다. 레거시가 어떤 검증도 막는다면 통제력을 되찾을 만큼은 조심스럽게 고칠 수 있다. 다만 그 예외는 레거시 회복 작업 안에 묶어 둔다.