Devin.KR

설정·버전과 실행 절차

100분 안팎

학습 목표

앱·검사·브라우저의 버전과 준비 조건을 기록합니다.

개념

같은 명령이 다른 결과를 내는 이유

동아리 프로필 검사는 내 컴퓨터에서 통과했지만 동료의 CI에서는 시작하지 못했습니다. 이때 테스트를 다시 작성하기 전에 실행한 앱과 검사 파일, 런타임, 설정이 같은지 확인합니다. 재현 가능한 실행은 같은 이름의 명령을 쓰는 데서 끝나지 않습니다. 그 명령이 읽은 입력과 관찰한 결과를 다른 사람이 식별할 수 있어야 합니다.

이번 모듈의 로컬 레슨은 Bash·Python 3·Node.js와 표준 라이브러리로 실행합니다. node:test가 있는 Node 환경을 준비하고 다운로드한 실습 폴더에서 명령을 입력합니다. 미션에는 이전 Java 17·Spring Boot 3.1.5 앱과 Maven wrapper, Playwright 잠금 파일이 이어집니다. Java와 실제 브라우저 준비 방법은 미션 README-m08.md에 있습니다.

첫 실습은 브라우저를 띄우지 않는 환경 기록기입니다. qa-ci-environment의 capture.py를 고치고 bash check.sh로 네 검사를 실행합니다. 이후 미션에서는 bash check-ci-core.sh와 전체 bash check.sh의 범위를 구별합니다. 브라우저를 실행하지 못한 상태는 PENDING으로 적습니다. 핵심 검사 통과를 UI 통과로 확장해서 기록하지 않습니다.

실행을 설명하는 네 가지 입력

앱 버전은 검사 대상의 정체입니다. 제공 app-version.json의 training-m07은 실습용 식별자이며 운영 배포 번호가 아닙니다. 검사 버전은 기대값을 담은 코드의 정체입니다. 앱만 같아도 단언 파일이 바뀌면 통과 의미가 달라집니다. 둘을 함께 남겨야 이전 실패와 현재 수정 결과를 비교할 수 있습니다.

런타임은 코드를 실제로 해석한 도구입니다. node --version의 실측값과 Python 버전을 기록하며 원하는 버전을 적은 문서와 구별합니다. Java 미션에서는 JDK 17과 pom의 Spring Boot 3.1.5를 확인합니다. 설치 안내의 숫자를 그대로 보고서에 복사하면 PATH가 가리킨 다른 실행 파일을 놓칠 수 있습니다.

설정은 입력 조건을 바꿉니다. TZ는 날짜 해석에 영향을 줄 수 있고 QA_RUN_ID는 이번 합성 데이터의 실행 식별자입니다. 환경 기록기는 이 두 변수만 허용합니다. 비밀번호·쿠키·토큰을 포함하는 전체 환경변수 덤프는 제출하지 않습니다. 허용한 키라도 개인정보를 실행 ID로 쓰지 않는 약속이 필요합니다.

준비 상태는 아직 실행하지 않은 검사와 실패한 검사를 구별합니다. 브라우저 파일이 없거나 실행 권한이 막히면 제품의 저장 동작을 관찰하기 전입니다. 앱 응답·저장값·화면 결과가 없다는 사실을 적고 환경 준비를 다음 조치로 제안합니다. 단순히 빨간 표시가 있다는 이유로 제품 결함 보고서를 만들지 않습니다.

잠금 파일과 해시를 읽습니다

package-lock.json은 의존성 선택의 입력을 기록합니다. 첫 레슨의 잠금 파일은 외부 패키지가 없는 작은 계약 예시이며 실제 UI 패키지를 설치하지 않습니다. 미션의 isolation-app/ui/package-lock.json은 앞 모듈의 실제 Playwright 의존성 입력입니다. 이름이 같다는 이유로 두 파일의 검증 범위를 섞지 않습니다.

SHA256 해시는 파일 바이트가 달라졌는지 비교할 식별자입니다. 같은 해시라면 이 비교에서 동일한 입력을 사용했다는 근거를 남길 수 있습니다. 해시가 코드의 정확성이나 공급자의 신뢰를 보증하지는 않습니다. 줄바꿈 한 개도 바이트 변경이므로 다른 해시가 나올 수 있고 이를 기능 결함이라고 해석하지 않습니다.

실습의 test_hash_changes는 잠금 파일에 줄바꿈을 더해 해시가 달라지는지 확인한 뒤 원래 바이트를 복구합니다. 테스트가 입력을 바꿨다가 정리하는 구조를 읽어 봅니다. 실행 뒤 입력 파일이 달라진 상태를 방치하면 다음 사람은 다른 입력으로 검사를 시작하므로 반복 결과의 비교 기준이 흔들립니다.

기록기를 완성하고 검증합니다

capture.py의 capture는 app-version.json과 package-lock.json을 읽고 런타임을 조회합니다. allowed에는 공유할 환경 키를 나열합니다. starter는 TZ만 기록하여 QA_RUN_ID 검사가 실패합니다. 여기에 QA_RUN_ID를 추가합니다. os.environ 전체를 config에 넣는 수정은 실행 ID 검사를 통과시킬 수 있어도 비밀 제외 검사를 깨뜨립니다.

env TZ=UTC QA_RUN_ID=r08 bash check.sh는 이번 명령에 합성 실행 ID를 전달합니다. 환경변수의 일반 상속과 셸 치환 원리는 더 읽기의 env 장으로 보냅니다. 여기서는 보고서가 실제로 전달받은 허용 입력과 파일 해시를 남기는지 확인합니다. 셸의 기존 설정을 외웠다는 사실보다 실행 기록이 검증 질문에 답하는지가 중요합니다.

check.sh는 tests.py가 성공한 뒤 capture.py를 호출합니다. unittest의 실패 목록에서 test_run_id와 비교된 딕셔너리를 읽으면 어느 입력이 빠졌는지 알 수 있습니다. reports/run.json이 없으면 기록기 전에 검사가 멈춘 것인지 확인합니다. 파일이 존재한다는 사실만으로 최신 실행에서 만든 보고서라고 판단하지 말고 명령 결과도 함께 봅니다.

오류를 입력 조건으로 좁힙니다

FileNotFoundError가 나오면 현재 폴더와 입력 파일 이름을 확인합니다. 이 기록기는 실습 루트에서 상대 경로를 읽도록 작성되어 있습니다. check.sh는 자기 위치로 이동하므로 다른 폴더에서 호출해도 같은 입력을 읽습니다. 임의로 빈 app-version.json을 만들면 오류만 없어지고 버전 정보를 잃으므로 제공 파일을 확인합니다.

node 실행 파일을 찾지 못했다면 Python 검증기의 잘못된 기대값이 아니라 실행 준비 문제입니다. command -v node와 node --version으로 PATH가 실제로 찾는 도구를 확인합니다. 런타임 설치와 팀 버전 합의가 끝난 뒤 같은 명령을 다시 실행합니다. 재실행 전후의 환경 차이를 적어 두면 환경 수정과 제품 수정이 혼동되지 않습니다.

Permission denied가 보이면 wrapper 실행 권한 또는 파일 접근 권한을 확인합니다. 제공 Java 실습의 wrapper는 프로젝트에 포함되어 있으므로 시스템 Maven 설치를 요구하지 않습니다. 학습자 실행은 ./mvnw test이며 작성 검증은 캐시를 사용하는 오프라인 모드입니다. 의존성 미준비 오류를 단언 실패로 보고하지 않습니다.

다른 사람에게 넘길 최소 실행 기록

제출에는 명령, 실행 폴더, 앱 식별자, 검사 파일 식별자, 런타임 실측값, 잠금 파일 해시, 공개 가능한 설정, 종료 코드, 미실행 범위를 담습니다. 각 값이 어디에서 왔는지 설명합니다. 제공 버전 JSON은 선언값이고 node 버전은 실측값입니다. 두 종류를 구별해 쓰면 아직 확인하지 않은 앱 버전을 실측했다고 말하는 실수를 줄입니다.

미션의 versions.json은 앱 pom·브라우저 검사·잠금 파일의 해시를 남기고 브라우저 상태를 별도로 적습니다. pom 해시는 실행된 배포 바이너리의 해시가 아닙니다. 실제 릴리스 검증에서는 배포 산출물 ID가 추가로 필요합니다. 이번 실습은 로컬 소스 프로젝트 재현 범위를 다루므로 보고서에서도 그 한계를 밝힙니다.

같은 입력으로 통과했다는 기록은 출발점입니다. 운영 프록시·TLS·다른 브라우저·자연 시간 경과의 세션 만료까지 확인한 것은 아닙니다. 이번 환경 기록기의 완료 기준은 네 검사를 통과하고 실행 ID와 비밀 제외가 동시에 성립하는 것입니다. 이어지는 레슨에서는 이 명령들을 실패가 전달되는 실행 순서로 묶습니다.

따라하기

환경을 공개 가능한 키로 읽습니다

아래 코드는 실행 ID와 시간대만 읽습니다. 이 명령을 그대로 실행하여 두 키가 전달되는지 확인합니다.

import os,subprocess
p=subprocess.run(['env','TZ=UTC','QA_RUN_ID=r08','python3','-c',"import os; print('TZ='+os.environ['TZ']); print('QA_RUN_ID='+os.environ['QA_RUN_ID'])"],capture_output=True,text=True,check=True)
print(p.stdout,end='')

실행 결과

TZ=UTC
QA_RUN_ID=r08

실행 ID 누락을 고칩니다

qa-ci-environment starter의 capture.py에서 allowed를 이 목록으로 고칩니다. 입력 JSON·tests.py는 유지합니다.

allowed=['TZ','QA_RUN_ID']

네 검사와 보고서를 생성합니다

압축 루트에서 실행합니다. test_run_id 실패가 없어지고 reports/run.json에 실행 ID·해시·런타임이 기록되는지 확인합니다. 실제 버전은 설치 환경마다 달라 보고서에서 읽습니다.

env TZ=UTC QA_RUN_ID=r08 bash check.sh

해시의 비교 범위를 확인합니다

파일 전체의 무결성 식별 예시를 표준 라이브러리로 실행합니다. 두 바이트 입력의 비교 결과만 확인합니다.

import hashlib
a=hashlib.sha256(b'lock\n').hexdigest()
b=hashlib.sha256(b'lock\n\n').hexdigest()
print('same input:',a==hashlib.sha256(b'lock\n').hexdigest())
print('extra newline:',a==b)

실행 결과

same input: True
extra newline: False

확인 문제

실습

capture.py의 허용 환경 키에 QA_RUN_ID를 추가합니다. 실행 ID 보존·비밀 제외·잠금 파일 변경 감지·런타임과 브라우저 상태 네 검사를 유지합니다. reports/run.json을 읽고 선언값과 실측값을 구별하여 제출합니다.

시작 코드·테스트 내려받기

실행 명령

bash check.sh

기대 결과

환경 기록기 네 검사 통과, reports/run.json 생성

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 재현하기 좋은 결함 보고서의 구성을 설명해 주시면 됩니다.