제공된 서비스 빌드와 실행
100분 안팎
학습 목표
Java 17·Spring Boot 3.1.x 시작 코드의 요청·테스트·실행 흐름을 읽습니다.
개념
빌드 성공과 서비스 성공을 나눕니다
안내판 API의 첫 운영 작업은 새 기능을 만드는 일이 아니라 제공된 코드를 같은 절차로 빌드하고 응답을 확인하는 일입니다. 파일이 있다는 사실, 테스트가 통과한 사실, 프로세스가 살아 있는 사실, 사용자가 공지 목록을 받는 사실은 서로 다른 증거입니다. 배포 담당자는 어느 단계까지 확인했는지 기록해야 합니다. 이번 레슨에서는 상태 값을 한 군데 고치면서 소스에서 테스트와 실행 JAR로 이어지는 흐름을 읽습니다. 작성할 애플리케이션은 개인 실습용이며 실제 계정이나 데이터베이스를 사용하지 않습니다.
실습 폴더를 엽니다
시작 코드 zip을 빈 개인 폴더에 풀고 터미널에서 해당 폴더로 이동합니다. pom.xml, mvnw, .mvn/wrapper, src/main/java, src/test/java가 보여야 합니다. 숨김 폴더 .mvn도 함께 풀었는지 확인합니다. JDK 17은 java 실행기와 javac 컴파일러를 함께 제공합니다. java -version과 javac -version에서 주 버전을 확인합니다. command not found가 나오면 설치나 PATH 문제입니다. mvnw는 Maven 버전을 고정하는 실행 스크립트이지 JDK를 설치하는 프로그램은 아닙니다.
의존성과 버전을 확인합니다
pom.xml의 부모는 spring-boot-starter-parent 3.1.5이고 java.version은 17입니다. spring-boot-starter-web이 HTTP 요청을 처리할 서버와 웹 기능을 제공하고 starter-test는 테스트에만 사용됩니다. 이 버전은 트랙의 재현 가능한 교육 기준입니다. 최신 운영 환경을 권하는 문장이 아닙니다. 개인 환경에서 첫 ./mvnw test는 Wrapper 배포본과 의존성을 내려받을 수 있습니다. 의존성을 받은 뒤에는 ./mvnw -o -q test로 캐시만 사용합니다. -o는 오프라인, -q는 일반 정보 로그를 줄이는 옵션입니다.
애플리케이션의 입구를 찾습니다
NoticeApplication의 main은 SpringApplication.run으로 애플리케이션 컨텍스트를 만듭니다. @SpringBootApplication은 lab 패키지 아래 구성 요소를 찾도록 돕습니다. NoticeController의 @RestController는 반환 값을 HTTP 응답 본문으로 변환하는 역할을 합니다. @GetMapping("/health")는 GET 경로를 메서드에 연결합니다. Map.of("status", "UP")는 status 키를 가진 값을 만들고 웹 계층은 JSON으로 변환합니다. 메서드 이름만 바꿔서는 URL이 바뀌지 않습니다. 이번 수정은 health의 DOWN 값을 UP으로 바꾸는 것입니다.
응답 계약을 먼저 읽습니다
GET /health는 상태 200과 {"status":"UP"}를 반환하고 GET /notices는 id와 title을 가진 공지 배열을 반환합니다. 없는 경로는 404이며 /fixture/error는 의도적으로 500을 반환합니다. /fixture/slow는 300ms 기다립니다. 이 fixture는 진단 연습을 위한 장치여서 실제 서비스에 그대로 노출하는 설계로 해석하지 않습니다. /health는 프로세스가 이 요청에 응답하는지만 확인합니다. 데이터 저장소나 다른 시스템의 준비 상태를 보장하지 않으며, 뒤 모듈에서 readiness의 범위를 설계합니다.
실패하는 테스트를 읽습니다
첫 ./mvnw test에서 expected: UP but was: DOWN이라는 assertion 실패는 기대 계약과 반환 값이 다르다는 뜻입니다. 테스트가 원하는 값을 바꾸어 통과시키지 않고 NoticeController.health를 고칩니다. 컴파일 오류인 cannot find symbol은 실행 전 소스 문제이고, Cannot access ... in offline mode는 캐시 준비 문제입니다. target/surefire-reports의 XML과 텍스트에는 테스트 수·실패·오류가 있습니다. 실패는 실행한 검사에서 기대와 달랐다는 뜻이고 오류는 검사가 정상 완료되지 못한 경우입니다. 둘을 동일한 코드 문제로 처리하지 않습니다.
두 검사 수준을 구분합니다
기본 ./mvnw test는 포트를 열지 않는 다섯 테스트를 실행합니다. 상태 값, 공지 ID, 오류 fixture 상태, 요청 ID와 로그의 연결, 잘못된 ID 대체를 검사합니다. LogTest는 Spring의 요청·응답 모형으로 필터를 호출하며 실제 네트워크 성공을 증명하지 않습니다. 개인 환경에서 ./mvnw -DexcludedGroups=none test를 실행하면 external 태그의 실제 HTTP 검사도 포함합니다. 이 검사는 임의 포트를 열고 health와 notices를 요청하며 종료 때 컨텍스트를 닫습니다. 샌드박스의 Operation not permitted는 포트 허용 환경에서 확인할 항목입니다.
테스트와 패키징을 연결합니다
./mvnw package는 앞선 컴파일과 테스트를 거쳐 JAR를 만듭니다. Spring Boot Maven 플러그인은 필요한 의존성을 담은 실행 가능 JAR로 재구성합니다. 산출물은 target/notice-board-1.0.0.jar입니다. 소스 파일을 java -jar에 넘기거나 .jar.original을 실행하지 않습니다. 기본 테스트 통과가 실제 HTTP 검사 통과를 대신하지 않으므로 미션에서는 별도의 check.sh로 연동까지 확인합니다. Maven 생명주기와 의존성 충돌의 자세한 설명은 더 읽기의 서재 장으로 보냅니다.
정해진 시간만 실행합니다
이 교육용 main은 --lab.duration-ms=15000이면 15초 후 컨텍스트를 닫아 정상 종료합니다. 개인 폴더에서 java -jar target/notice-board-1.0.0.jar --lab.duration-ms=15000으로 실행하고 그 시간 안에 다른 터미널에서 요청합니다. 기본 주소는 127.0.0.1이고 포트는 18080입니다. 127.0.0.1은 요청을 보내는 컴퓨터 자신의 루프백 주소입니다. 다른 컴퓨터에서 같은 주소를 입력해도 이 앱으로 연결되지 않습니다. 외부 노출은 이후 모듈의 네트워크 경계를 설계한 뒤 연습합니다.
접속 실패를 잘라 봅니다
Port ... already in use라면 해당 포트를 누가 사용하는지 개인 환경에서 확인하고 앱을 --server.port=18081로 실행합니다. URL의 포트도 함께 바꿉니다. 다른 프로그램을 종료하는 방식으로 해결하지 않습니다. Unable to access jarfile은 실행 파일의 경로와 이름을 먼저 확인합니다. connection refused가 나오면 앱의 자동 종료 시각이나 listen 포트부터 확인합니다. JSON이 아닌 404 페이지를 받았다면 서버에는 도달했으므로 URL 경로를 확인합니다. 무작정 빌드를 반복하면 이 서로 다른 원인을 구분하기 어렵습니다.
요청 기록을 남깁니다
curl -i -H "X-Request-ID: lesson-01" http://127.0.0.1:18080/health로 헤더와 본문을 함께 확인합니다. -i는 상태 줄과 헤더를 보여 줍니다. RequestLogFilter는 같은 ID를 응답 헤더와 workspace/logs/requests.log에 남깁니다. 정상 상태를 확인한 시각, 실행 JAR 이름, 설정한 포트, 요청 경로를 README에 기록합니다. 요청 한 번이 성공했다고 계속 정상인 것은 아니므로 이번 기록은 그 시점의 증거로 다룹니다. 다음 레슨에서 프로세스와 자원이 무엇을 보여 주는지 함께 확인합니다.
다음 배포의 입력을 만듭니다
공지 목록은 코드에 있는 메모리 초기값입니다. 재시작하면 같은 초기 목록을 다시 구성하며 아직 workspace/data를 읽거나 쓰지 않습니다. 앞 모듈에서 만든 data 폴더는 뒤에서 지속 저장을 연결할 자리입니다. 상태 조회 수정과 테스트 결과를 개인 작업 폴더에 보관하고 JAR만 별도 산출물로 취급합니다. 설정과 생성 로그를 소스와 섞지 않습니다. m03은 이 구조를 그대로 받아 컨테이너 이미지 안에서 같은 JAR를 실행하므로 src와 pom의 경로를 임의로 바꾸지 않습니다.
사실 확인 참고: Spring Boot 3.1.5 시작 안내에서 이 레슨에 사용한 기능의 공식 정의를 확인할 수 있습니다.
따라하기
프로젝트 버전 확인
zip 루트에서 pom의 버전을 읽습니다. 모범 답안에서도 같은 결과입니다. java -version과 javac -version으로 설치한 JDK도 따로 확인합니다.
python3 - <<'PYCODE'
import xml.etree.ElementTree as ET
root=ET.parse('pom.xml').getroot(); ns={'m':'http://maven.apache.org/POM/4.0.0'}
print('Java',root.findtext('m:properties/m:java.version',namespaces=ns))
print('Spring Boot',root.findtext('m:parent/m:version',namespaces=ns))
PYCODE실행 결과
Java 17 Spring Boot 3.1.5
실패를 읽고 기본 테스트 수정
starter에서는 healthUnit이 DOWN을 보고 실패합니다. health를 UP으로 고치고 다시 실행합니다. 기본 검사 5개가 통과하는지 surefire-reports를 확인합니다. 캐시를 준비한 뒤 오프라인 재검사는 ./mvnw -o -q test입니다.
chmod u+x mvnw
./mvnw test실행 JAR 만들기
테스트 뒤에 package를 실행합니다. target/notice-board-1.0.0.jar가 생겼는지 확인합니다. 결과 파일은 생성 산출물이며 Git에 넣지 않습니다.
./mvnw package
test -f target/notice-board-1.0.0.jar개인 환경에서 실제 요청 확인
포트를 허용한 개인 환경에서 실행합니다. 다른 터미널에서 curl -i -H "X-Request-ID: lesson-01" http://127.0.0.1:18080/health와 /notices를 요청합니다. 상태 200·JSON·ID를 확인합니다. 15초 후 정상 종료합니다. 이 샌드박스에서는 bind가 차단되어 실행 출력은 비워 둡니다. 전체 연동 검사는 ./mvnw -DexcludedGroups=none test입니다.
java -jar target/notice-board-1.0.0.jar --lab.duration-ms=15000확인 문제
실습
NoticeController.health의 상태 값을 UP으로 고칩니다. ./mvnw test로 기본 5개 테스트를 통과시키고 ./mvnw package로 실행 JAR를 만듭니다. 테스트는 수정하지 않습니다. 실제 응답은 개인 포트 허용 환경에서 ./mvnw -DexcludedGroups=none test로 확인합니다. JAR는 15초 후 정상 종료합니다. README에 단위 검사와 실제 HTTP 확인 범위를 구분해 적습니다.
실행 명령
./mvnw test
기대 결과
기본 단위 검사 5개 통과, 실패·오류 0. 실제 HTTP는 별도 external 태그 검사입니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 빌드와 단위 테스트가 성공했는데도 서비스 접속이 실패할 수 있는 이유를 설명해 주세요.
- /health의 200 응답으로 확인한 범위와 추가로 확인해야 할 항목은 무엇인가요?