--- kind: CASE slug: a-test-that-passed-on-the-wrong-guard title: 다중 타깃 검증을 확인한다는 테스트가 다른 가드에 걸려 통과했다 topic: what-a-gate-does-not-prove project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: case:a-test-that-passed-on-the-wrong-guard evidenceCapturedOn: 2026-09-02 assets: - key: a-test-that-passed-on-the-wrong-guard file: ../../../final/evidence/rendered/a-test-that-passed-on-the-wrong-guard.svg evidence: - ../../../final/evidence/raw/a-test-that-passed-on-the-wrong-guard.txt source: - 분석 문서는 notification 어댑터 편 §9 다. 그 절이 같은 메서드가 전략을 두 번 본다는 것을 코드로 보이고, SINGLE 을 뺀 세 갈래를 도달 불가로 표시하며, 이름이 약속한 두 성질 중 하나만 그것도 두 번 확인된다고 적는다. 권고는 단언을 좁히는 것이다. --- # 다중 타깃 검증을 확인한다는 테스트가 다른 가드에 걸려 통과했다 경로 형태 검증기의 첫 가드가 SINGLE 이 아닌 전략을 전부 거부한다. 그 뒤 switch 의 나머지 세 갈래와 폴백 그래프 검사는 도달할 수 없고, 그것을 확인한다는 테스트는 앞 가드의 예외로 통과한다. ## 관계 - **빠뜨림이 통과가 되는 게이트는 게이트가 아니다** 통과의 이유가 의도한 것과 다른 형태다. - **계약 테스트는 어댑터가 실제로 돌리는 statement를 실행해야 한다** 계약 테스트가 어댑터의 실제 statement 를 돌려야 한다는 규칙이다. ## 문제 경로 형태 검증기 안에서 전략을 보는 자리가 한 메서드에 둘이다. 첫 번째는 가드다. 전략이 SINGLE 이 아니면 초기 카탈로그는 SINGLE 만 지원한다는 예외를 던진다. 두 번째는 switch 다. 네 갈래를 갖고, 그 뒤에 폴백 그래프 검사가 온다. ## 결론 첫 가드를 통과한 경로는 전략이 SINGLE 뿐이다. switch 의 나머지 세 갈래 — 팬아웃, 순서 폴백, 기본 — 에 들어갈 값이 없다. SINGLE 갈래가 폴백을 금지하므로 그 뒤 폴백 그래프 검사의 두 문구도 도달할 수 없다. 그것을 확인한다는 테스트가 있다. 이름은 폴백 범위와 순환이 거부된다고 말하고, 순환 경로와 팬아웃 경로를 각각 만든다. 단언 둘 다 타입을 확인한 뒤 메시지에서 전략이라는 단어를 찾는다. 두 경로 다 첫 가드에 걸린다. 그 문구에 그 단어가 있으므로 단언이 통과한다. 테스트가 실제로 확인하는 것은 SINGLE 이 아닌 전략이 거부된다는 성질 하나이고, 그것을 두 번 확인한다. 이름이 약속한 두 성질 중 하나는 확인되지 않는다. 첫 가드와 switch 사이에는 전략을 보지 않는 검사가 둘 있다. 타깃 수 일치와 증폭 한계다. 도달 불가를 만든 것은 위치가 아니라 switch 갈래 안에 들어갔다는 사실이다. ## 검증 환경 OpenJDK : 21.0.12 Gradle : 9.0.0 확인 방식 : 코드 경로와 테스트 단언 비교, 해당 테스트 실행 소스 수정 : x ## 재현 조건 1. 경로 형태 검증기의 첫 가드와 그 뒤 전략을 보지 않는 검사 둘을 읽는다. 2. switch 네 갈래와 그 뒤 폴백 그래프 검사가 던지는 두 문구를 확인한다. 3. 테스트가 만든 두 라우트의 전략과 범위 값을 읽는다. 4. 두 단언이 기대하는 문자열과 각 예외 문구를 대조한다. 5. 이 두 갈래를 태울 라우트를 만드는 곳을 저장소 전체에서 센다. 6. 그 테스트를 실행한다. ## 본문 경로 형태 검증기가 같은 메서드 안에서 전략을 두 번 본다. 앞은 가드이고 뒤는 switch 다. ## 앞의 가드가 뒤의 갈래들을 비운다 :::evidence key="a-test-that-passed-on-the-wrong-guard" alt="코드베이스에서 경로 형태 검증기의 첫 가드와 그 뒤 전략을 보지 않는 검사 둘, switch 네 갈래 전체와 기본 갈래의 문구, 폴백 그래프 검사가 던질 수 있는 두 문구의 위치, 순환을 거부한다는 테스트의 이름과 그 테스트가 만든 팬아웃 라우트와 순환 라우트의 전략과 범위 값과 두 단언, 이 두 분기를 태울 라우트를 만드는 곳의 저장소 전수 목록과 설정 파일의 비-SINGLE 전략 수, 그리고 그 테스트를 돌린 결과를 뽑은 출력 106줄. 순환 라우트의 범위가 그 갈래의 검사를 통과하는 값이라는 것이 그 출력에 보인다." caption="첫 가드와 전략을 보지 않는 검사 둘 · switch 네 갈래 · 폴백 그래프의 두 문구 · 두 라우트의 전략과 범위 · 전수 목록 · 5건 통과 — 106줄" zoom="true" ::: 첫 가드는 전략이 SINGLE 이 아니면 예외를 던진다. 문구는 초기 카탈로그가 SINGLE 만 지원한다는 것이다. 그 뒤 switch 에 네 갈래가 있다. SINGLE 은 타깃 하나와 폴백 없음을 요구하고, 팬아웃은 폴백 정의를 금지하고, 순서 폴백은 타깃 둘 이상과 활성화 범위를 요구하고, 기본 갈래는 알 수 없는 전략을 거부한다. 가드를 통과한 값은 SINGLE 뿐이다. 나머지 세 갈래에 들어갈 값이 없다. ## 폴백 그래프 검사도 그 뒤에 있다 switch 가 끝나면 폴백 그래프 검사를 부른다. 던질 수 있는 문구가 둘이다. 폴백이 없는 타깃을 가리킬 때와, 순환일 때다. SINGLE 갈래는 폴백이 하나라도 있으면 그 앞에서 거부한다. 그래서 이 검사에 도달하는 경로에는 폴백 간선이 없고, 두 문구 다 나올 수 없다. ## 테스트는 두 라우트를 만들고 같은 것을 묻는다 테스트 이름이 폴백 범위와 순환이 거부된다고 말한다. 하나는 팬아웃 라우트다. 다른 하나는 순서 폴백 라우트이고, 두 타깃이 서로를 폴백으로 가리키는 순환이다. 두 단언 모두 예외 타입과 메시지에 전략이라는 단어가 들어 있는지를 본다. 두 라우트 다 첫 가드에 걸리고, 그 문구에 그 단어가 있다. 그래서 이 테스트가 실제로 확인하는 것은 하나다. SINGLE 이 아닌 전략이 거부된다는 것. 그것을 두 번 확인한다. ## 가드가 사라지는 날 두 단언이 갈린다 순환 라우트는 타깃 둘과 폴백 활성화 하나를 갖는다. 순서 폴백 갈래의 범위 검사는 타깃 둘 미만이거나, 활성화가 하나 미만이거나, 활성화가 타깃 수보다 하나 이상 적지 않으면 거부한다. 이 값들은 셋 다 통과한다. 그러면 폴백 그래프 검사에 닿고 순환 문구를 던진다. 그 문구에는 전략이라는 단어가 없다. 이 단언은 그날 실패한다. 팬아웃 라우트는 다르다. 공급자 능력 카드의 전략이 라우트와 어긋나면 그것을 지목하는 예외가 나오는데, 그 문구에도 전략이라는 단어가 있다. 이 단언은 그날도 통과한다. 한쪽은 실패로 존재를 알리고, 다른 한쪽은 계속 조용하다. ## 이 갈래들을 태우는 라우트는 이 테스트뿐이다 저장소 전체에서 비 SINGLE 전략으로 라우트를 만드는 자바 코드를 찾으면 두 곳이다. 이 테스트의 두 라우트다. 같은 열거형을 쓰는 다른 파일들은 종류 정책 클래스와 그 테스트인데, 그 두 파일은 자기 클래스의 검증을 다룬다. 설정 파일에서 비 SINGLE 전략을 찾으면 0 이다. ## 도달 불가를 만든 것은 위치가 아니다 첫 가드와 switch 사이에도 검사가 둘 있다. 타깃 수가 상한과 정확히 같은지 보는 것과, 최악의 경우 호출 수를 계산해 총 한계와 비교하는 것이다. 둘 다 전략을 보지 않으므로 SINGLE 경로에서도 돈다. 갈라놓은 것은 switch 갈래 안에 들어갔는지 여부다. ## 고치는 방법 단언을 메시지 전문이나 그 갈래에만 있는 부분 문자열로 좁히면 된다. 순환이라는 단어나 잘못된 범위라는 문구가 그것이다. 지금 그 테스트를 돌리면 다섯 건이 통과한다. ## 확인하지 못한 것 첫 가드를 지운 사본을 만들어 돌려 보지는 않았다. 가드를 뺐을 때 각 라우트가 어디에 걸리는지는 실행 대신 범위 값과 갈래 조건으로 계산했다.