빌드와 외부 의존성
60분 안팎
학습 목표
JDK 17과 Spring Boot 3.1.x 시작 프로젝트의 pom.xml과 실제 사용 API를 확인합니다.
개념
코드 외에 빌드 입력도 검토합니다
AI가 서비스 메서드를 잘 작성했어도 빌드 파일에 필요 없는 라이브러리나 다른 Java 버전을 넣으면 재현이 어려워집니다. 신입이 의존성 검토를 맡을 때는 pom.xml 각 항목에 왜 필요한지 한 문장씩 적게 합니다. 이 레슨에서는 Maven이 사용하는 부모 POM, Java 버전, 직접 의존성과 테스트 범위를 읽습니다. 실제 사용한 API까지 연결해야 파일이 존재한다는 사실보다 강한 근거를 얻습니다.
Maven은 pom.xml을 입력으로 컴파일과 테스트 같은 작업을 수행합니다. groupId, artifactId, version 세 값은 프로젝트 또는 라이브러리의 좌표입니다. 파일 이름이 같아도 버전이 다르면 다른 아티팩트입니다. 이 실습 좌표는 kr.devin.bootcamp:java-bridge:1.0.0이며 의존성을 받은 로컬 저장소는 보통 사용자 홈의 .m2/repository입니다. 그 캐시는 앱 데이터 저장소와는 다른 역할입니다.
부모는 org.springframework.boot:spring-boot-starter-parent:3.1.5입니다. java.version은 17로 설정합니다. 부모가 여러 플러그인과 라이브러리 버전의 기준을 제공하므로 모든 의존성에 버전을 따로 반복하지 않습니다. 현재 저장소의 본 앱 버전과 이번 수업의 고정 버전을 혼동하지 않습니다. 이 숫자는 준비된 실습 환경을 맞추는 조건이며 최신 버전이나 운영 적합성을 주장하는 문장이 아닙니다.
의존성 목록과 import를 연결합니다
TaskService가 쓰는 List, Map, ArrayList와 BigInteger는 Java 표준 라이브러리입니다. 이들을 쓰려고 Maven에 별도 의존성을 추가하지 않습니다. 반대로 org.junit.jupiter.api.Test는 JDK에 들어 있지 않습니다. spring-boot-starter-test가 테스트 클래스 경로에 제공하는 외부 API입니다. import를 발견하면 JDK 제공인지 의존성 제공인지 먼저 나누어 표시합니다.
spring-boot-starter는 다음 앱 단계의 기본 기반이며 여기서는 SpringBootVersion.getVersion으로 실제 로드된 Boot 버전을 확인합니다. 이번 레슨에서 Spring 컨텍스트를 띄우지 않아도 그 클래스의 출처를 확인할 수 있습니다. 의존성을 선언했다고 모든 자동 설정이 사용된 것은 아닙니다. DB·HTTP·로그인 기능을 추가하려는 목적으로 관련 starter를 가져오는 작업은 아직 범위 밖입니다.
jackson-databind는 cases.json의 입력과 기대값을 읽는 ObjectMapper에 연결합니다. ObjectMapper는 JSON을 Java 값으로 바꾸거나 반대로 직렬화합니다. 이런 외부 경계 변환과 서비스의 제목 규칙은 다른 책임입니다. 라이브러리가 JSON 숫자를 Integer로 읽었다고 실수와 boolean도 허용되는 것은 아닙니다. 데이터 읽기 성공 뒤에도 계약에 맞는 자료형인지 서비스에서 확인합니다.
spring-boot-starter-test의 scope는 test입니다. JUnit을 src/test/java에서 사용하는 데에는 필요하지만 앱의 업무 클래스가 JUnit에 의존하지는 않습니다. scope를 빼면 기본 compile 범위가 되어 앱 의존성에 포함되는 범위가 넓어집니다. 테스트만 필요하다는 사실을 POM에 드러내며 main 코드에서 Test 애노테이션을 import하는 실수를 피합니다. 두 소스 경로가 언제 컴파일되는지도 확인합니다.
직접 선언하지 않은 라이브러리가 함께 들어오는 것을 전이 의존성이라고 합니다. starter-test에서 JUnit Jupiter가 제공되는 것이 이 사례입니다. 부모가 관리하는 버전과 실제 선택된 의존성은 서로 관련되지만 동일한 목록은 아닙니다. 별도 버전 지정이나 다른 경로가 선택 결과를 바꿀 수 있으므로 검토 시 선언 POM과 실제 실행의 클래스 출처를 함께 봅니다. 좌표 이름만 보고 기능을 추측하지 않습니다.
Wrapper와 캐시의 준비 상태를 확인합니다
mvnw는 프로젝트가 정한 Maven 배포본으로 작업을 실행하는 스크립트입니다. .mvn/wrapper의 properties와 jar도 함께 제공됩니다. Wrapper를 복사했다고 JDK나 모든 의존성 캐시가 포함되는 것은 아닙니다. 배포본을 처음 가져오는 데 네트워크가 필요할 수 있으며 Maven의 -o 옵션은 의존성을 오프라인으로 해결하게 할 뿐 최초 Wrapper 다운로드를 만들어 주지는 않습니다.
준비 환경에서는 README에 따라 ./mvnw test를 실행해 컴파일과 Surefire 테스트 보고서 생성까지 확인합니다. 네트워크 접속 가능한 준비 작업과 학습자가 수행하는 업무 검증을 분리해 기록합니다. starter의 의도된 assertion 실패는 의존성 준비가 끝난 뒤 발생한 결과일 수 있습니다. 반면 플러그인이나 부모 POM을 찾지 못한 실패는 테스트까지 도달하지 못한 환경 실패입니다.
Cannot access central in offline mode라는 메시지를 보면 오프라인 캐시에 필요한 항목이 없는 상황인지 확인합니다. requested profile 관련 경고나 잘못된 프로젝트 경로도 함께 보되 우선 마지막 원인 메시지와 누락 좌표를 기록합니다. 코드의 if를 고쳐도 캐시 누락이 해결되지는 않습니다. 오프라인 검증을 통과한 것처럼 쓰지 말고 준비 환경에서 필요한 아티팩트를 받은 뒤 같은 명령을 다시 실행합니다.
invalid target release: 17이 보이면 실제 Maven을 실행하는 Java가 17을 처리할 수 있는지 확인합니다. 터미널의 java와 Maven이 보는 JAVA_HOME이 다를 수 있습니다. ./mvnw -v에서 Java version과 Java home을 대조합니다. 환경을 수정했다면 증거에도 그 변경을 남깁니다. 이번 테스트의 Runtime.version 검사는 테스트 JVM이 17인지 확인하는 추가 근거이며 모든 개발 도구의 버전을 대신 검사하지 않습니다.
실행 증거로 버전 차이를 발견합니다
BuildTest는 실행 JVM의 주 버전과 SpringBootVersion의 결과를 비교합니다. 또 JUnit 패키지와 ObjectMapper 동작을 확인합니다. pomContract는 POM의 부모와 Java 설정을 별도로 읽습니다. 선언만 3.1.5인데 실행된 클래스는 다른 버전인 경우와 실행 결과는 맞지만 선언이 틀린 경우를 나누어 찾는 검사입니다. 다만 이 테스트만으로 전체 전이 의존성의 목록을 감사한 것은 아닙니다.
starter는 부모 버전을 3.1.12로 두었습니다. 두 버전의 캐시가 준비되어 있으므로 테스트는 실제 실행 후 버전 비교에서 실패합니다. 사용자가 수정할 곳은 parent의 version이며 테스트 기대값이나 코드 출력은 바꾸지 않습니다. Java와 JSON 경계 검사는 그대로 통과할 수 있습니다. 실패 두 건을 라이브러리의 결함으로 해석하기 전에 수업의 환경 계약과 실제 버전이 다른지 대조합니다.
실습 폴더의 DEPENDENCIES.md는 직접 의존성의 좌표, scope와 실제 사용 클래스를 기록하는 제출물입니다. 자바 표준 API를 별도 행으로 구분하고 HTTP·DB 연결을 미확인으로 표시합니다. AI가 POM에 추가한 의존성이 있다면 이번 코드가 어떤 API를 사용하는지 근거를 요청합니다. 사용하지 않는 의존성을 지우는 판단도 테스트와 범위를 확인한 뒤 합니다.
빌드 성공은 코드가 컴파일되고 요청한 단계가 끝났다는 증거입니다. 테스트가 실행되지 않아도 명령이 성공할 수 있으므로 Surefire의 테스트 수와 실패·오류·건너뜀 수를 확인합니다. 이번 레슨에서는 외부 서버 기동과 운영 배포를 하지 않습니다. BUILD SUCCESS를 앱 전체의 완료로 확대하지 않고 실제 실행한 단계와 환경, 남은 확인 사항을 기록합니다. 배포 산출물 구성의 확장은 더 읽기로 보냅니다.
실습 코드에서 찾을 부분
다음 부모 좌표와 Java 설정을 pom.xml에서 찾습니다. starter의 실제 부모 버전과 비교하고 test 의존성의 scope는 유지합니다. XML의 닫는 태그도 함께 확인합니다.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/>
</parent>
<properties><java.version>17</java.version></properties>따라하기
관찰 프로젝트 준비
BuildTest를 실행해 선언 버전·실행 버전·테스트 JVM·JSON API를 확인합니다. 보고서의 네 검사 결과를 확인한 뒤 클래스 출처와 POM을 대조합니다.
./mvnw test실행 JVM과 Boot 버전 확인
case 1은 실제 테스트 환경의 Java 주 버전과 로드된 Boot 버전을 출력합니다. demos/run.sh는 관찰용으로 3.1.5 클래스 경로를 고정합니다. starter의 실제 Maven 클래스 경로 검사는 BuildTest 보고서로 따로 확인합니다.
bash demos/run.sh 1실행 결과
JAVA=17 BOOT=3.1.5
JSON 경계 API 직접 호출
case 2는 Jackson으로 JSON을 읽어 정수 id와 boolean done을 출력합니다. demos/run.sh의 로컬 캐시 경로가 이 실행의 의존성을 제공하며 업무 제목 검증을 대신하지 않는다고 설명합니다.
bash demos/run.sh 2실행 결과
ID=1 DONE=false
POM의 부모와 scope 대조
case 3은 실행 폴더의 POM을 읽습니다. 부모 좌표와 직접 의존성 scope를 API 대응표와 대조합니다. 이 출력은 선언 목록이며 전체 전이 의존성의 해석 결과는 아닙니다.
bash demos/run.sh 3실행 결과
PARENT=spring-boot-starter-parent VERSION=3.1.5 spring-boot-starter:compile jackson-databind:compile spring-boot-starter-test:test
확인 문제
실습
pom.xml의 부모 버전을 수업 계약 3.1.5에 맞춥니다. 테스트 기대값은 유지하고 제공된 빌드 테스트 4개를 통과시킵니다. DEPENDENCIES.md의 API·scope 대응을 코드와 대조합니다.
실행 명령
./mvnw test
기대 결과
테스트 4개가 실패·오류·건너뜀 없이 통과하며 종료 코드는 0입니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- AI가 만든 코드가 실행될 때 추가로 확인할 내용을 설명해 주시면 됩니다.