.dockerignore 한 줄이 mvn test 를 통째로 사라지게 했다
CodeBuild 에서 "No tests were executed!" 가 나오면 의존성·JUnit 보다 먼저 봐야 할 곳

평소처럼 PR 을 올렸는데 CI 가 빨갛게 떴습니다. 깨진 단계는 docker build 의 테스트 실행 step 이었고, 로그 끝줄은 이랬습니다.
[ERROR] Failed to execute goal ... maven-surefire-plugin:2.22.2:test ...:
No tests were executed! (Set -DfailIfNoTests=false to ignore this error.)
이상한 점이 있었습니다. 로컬에서 ./mvnw test -Dtest='RedisServiceScanKeysTest' 는 5/5 통과합니다. pom.xml 의존성도 정상입니다. git push 도 잘 됐습니다. CodeBuild 만 실패하는데, 그 차이를 만들 만한 코드 변경은 새 테스트 클래스 한 개와 Dockerfile 의 RUN mvn test 단계 추가가 전부였습니다.
여기서 자연스러운 가설들이 쏟아집니다. POM 의 maven.test.skip 우선순위 문제일까요. Surefire 가 JUnit 5 jupiter 엔진을 못 찾는 걸까요. COPY --from=common /root/.m2 캐시에서 lettuce 가 빠진 걸까요. 모두 그럴듯한데 모두 아니었습니다.
결정적 로그 3줄
CodeBuild 로그를 위로 거슬러 올라가다 보면 결정적 시퀀스가 나옵니다.
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) ---
[INFO] skip non existing resourceDirectory /build/src/test/resources
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) ---
[INFO] No sources to compile ← src/test/java 자체가 컨테이너 안에 없음
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) ---
[INFO] No tests to run.
핵심은 testCompile 단의 "No sources to compile" 입니다. 이게 보이면 maven 입장에서 컴파일할 테스트 소스가 0개라는 뜻입니다. 즉 빌드 컨텍스트에 src/test/ 자체가 안 들어왔다는 신호입니다.
컴파일 실패라면 Compilation failure 가 떴을 거고, 의존성 누락이면 Could not resolve dependencies ... 가 떴을 겁니다. "No sources to compile" 은 의미가 다릅니다. 파일이 처음부터 없다는 뜻입니다.
실제 원인
여러 백엔드 프로젝트가 공통으로 갖고 있는 .dockerignore 룰이 있었습니다. 이미지 크기와 빌드 시간 최적화를 위해 누군가 한 줄 넣어 둔 것입니다.
# Test
src/test/
docker build 는 이 패턴에 매칭되는 경로를 빌드 컨텍스트에서 통째로 제외합니다. 그래서 Dockerfile 의
COPY src ./src
가 실행돼도 컨테이너 안의 /build/src/ 에는 src/main/ 만 들어가고 src/test/ 는 빈 채로 들어옵니다. Maven 입장에선 테스트 소스가 0개이니 testCompile 출력이 없고, surefire 가 매칭 클래스를 못 찾고, No tests were executed! 로 빌드가 실패합니다(-DfailIfNoTests=false 가 없으면).
기존엔 모든 프로젝트가 mvn package -Dmaven.test.skip=true 로만 이미지를 빌드했기 때문에 이 룰이 문제가 안 됐습니다. 이번에 처음으로 Dockerfile 안에서 단위 테스트를 돌리기 시작했고, 그 순간 문제가 드러났습니다.
픽스
.dockerignore 에서 src/test/ 한 줄 삭제로 끝났습니다.
# Environment
.env
.env.*
-# Test
-src/test/
-
# Others
영향은 두 가지입니다. 테스트를 실행하지 않는 Dockerfile(개발용, 로컬용) 은 동작이 안 바뀝니다. 빌드 컨텍스트가 테스트 파일 수만큼(보통 수십 KB) 커질 뿐입니다. 테스트를 실행하는 Dockerfile 은 의도대로 mvn test 가 돕니다. 이미지 크기도 문제없습니다. 어차피 multi-stage build 의 final stage 에는 컴파일된 jar 만 들어가지 테스트 클래스가 같이 가지 않습니다.
헷갈리기 쉬운 가설들
처음에 이 에러를 보면 다음 가설들이 더 자연스러워 보입니다. 시간을 가장 많이 빼앗는 것들이라 적어 둡니다. 위의 "No sources to compile" 3줄 시퀀스가 결정적이라, 그게 보이면 모두 빠르게 배제할 수 있습니다.
- POM 의
<maven.test.skip>true</maven.test.skip>과 CLI-Dmaven.test.skip=false의 우선순위 충돌. Maven 3.8.x 에서 실제로 가끔 생기는 버그입니다. 다만 이 경우엔 testCompile 단에 "No sources to compile" 이 명확히 찍혀 있어서 우선순위 문제가 아닙니다(우선순위가 깨졌다면 surefire 자체가 안 호출됩니다). - Surefire 2.22.2 가 JUnit 5 jupiter 엔진을 못 인식. spring-boot-starter-test 가 jupiter-engine 을 transitive 로 갖고 오므로 보통 인식됩니다. 이게 원인이면 로그에
T E S T S헤더 자체가 안 나옵니다. 우리 케이스는 testCompile 단부터 결과가 0이라 surefire 까지 가지도 못합니다. COPY --from=common /root/.m2캐시에 의존성이 빠짐. CodeBuild 가 maven central 에 접근할 수 있으면 누락된 의존성은 실시간으로 받아 옵니다. 로그 앞부분에Downloaded from central: ...가 보이면 이 가설은 배제됩니다.
세 가설 모두 검증에 5분 이상 걸리는데, "No sources to compile" 한 줄만 보면 1분 안에 진짜 원인으로 갈 수 있습니다. 그게 이 글의 절약 포인트입니다.
로컬에서는 .dockerignore 가 적용되지 않습니다. 그래서 ./mvnw test 는 어떤 상황에서도 통과합니다(직접 확인하니 5/5 통과였습니다). 재현하려면 로컬에서도 docker build -f Dockerfile.prod . 로 실제 컨테이너 빌드를 돌려 봐야 합니다. "로컬에서는 멀쩡한데 CI 에서만 깨진다" 는 류는 거의 대부분 로컬과 CI 가 본 입력이 다르다 가 원인입니다. .dockerignore, .gitignore, 환경변수, Maven settings, JDK 마이너 버전 같은 것들. 이번엔 그 후보 중 가장 첫 번째였습니다.
같은 조합이면 같은 문제가 재발합니다. .dockerignore 에 src/test/ 류 패턴이 있고, Dockerfile 빌드 안에서 처음으로 mvn test(혹은 gradle test) 를 돌리려 하고, 그동안은 mvn package -DskipTests 로만 이미지를 빌드했던 경우입니다. 이 셋이 겹치는 프로젝트가 둘 이상이면, 첫 프로젝트에서 부딪힌 뒤 두 번째도 비슷한 PR 에서 같은 에러를 만나기 쉽습니다. 처음 한 번 본 사람은 1분 컷으로 끝나지만, 처음 보는 사람한테는 한 시간을 빼앗는 종류의 문제입니다.