Files
document-haness/docs/TechLog/tech-log-studio/seams-no-test-crosses/case/case-two-pods-crashlooped-with-no-test-starting-the-context.md
T
DongHyeonkaandClaude Opus 5 53537e37e8 docs(TechLog): 주제 4·5 를 스킬대로 다시 쓴다
what-the-compiler-lets-through  중앙값 2,096 → 2,590 자
  seams-no-test-crosses                    → 3,091 자

bivariance 가 왜 느슨한 판정을 받는지, never 캐스트가 왜 아무것도 요구하지 않는지처럼
「이름을 댔으면 왜 있는지도 댄다」를 채웠다. 네 가지가 각각 무엇을 통과시키고 어디서
드러났는지를 표로 갈랐다. 검사 넷 중 무엇이 SQL 을 실제로 돌리는지도 표로 세웠다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 18:57:59 +09:00

5.8 KiB

kind, slug, title, topic, topicName, project, status, lastVerifiedOn, sourceRevision, source
kind slug title topic topicName project status lastVerifiedOn sourceRevision source
CASE two-pods-crashlooped-with-no-test-starting-the-context 파드가 두 번 CrashLoopBackOff 로 들어갔다 — 어떤 테스트도 애플리케이션 컨텍스트를 띄우지 않았다 seams-no-test-crosses 테스트가 지나지 않는 이음매 TechLog 게시 전 2026-09-04 tech-log@2026-09-02
final/document.md#§7.1
final/document.md#§6.5
final/document.md#§12.1

파드가 두 번 CrashLoopBackOff 로 들어갔다 — 어떤 테스트도 애플리케이션 컨텍스트를 띄우지 않았다

파드가 두 번 CrashLoopBackOff 로 들어갔다. 한 번은 스캔되는 컴포넌트에 생성자가 둘이었고, 한 번은 이 빌드에 없는 Jackson 2 의 타입을 import 했다. 컴파일도 단위 테스트도 실제 PostgreSQL 위에서 도는 통합 테스트 26개도 전부 통과했다. 그중 어느 것도 애플리케이션 컨텍스트를 띄우지 않는다.

관계

  • 이음매마다 그 이음매를 실제로 지나는 검사를 하나씩 둔다 이 사건이 그 목록의 첫 줄이다.
  • TypeScript 가 검사를 놓아 주는 네 곳 다른 언어에서 컴파일 통과가 반영의 증거가 아니었던 자매 사건이다.
  • 그 SQL 은 한 번도 실행된 적이 없었다 같은 「지나지 않은 이음매」의 다른 예다.

문제

컴포넌트 스캔이 생성자를 고르지 못하면 컨텍스트가 refresh 에 실패한다. 컨텍스트를 띄우는 테스트가 없으면 그 실패는 배포에서 처음 나타난다.

두 번째 사건은 import 였다. JdbcProjectRepositoryAdapter 가 Jackson 2 의 ObjectMapper 를 요구했는데 이 빌드는 Jackson 3 이다.

결론

두 건 다 컨텍스트가 뜰 때 처음 드러났다.

첫 번째 : 스캔되는 컴포넌트에 생성자 둘, @Autowired 없음 두 번째 : Jackson 2 ObjectMapper 를 요구, 이 빌드는 Jackson 3

두 번째가 컴파일을 통과한 이유는 Jackson 2 타입이 어떤 전이 의존성을 통해 클래스패스에 남아 있어서다. 잘못된 import 가 정상적으로 해석된다.

첫 번째는 ArchUnit 규칙으로 막았다. 스캔되는 컴포넌트는 생성자가 하나이거나, 여럿이면 그중 하나에 @Autowired 가 붙어야 한다. 규칙이 실제로 잡는지 결함을 되돌려 확인했다.

검증 환경

tech-log-backend : ca63d7d · 0da7c7e 런타임 : k3s 위의 파드 Jackson : 이 빌드는 Jackson 3, 클래스패스에 Jackson 2 타입이 전이 의존성으로 남아 있음 확인 방식 : ArchUnit 규칙을 결함으로 되돌려 실제로 빨개지는지 확인

재현 조건

  1. 스캔되는 컴포넌트에 생성자를 둘 만들고 @Autowired 를 붙이지 않는다
  2. ./gradlew check 를 돌린다 — 통과한다
  3. ArchUnit D20 규칙을 켠 상태로 돌린다 — 그 컴포넌트를 짚는다

본문

어떤 테스트도 컨텍스트를 띄우지 않았다

새 활동 어댑터가 생성자를 둘 갖고 있었다. 하나는 운영용, 하나는 테스트가 id 생성기를 넣기 위한 것이다. 둘 중 어느 것에도 @Autowired 가 없어 컴포넌트 스캔이 고르지 못했다.

스캔은 생성자가 하나면 그것을 쓰고, 여럿이면 @Autowired 가 붙은 것을 쓴다. 둘 다 아니면 어느 것을 쓸지 정할 수 없으므로 컨텍스트가 refresh 에 실패한다.

컴파일도, 단위 테스트도, 실제 PostgreSQL 위에서 도는 통합 테스트 26개도 전부 통과했다. 그 어느 것도 애플리케이션 컨텍스트를 띄우지 않기 때문이다. 운영에서 파드가 CrashLoopBackOff 로 들어갔고, 그때서야 드러났다.

통합 테스트가 실제 데이터베이스를 쓴다는 것과 애플리케이션 컨텍스트를 띄운다는 것은 다르다. 어댑터를 직접 만들어 SQL 을 돌리는 테스트는 컨테이너가 필요하지만 스프링 컨텍스트는 필요하지 않다.

클래스패스에 남은 옛 타입

두 번째 건은 import 였다. 어댑터가 Jackson 2 의 ObjectMapper 를 요구했는데 이 빌드는 Jackson 3 을 쓴다. 그런 빈이 없으므로 컨텍스트가 refresh 에 실패한다.

컴파일이 잡지 못한 이유는 어떤 전이 의존성이 Jackson 2 타입을 클래스패스에 올려 두어 import 가 정상적으로 해석되기 때문이다. 컴파일러가 보는 것은 그 타입이 클래스패스에 있는지까지이고, 그 타입의 빈이 컨텍스트에 있는지는 컨텍스트를 띄워야 알 수 있다.

원인 왜 컴파일·테스트가 못 잡았나 커밋
스캔되는 컴포넌트에 생성자 둘, @Autowired 없음 어떤 테스트도 애플리케이션 컨텍스트를 띄우지 않는다 ca63d7d
Jackson 2 ObjectMapper 를 요구(이 빌드는 Jackson 3) Jackson 2 타입이 전이 의존성으로 클래스패스에 남아 있어 import 가 정상 해석된다 0da7c7e

D20 규칙으로 막은 것

스캔되는 컴포넌트는 생성자가 하나이거나, 여럿이면 그중 하나에 @Autowired 가 붙어야 한다는 아키텍처 규칙을 세웠다.

이 규칙은 컨텍스트를 띄우지 않고도 돈다. 바이트코드를 훑어 스캔 대상 애노테이션이 붙은 클래스의 생성자를 세면 되므로 빌드 시간이 늘지 않는다. 결함을 되돌려 규칙이 실제로 멈추는 것을 확인한 뒤 커밋했다.

D20 이 보지 않는 것

이 규칙은 생성자 개수만 센다. 클래스패스에 남은 옛 라이브러리 타입을 import 하는 것은 대상이 아니다.

그 경로를 막으려면 컨텍스트를 실제로 띄우는 검사가 필요하고, 그것은 아직 없다. 지금은 컨테이너가 뜰 때 알게 된다.