개념
이 레슨의 경계 — 테스트 하나가 아니라 환경 전체
m13 레슨 10은 테스트 하나가 필요한 데이터를 스스로 만들고 치우는 방법을 다뤘다. 픽스처가 API 로 전제 데이터를 생성하고, 식별 접두사로 멱등성을 보장하고, 끝나면 정리한다. 그 레슨은 테스트 한 건의 자급자족 을 책임진다.
이 레슨은 그 테스트들이 올라타는 바닥을 책임진다. 픽스처가 완벽해도 바닥이 흔들리면 전부 무너진다 — 스키마가 어제와 다르거나, 의존 서비스가 내려가 있거나, 공용 코드 테이블의 시드가 바뀌었거나, 앞 실행이 남긴 쓰레기가 쌓여 있거나. 픽스처는 자기가 만든 데이터만 알고, 환경 전체의 상태는 아무도 모르는 영역 이 되기 쉽다. 그 영역이 TestOps 의 일이다.
구분하는 기준은 간단하다. 그 데이터를 테스트 하나가 만들고 지울 수 있으면 픽스처의 일(m13), 여러 테스트가 공유하거나 테스트 밖에서 결정되면 환경 운영의 일(m59)이다. 사용자 계정 하나는 픽스처, 통화 코드 테이블은 환경. 주문 한 건은 픽스처, 결제 연동의 모의 서버 버전은 환경.
환경을 버전으로 관리한다는 것
"스테이징에서 테스트했다"는 문장은 정보를 거의 담고 있지 않다. 스테이징은 어제의 스테이징과 다르다. 환경을 버전으로 관리한다는 것은 그 다름을 기록 가능한 좌표로 바꾸는 일 이다. 좌표에는 최소한 네 축이 들어간다.
| 축 | 무엇을 가리키는가 | 기록하지 않으면 생기는 일 |
|---|---|---|
| 애플리케이션 버전 | 배포된 제품의 커밋·이미지 태그 | 어느 코드가 테스트됐는지 모른다 |
| 구성(configuration) | 기능 플래그, 환경 변수, 외부 연동 모드 | 플래그 하나로 결과가 갈리는 이유를 못 찾는다 |
| 스키마 버전 | 적용된 마이그레이션의 마지막 번호 | 스키마 불일치 실패가 제품 결함으로 오분류된다 |
| 시드 데이터 세트 | 공용 기준 데이터의 버전 | 기준 데이터가 바뀐 날 전부 실패하고 원인을 못 짚는다 |
여기에 의존 서비스(모의 서버·결제 샌드박스·검색 엔진)의 버전을 더하면 사실상 완성된다. 좌표를 파일 하나로 두고, 실행 결과에 그 파일의 해시를 같이 남긴다.
# env/staging.coordinate.yml — 예시 형태
env: staging
app:
image: registry.internal/shop-api:2026.10.02-7f3a1c9
commit: 7f3a1c9
schema:
migration_head: "20261001_add_order_memo"
config:
feature_flags: flags/staging-2026-10-02.json
payment_mode: sandbox
seed:
dataset: seed/base-v14
applied_at: "2026-10-02T21:10:00+09:00"
dependencies:
payment_mock: payment-mock:1.8.0
search: opensearch:2.x # 정확한 패치 버전은 배포 기록 참조이 파일의 값이 바뀌면 환경이 바뀐 것이다. 바뀐 사실을 사람이 기억하는 대신 좌표가 증언하게 만드는 것 이 핵심이고, 레슨 6에서 이 좌표를 테스트 결과와 묶는다.
환경 좌표가 있으면 평소에 못 하던 질문에 답할 수 있다.
- "어제는 통과했는데 오늘 실패" → 두 실행의 좌표를 비교한다. 다른 축이 원인 후보다.
- "내 로컬에서는 됩니다" → 로컬 좌표와 CI 좌표를 나란히 놓는다. 대화가 3분에 끝난다.
- "이 버전 다시 테스트해 주세요" → 같은 좌표를 재현할 수 있는지가 답을 결정한다.
환경 대장과 점유 — 환경은 공유 자원이다
테스트 환경이 하나뿐인 조직에서는 환경 자체가 경쟁 자원이 된다. 개발자가 수동으로 확인하는 동안 자동화가 돌면 둘 다 실패하고, 둘 다 상대를 의심한다. 그래서 환경에도 재고 장부가 필요하다.
환경 대장 (최소 열)
이름 | 용도 | 현재 좌표 | 점유자 | 점유 만료 | 마지막 초기화 | 상태
----
staging-1 | 자동화 야간 | base-v14 | CI(야간) | 06:00 | 02:10 | 가용
staging-2 | 수동 탐색 | base-v14 | 김OO | 18:00 | 09:00 | 점유
perf-1 | 성능 | base-v13 | - | - | 월요일 | 점검점유를 기계적으로 강제할 수 없는 조직이라도, 장부만 있으면 충돌 대화가 짧아진다. 자동화가 환경을 잡을 때 점유 표시를 남기고 끝나면 해제하게 만드는 것이 첫 단계다.
초기화 전략 — 매번·주기·더티
환경을 언제 깨끗하게 되돌릴지는 비용과 간섭 위험의 교환이다. 세 가지 기본형이 있고, 실무에서는 데이터 영역별로 다르게 섞어 쓴다.
| 전략 | 방식 | 유리한 점 | 비용과 위험 |
|---|---|---|---|
| 매 실행 초기화 | 실행 전 DB·스토리지를 알려진 상태로 되돌린다 | 간섭이 0 에 가깝다. 재현성이 최고 | 준비 시간이 고정비로 붙는다. 병렬 실행이 서로의 초기화에 걸린다 |
| 주기 초기화 | 야간·주간 등 정해진 시점에만 되돌린다 | 준비 비용이 분산된다. 운영과 비슷한 데이터 밀도를 유지 | 주기 사이에 쓰레기가 쌓인다. 초기화 직전 실행이 가장 불안정하다 |
| 더티(초기화 없음) | 되돌리지 않고 계속 쓴다. 테스트가 자기 데이터만 책임진다 | 준비 비용이 없다. 병렬에 가장 유리 | 데이터가 무한히 늘어난다. 간섭 사고가 잠복한다. 고아 데이터 정리가 필수 |
선택의 기준은 "얼마나 깨끗해야 하는가"가 아니라 "간섭이 생겼을 때 얼마나 빨리 알아챌 수 있는가" 다. 간섭을 탐지할 수 없는 조직에서 더티 전략을 쓰면 원인 불명 플래키가 쌓인다. 반대로 탐지와 정리가 되는 조직에서는 더티가 가장 경제적이다.
실무에서 가장 잘 작동하는 혼합형은 이렇다.
데이터 영역별 초기화 정책 (혼합형 예시)
공용 기준 데이터(코드·카테고리·권한) → 주기 초기화 + 좌표로 버전 관리
트랜잭션 데이터(주문·결제·로그) → 더티 + 픽스처가 자기 것만 생성·정리
계정 → 더티 + 실행별 접두사로 격리, 7일 후 일괄 삭제
파일 스토리지 → 더티 + 경로 접두사로 격리, 주간 정리 작업
캐시·검색 인덱스 → 매 실행 초기화(비용이 작고 간섭이 치명적)캐시를 매 실행 비우는 이유가 중요하다. 캐시는 앞 실행의 결과를 다음 실행에 보이게 만드는 가장 흔한 경로 이고, 그로 인한 실패는 테스트 코드 어디를 봐도 원인이 없다. 비용이 거의 없으므로 의심할 가치가 없게 만드는 것이 낫다.
테스트 데이터의 수명 — 세 가지를 반드시 정한다
더티 전략을 쓰든 안 쓰든, 생성된 데이터에는 세 가지가 붙어 있어야 한다.
- 소유자 — 어느 실행이 만들었는가. 실행 식별자를 데이터에 심는다(이름 접두사, 메모 필드, 전용 태그 컬럼 중 가능한 것).
- 수명 — 언제까지 살아 있어도 되는가. 테스트 종료 시점·당일·7일 같은 등급으로 나눈다. 실패 조사용으로 남겨야 하는 데이터가 있으므로 "즉시 삭제"가 늘 정답은 아니다.
- 정리 책임 — 누가 지우는가. 테스트 자신(픽스처 해제), 실행 종료 훅, 또는 주기적 정리 작업 중 하나로 명시한다.
세 가지가 정해지면 고아 데이터 탐지 가 가능해진다. 고아는 소유자 식별자가 이미 끝난 실행을 가리키면서 수명이 지난 데이터다. 탐지 쿼리는 보통 한 줄이면 된다.
-- 예시: 실행 접두사가 있고 생성 7일이 지난 테스트 계정
SELECT run_id, count(*) AS rows, min(created_at) AS oldest
FROM accounts
WHERE login LIKE 'qa-%'
AND created_at < now() - interval '7 days'
GROUP BY run_id
ORDER BY oldest;이 쿼리의 결과 추세를 주간으로 보면 정리 체계가 작동하는지 바로 보인다. 고아 건수가 우상향하면 어딘가의 정리 책임이 비어 있다는 뜻이다. 픽스처 해제가 실패하는 경로 (테스트 타임아웃, 러너 강제 종료)가 보통 원인이고, 그래서 픽스처 해제만으로는 부족하고 주기 정리가 항상 함께 있어야 한다.
데이터 운영에서 반드시 짚어야 할 제약이 하나 더 있다. 운영 데이터를 복제해 테스트에 쓸 때는 개인정보가 함께 넘어온다. 가명·마스킹 처리 없이 복제하는 관행은 법적 위험이고, 처리 방식·보존 기간·접근 권한은 조직의 개인정보 정책에 따라 결정된다. 관련 규정의 적용 범위는 법무·보안 담당과 확인하고, 기술적으로는 "복제 후 마스킹"보다 "마스킹된 생성 데이터"가 안전하다.
프리플라이트 — 실행 전 60초가 아침 두 시간을 산다
환경 문제로 생긴 대량 실패는 TestOps 의 비용 구조에서 가장 큰 항목이다. 300개가 전부 실패한 아침에 당번은 원인을 찾는 데 두 시간을 쓰고, 결과는 "모의 결제 서버가 내려가 있었음"으로 끝난다. 그 두 시간은 실행 전 60초로 막을 수 있다.
프리플라이트는 테스트가 아니라 게이트다. 실패하면 테스트를 돌리지 않고 멈추고, 결과를 "환경 미준비"라는 별도 상태로 기록한다. 테스트 실패로 기록하지 않는 것이 중요하다 — 통과율 지표를 오염시키면 추세가 거짓말을 한다.
프리플라이트 체크 목록 (실행 직전, 목표 60초 이내)
[좌표]
- 환경 좌표 파일을 읽어 결과 메타에 기록했다
- 애플리케이션이 보고하는 버전이 좌표의 값과 일치한다
- 스키마 마이그레이션 head 가 좌표의 값과 일치한다
[가용성]
- 애플리케이션 헬스 엔드포인트가 정상을 반환한다
- 의존 서비스(모의 결제·검색·메시지 큐)가 각각 정상을 반환한다
- 테스트 계정 발급 API 로 계정 하나를 만들고 지우는 왕복이 성공한다
[상태]
- 공용 기준 데이터의 행 수가 기대 범위 안이다 (예: 통화 코드 ≥ 20)
- 디스크·DB 커넥션 여유가 임계 이상이다
- 직전 실행의 정리 작업이 성공으로 끝났다
[판정]
- 좌표 불일치 → 중단, '환경 미준비' 로 기록, 당번 알림
- 의존 서비스 1개 장애 → 해당 서비스 의존 테스트만 건너뛰기로 표시
- 전부 정상 → 실행 시작세 번째 항목의 "계정 왕복"이 실무에서 가장 값을 한다. 헬스 엔드포인트는 서버가 떠 있다는 것만 말하지만, 계정 하나를 만들고 지우는 왕복은 DB 쓰기·인증·정리 경로를 한 번에 확인한다. 가장 짧은 실제 트랜잭션을 프리플라이트에 넣는 것이 원칙이다.
의존 서비스 하나가 죽었을 때 전체를 중단하지 않고 해당 의존 테스트만 건너뛰기로 표시 하는 것도 운영 판단이다. 전체 중단은 안전하지만 정보를 0 으로 만든다. 부분 실행은 정보를 남기되, 건너뛴 범위를 결과에 명시해야 "통과했다"는 오해를 막는다.
실무에서 왜 필요한가
자동화가 신뢰를 잃는 가장 빠른 경로는 환경 문제로 생긴 대량 실패다. 테스트 결함은 한두 건이라 개별적으로 처리되지만, 환경 문제는 수백 건을 한꺼번에 빨갛게 만든다. 그런 아침을 세 번 겪으면 사람들은 빨간 배지를 "또 환경일 것"으로 읽기 시작하고, 그 순간부터 진짜 결함도 같이 묻힌다. 환경 운영은 자동화의 신뢰도를 지키는 일 이고, 신뢰도가 떨어진 자동화는 아무리 커버리지가 넓어도 결정에 쓰이지 않는다.
좌표 기록은 조사 시간에 직접 작용한다. 좌표가 없으면 "어제와 오늘이 왜 다른가"가 추측 게임이 되고, 조사는 몇 시간에서 며칠로 늘어난다. 좌표가 있으면 두 실행의 차이를 기계적으로 비교해 후보를 몇 개로 좁힐 수 있다. 이 차이는 테스트 개수와 무관하게 일정하게 돌아오는 수익이다.
데이터 수명 정책은 조용히 쌓이는 비용을 막는다. 정리 없는 테스트 데이터는 몇 달 뒤 DB 성능으로, 스토리지 비용으로, 그리고 "테스트 계정이 1만 개여서 검색 결과가 이상하다" 같은 엉뚱한 실패로 돌아온다. 처음부터 소유자·수명·정리 책임을 붙이는 비용은 작고, 나중에 소급해 정리하는 비용은 크다.