--- kind: CASE slug: test-names-that-assert-what-their-bodies-do-not title: 이름이 검사한다고 말하는 것을 본문이 검사하지 않는 테스트 넷 topic: what-a-gate-does-not-prove project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: case:test-names-that-assert-what-their-bodies-do-not evidenceCapturedOn: 2026-09-01 assets: - key: test-names-that-assert-what-their-bodies-do-not file: ../../../final/evidence/rendered/test-names-that-assert-what-their-bodies-do-not.svg evidence: - ../../../final/evidence/raw/test-names-that-assert-what-their-bodies-do-not.txt source: - 원본 분석 절은 final/document.md#a19-messaging-pulsar-experimental §17.3 · final/document.md#a19-messaging-nats-experimental §17.4 · final/document.md#a20-grpc-advanced-bootstrap §17.4 이다. --- # 이름이 검사한다고 말하는 것을 본문이 검사하지 않는 테스트 넷 네 테스트가 이름과 설명 메시지로 무엇을 붙든다고 말하는데 본문이 다른 것을 확인하거나 아무것도 배제하지 않는다. 어느 것도 잘못된 동작을 통과시키지는 않는다. 틀리는 것은 커버리지 지도다. ## 관계 - **빠뜨림이 통과가 되는 게이트는 게이트가 아니다** 같은 계열의 규칙이다. - **다중 타깃 검증을 확인한다는 테스트가 다른 가드에 걸려 통과했다** 같은 형태가 다른 리프에서 나타난 사례다. - **산문이 선언한 게이트는 빌드에 있는 게이트가 아니다** 이 사례가 그 규칙이 왜 오래 눈에 띄지 않는지를 설명한다. ## 문제 테스트 이름은 이 저장소에서 문장으로 쓰인다. 무엇을 붙드는지를 이름이 말하고, 설명 메시지가 왜 그것을 붙드는지를 말한다. 그 관행이 읽기를 빠르게 만든다. 빠른 읽기는 이름을 커버리지 지도로 쓰게 만든다. 어떤 규칙에 테스트가 있는지 확인할 때 본문 대신 이름을 센다. ## 결론 네 곳에서 이름과 본문이 갈린다. 첫째. 이름이 검증기가 키 공유 프로파일을 받아들인다고 말하는데 본문에 검증기가 없다. 프로파일 생성자만 부르고 예외가 나지 않는 것을 확인한다. 그 리프에서 검증기를 언급하는 유일한 테스트 이름이 이것이라, 이름만 읽으면 검증기에 커버리지가 있다고 읽힌다. 실제로 그 검증기는 저장소 전체에서 자기 선언 한 줄 말고 아무 데도 없다. 둘째. 이름이 소비자 팩토리 없이 만든 전송이 등록을 거절한다고 말한다. 그 거절은 기본 팩토리가 던지는 예외다. 본문은 등록 메서드에 널을 넘겨 첫 줄의 널 검사에 걸린다. 단언하는 예외 타입도 그 널 검사의 것이다. 겨냥한 거절 코드는 저장소에서 한 번도 실행되지 않는다. 셋째. 설명 메시지가 모든 결과가 영으로 보고되던 회귀를 막는다고 적는다. 단언은 경과 시간이 영 이상이라는 것이다. 경과 시간은 시작 시점에서 잰 값이라 음수가 될 수 없으므로 이 단언은 구현이 무엇을 하든 통과한다. 영을 배제하려던 단언이 영을 통과시킨다. 넷째. 이름이 상위 등급이 되는 데 더 긴 담금이 필요하다고 말한다. 본문이 실제로 평가하는 전이는 철회 방향이다. 겨냥한 등급이 열거형에 없어서 그것을 밟을 방법이 없었고, 남은 값 중 하나를 골라야 했던 흔적이다. 넷 다 통과한다. 그리고 넷 다 그 아래에 진짜 공백을 하나씩 두고 있다. 첫째와 둘째는 호출자 없는 검증기와 실행되지 않는 거절 경로, 셋째는 측정되지 않는 경과 시간, 넷째는 존재하지 않는 등급을 위해 쓰인 임계값이다. 이름이 커버리지를 주장했기 때문에 그 공백들이 오래 보이지 않았다. ## 검증 환경 OpenJDK : 21.0.12 Gradle : 9.0.0 확인 방식 : 테스트 본문과 이름 및 설명 메시지 대조, 그리고 단언 의미론 확인 소스 수정 : x ## 재현 조건 1. 각 테스트의 이름과 설명 메시지가 무엇을 주장하는지 적는다. 2. 본문이 실제로 만드는 객체와 부르는 메서드를 나열한다. 3. 단언이 배제하는 값의 집합을 구한다. 4. 이름이 지목한 코드 경로에 도달하는지 확인한다. ## 본문 네 형태가 같은 결과를 낳는다. ## PulsarProfile 참조 위치 :::evidence key="test-names-that-assert-what-their-bodies-do-not" alt="코드베이스에서 PulsarProfile 를 검색한 출력 5줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="PulsarProfile 코드베이스 검색 — 5줄 · exit 0" zoom="true" ::: ## 넷이 각각 놓치는 것 1. 이름이 `theValidatorAccepts…` 인데 본문에 검증기가 없다 — `PulsarProfile` 생성자만 부른다. 그 리프에서 검증기를 언급하는 유일한 테스트 이름이라, 이름만 읽으면 커버리지가 있다고 읽힌다. 2. 이름이 "소비자 팩토리 없이 만든 전송이 등록을 거절한다" 인데 본문은 `register(null)` 을 불러 첫 줄의 널 검사에 걸린다 — 겨냥한 `PULSAR_CONSUMER_NOT_CONFIGURED` 는 한 번도 실행되지 않는다. 3. `as()` 가 "모든 결과가 `Duration.ZERO` 였다" 는 회귀를 막는다고 적는데 단언이 `isGreaterThanOrEqualTo(Duration.ZERO)` 라 `Duration.ZERO` 도 통과한다 — 구현이 무엇을 하든 참이다. 4. 이름이 "Stable default 가 되는 데 더 긴 담금이 필요하다" 인데 실제로 평가하는 전이는 `ADVANCED_STABLE → DISABLED`(철회)다. ## 틀리는 것은 커버리지 지도다 어느 것도 잘못된 동작을 통과시키지는 않는다. 그래서 그 아래의 진짜 공백이 오래 눈에 띄지 않았다. ## 확인하지 못한 것 테스트를 실행하지 않았다. 판정은 단언 의미론과 호출 경로에 대한 것이다.