테스트가 통과했는데, 고치려던 동작은 실행됐을까

두 줄의 토큰 중 반복 토큰 하나가 검사 틀 밖에 남아 있고 돋보기가 놓인 개념 일러스트

같은 함수를 두 번 호출했는데 결과가 하나만 남았다. 중복을 막는 기능을 시험했다면 통과라고 쓰고 싶어진다. 하지만 그 하나를 남긴 장치가 방금 고친 코드인지는 아직 모른다. 더 아래에 있는 검증이나 저장소 제약이 두 번째 결과를 막았을 수도 있다.

Dhruv Jani의 DEV 배지 문제 분석기는 2026년 10월 24일 공개됐다. 저자는 첫 테스트가 기존 코드에서도 통과했던 이유를 돌아본다. 여기서는 특정 서비스의 수정 완료를 재검증하는 대신, 그 질문을 가상의 알림 시스템에 옮겨보려 한다.

최종 개수로는 보이지 않는 실행

가상의 함수는 같은 작업에 알림을 한 번만 만드는 역할을 맡는다. 이미 알림이 있다면 새로 만들지 않아야 한다. 테스트는 함수를 두 번 호출한 뒤 저장된 알림이 한 개인지 확인한다. 그런데 저장 단계에도 같은 작업의 알림을 중복 저장하지 못하게 하는 별도 규칙이 있다고 하자.

함수에서 기존 알림을 확인하는 코드를 빼도 최종 개수는 하나일 수 있다. 두 번 만들려고 했지만 두 번째 저장이 거절되기 때문이다. 최종 상태만 보면 기대와 같다. 그러나 함수가 중복 작업을 시도하지 않아야 한다는 요구는 지켜지지 않았다. 어떤 조건을 시험하는지에 따라 이 차이가 중요해진다.

그렇다고 저장소의 중복 방어가 나쁜 것은 아니다. 여러 계층의 방어는 다른 실패를 막을 수 있다. 문제는 다른 계층이 막아준 결과를 현재 계층의 동작 증거로 삼는 데 있다. 최종 결과의 안전성과 함수를 고친 효과를 같은 검사 하나로 모두 증명하려 하면 빈칸이 생길 수 있다.

알림을 외부로 보내는 과정까지 있다고 가정하면 차이는 더 커진다. 저장은 한 번만 되었어도 외부 발송은 두 번 시도됐을 수 있다. 이 글에서는 실제 알림을 보내지 않았다. 예시는 부작용이 어디에 생기는지에 따라 최종 행 개수만으로 충분하지 않을 수 있음을 설명하기 위한 것이다.

검사하려는 약속을 한 문장으로 나누기

먼저 요구를 나눠 적어볼 수 있다. 저장된 알림이 중복되지 않는다는 약속, 불필요한 생성 시도를 하지 않는다는 약속, 외부 발송이 반복되지 않는다는 약속이다. 이들은 관련되어 있지만 같은 문장은 아니다. 제품이 실제로 요구하는 범위를 정한 뒤 그에 맞는 관찰 대상을 골라야 한다.

첫 약속은 저장 결과로 확인할 수 있다. 두 번째는 생성 시도가 일어났는지 볼 수 있는 관찰이 필요할 수 있다. 세 번째는 실제 외부 시스템에 계속 발송하는 대신 안전한 시험 대역이나 기록을 통해 확인하는 방식을 설계할 수 있다. 특정 도구를 쓰라는 처방보다 무엇을 보아야 하는지가 먼저다.

시험 환경에서 다른 방어가 목표 동작을 가리고 있는지도 살핀다. 다만 이를 확인한다고 운영 환경의 제약을 지워서는 안 된다. 분리된 테스트의 준비 자료를 조정하는 것과 실제 서비스의 보호 장치를 제거하는 것은 전혀 다른 행동이다. 목표 동작을 드러내는 최소한의 조건만 시험 안에서 다룬다.

테스트가 내부 함수 이름에만 매달리면 리팩터링할 때 불필요하게 깨질 수 있다. 반대로 최종 상태만 보면 실행의 중요한 차이를 놓칠 수 있다. 어느 쪽을 택할지는 요구가 무엇인지에 달려 있다. 반복 시도가 비용이나 부작용을 만드는 시스템이라면 그 시도 자체도 관찰할 이유가 생긴다.

고치기 전에도 실패하는지 보기

회귀 테스트라면 수정 전 동작에서 실패하고 수정 후에 통과하는지 비교하는 방법이 도움이 된다. 단, 실패가 목표 문제 때문인지 확인해야 한다. 잘못된 준비 자료나 실행 환경 때문에 실패했다면 문제의 재현이라고 부르기 어렵다. 초록색과 빨간색 표시보다 실패한 이유를 읽는 시간이 필요하다.

가상의 알림 예제에서는 중복 확인을 수행하지 않는 옛 동작을 넣었을 때 생성 시도 관찰이 달라져야 한다. 최종 개수 검사만 계속 통과한다면 그 검사가 다루는 범위를 다시 적을 수 있다. 검사를 버려야 한다는 뜻은 아니다. 저장 중복을 막는 별도 요구를 확인하는 유효한 검사일 수 있다.

실패 조건이 드러난 뒤에는 검사의 설명도 좁히는 편이 좋다. 중복을 완벽하게 막는 테스트라는 이름보다 같은 작업을 두 번 처리해도 생성 시도가 반복되지 않는다는 설명이 무엇을 보았는지 알려준다. 동시 실행까지 다루지 않았다면 그 범위도 포함하지 않는다. 순차 호출 시험으로 경쟁 상태까지 확인했다고 말할 수는 없다.

원문의 반성은 테스트를 더 많이 쓰자는 주장만으로 읽기보다, 통과의 이유를 묻는 습관으로 가져올 만하다. 저자의 서비스 분석과 여기의 가상 알림 사례는 서로 다른 것이다. 이 글에는 실제 제품에서 이 방법을 적용해 오류가 줄었다는 수치가 없다.

변경의 검토 범위를 정할 때도 같은 질문을 이어갈 수 있다. 작은 수정에 큰 검사 묶음을 무조건 돌리는 대신, 수정의 전후를 구분하는 관찰이 포함되어 있는지 본다. 많은 검사가 통과했다는 사실과 필요한 동작을 보았다는 사실을 함께 확인하는 것이다.

다음에 테스트가 너무 쉽게 통과한다면 잠깐 멈춰볼 수 있다. 내가 고친 부분이 아무 일도 하지 않아도 이 검사는 통과할까. 그 질문에 답하면 지금의 초록색 표시가 무엇의 증거인지 조금 더 정확하게 설명할 수 있다.

공개 원문을 참고해 AI의 도움으로 구성·작성한 글입니다. 가상 예시는 실제 수행 결과와 구분했으며, 표지는 Google Gemini로 만든 개념 일러스트입니다.