개념
읽을 줄 아는 것과 쓰는 것은 다르다
레슨 7에서 커밋·브랜치·PR·diff를 배웠고, 레슨 8에서 diff로 리스크를 읽는 법을 익혔다. 거기까지가 Git 문해력 이다.
그런데 Git은 QA에게 읽기 도구만이 아니다. 업무 프로세스를 바꾸는 도구 이기도 하다. 이런 질문들에 Git이 답을 준다.
- 이 버그는 언제부터 있었나? 어느 변경에서 들어왔나?
- 이번 릴리즈에 정확히 무엇이 들어가나?
- 저 핫픽스가 다음 정식 릴리즈에도 반영됐나?
- 이 파일이 바뀌면 어떤 테스트를 돌려야 하나?
- 우리 테스트 케이스는 어느 버전 기준 인가?
이 질문들에 매번 개발자에게 물어보는 QA와, 스스로 답을 찾는 QA는 일하는 방식이 다르다. 이 레슨은 그 차이를 만드는 도구들을 다룬다.
브랜치 전략이 테스트 전략을 결정한다
가장 먼저 알아야 할 것은 조직의 브랜치 전략 이다. 이게 "언제 무엇을 테스트하는가"를 사실상 결정하기 때문이다.
| 전략 | 구조 | QA가 붙는 지점 | 특징 |
|---|---|---|---|
| Git Flow | develop / release / main + feature | release 브랜치에서 집중 테스트 | 릴리즈 주기가 길다. QA 기간이 명확 |
| GitHub Flow | main + feature 브랜치 | PR 단계에서 검증 | 배포가 잦다. PR 게이트가 핵심 |
| Trunk-based | main 중심, 짧은 브랜치 | PR + 기능 플래그 뒤에서 검증 | 배포가 매우 잦다. 자동화 의존도 높음 |
전략이 바뀌면 QA의 일하는 방식이 바뀐다.
Git Flow에서는 release 브랜치가 열리는 시점이 곧 "테스트 시작"이고, 그 기간에 회귀를 돌린다. 모듈 4에서 배운 테스트 계획이 잘 맞는 구조다.
반면 trunk-based로 옮기면 "테스트 기간"이라는 것이 사라진다. 매일 여러 번 배포되므로 릴리즈 전에 몰아서 테스트할 수 없다. 이때 QA가 해야 할 일은 두 가지다 — PR 단계로 검증을 앞당기고(모듈 14의 품질 게이트), 기능 플래그로 배포와 노출을 분리 하는 것이다.
실무 감각: 조직이 브랜치 전략을 바꾼다고 하면 QA가 회의에 들어가야 한다. 그건 개발 방식의 변경이 아니라 테스트 프로세스의 변경 이기 때문이다.
PR을 QA 게이트로 만들기
PR은 코드 리뷰만 하는 자리가 아니다. QA가 개입할 수 있는 가장 이른 지점 이다. 세 가지 장치를 쓸 수 있다.
1) PR 템플릿에 테스트 항목 넣기
저장소에 .github/pull_request_template.md 를 두면 PR을 열 때마다
그 내용이 본문에 채워진다.
## 변경 내용
## 테스트
- [ ] 영향 범위를 확인했다 (변경된 모듈과 의존하는 곳)
- [ ] 로컬에서 확인한 시나리오:
- [ ] 자동화 테스트 추가/수정 여부:
- [ ] DB 마이그레이션 포함 여부 (있다면 롤백 가능한가)
- [ ] 기능 플래그 뒤에 있는가
## QA 참고
- 중점적으로 봐야 할 부분:
- 재현 방법:
- 확인이 필요한 예외 상황:'QA 참고' 섹션이 핵심이다. 개발자가 "여기를 봐주세요"를 적어두면 QA의 탐색 시작점이 생긴다. 모듈 5의 탐색적 테스팅에서 차터를 정하는 것과 같은 효과다. 이 한 칸을 추가하는 것만으로 테스트 효율이 눈에 띄게 오르는 팀이 많다.
2) CODEOWNERS로 리뷰를 강제하기
특정 경로가 바뀌면 특정 사람의 리뷰를 필수로 만들 수 있다.
# .github/CODEOWNERS
/tests/ @qa-team
/e2e/ @qa-team
/docs/api/ @qa-team @backend-team
/src/payment/ @payment-team @qa-team결제처럼 위험이 큰 영역의 변경을 QA가 놓치지 않고 보게 되는 구조다. 모듈 4의 리스크 기반 사고를 저장소 설정으로 구현하는 셈이다.
3) 상태 체크를 병합 조건으로
모듈 14(레슨 4)에서 배운 품질 게이트가 PR에 붙는다. 테스트 실패, 커버리지 하락, 린트 오류 시 병합을 막는 설정이다. 여기서 QA가 정할 것은 기술이 아니라 기준 이다 — 무엇이 병합을 막아야 하는가.
결함이 언제 들어왔는지 찾아내기
여기가 이 레슨에서 가장 실용적인 부분이다. "이 버그 언제부터 있었어요?" 라는 질문에 Git이 답을 준다.
git log -S — 특정 코드가 언제 생겼나(pickaxe)
# "MAX_RETRY" 라는 문자열이 추가·삭제된 커밋만 찾는다
git log -S "MAX_RETRY" --oneline
# 특정 파일에서 그 문자열의 변화 이력
git log -S "discountRate" --oneline -- src/payment/git log -p --follow — 파일의 변경 이력을 내용까지
git log -p --follow src/payment/coupon.jsgit blame — 이 줄은 누가, 언제, 어느 커밋에서
git blame -L 40,60 src/payment/coupon.jsblame을 "누구 탓인지 찾는 도구"로 쓰면 안 된다. 모듈 6(레슨 3)의 비난 없는 소통 원칙 그대로다. 목적은 사람이 아니라 맥락 이다 — 그 커밋의 PR을 열면 왜 그렇게 바꿨는지, 어떤 이슈와 연결됐는지가 나온다.
git bisect — 이진 탐색으로 유입 커밋 찾기
가장 강력한 도구인데 가장 덜 알려져 있다. "어느 시점엔 되던 것이 지금은 안 된다" 는 상황에서 쓴다.
git bisect start
git bisect bad # 지금(HEAD)은 깨져 있다
git bisect good v2.3.0 # v2.3.0에서는 정상이었다
# → Git이 중간 커밋으로 이동시킨다. 확인하고 good/bad를 알려준다
git bisect good # 또는 git bisect bad
# ... 반복 ...
# → "abc1234 is the first bad commit" 이 출력된다
git bisect reset커밋이 1,000개여도 10번 정도 확인하면 범인을 찾는다. 로그 2의 1000은 약 10이기 때문이다. 손으로 되돌려가며 찾으면 하루가 걸릴 일이 30분이 된다.
재현 스크립트가 있다면 자동화까지 된다.
git bisect start HEAD v2.3.0
git bisect run ./scripts/reproduce-bug.sh # 0이면 good, 1이면 badQA에게 이게 왜 중요한가. 결함 리포트에 "유입 커밋: abc1234"가 적혀 있는 것과 없는 것은 개발자의 수정 시간이 다르다. 그리고 그 정보는 모듈 6(레슨 5)의 결함 지표 로도 이어진다 — 결함이 유입된 시점과 발견된 시점의 간격이 곧 결함 잔존 기간 이고, 이게 길수록 수정 비용이 크다는 사실을 데이터로 보일 수 있다.
릴리즈에 무엇이 들어가는지 스스로 알기
"이번 릴리즈 범위가 뭔가요?"를 매번 물어보지 않아도 된다.
# 두 태그 사이의 커밋 목록
git log v2.3.0..v2.4.0 --oneline
# 변경된 파일 목록 — 회귀 범위 산정의 근거
git diff --name-only v2.3.0..v2.4.0
# 디렉터리별 변경량 — 어디에 집중할지
git diff --stat v2.3.0..v2.4.0 | sort -k3 -n -r | head -20
# 특정 영역만 바뀐 게 맞는지 확인
git diff --name-only v2.3.0..v2.4.0 -- src/payment/세 번째 명령이 모듈 4(레슨 3)의 커버리지 계획과 직결된다. 변경량이 큰 영역에 테스트를 더 배치하는 판단의 근거가 여기서 나온다. 감이 아니라 숫자로.
네 번째는 릴리즈 노트 검증에 쓴다. "이번엔 결제만 손댔습니다"라는 말이 사실인지 확인하는 방법이다. 실제로 다른 영역이 함께 바뀐 것이 발견되는 경우가 꽤 있다.
커밋 컨벤션이 QA에게 주는 것
팀이 커밋 메시지 규칙(예: Conventional Commits)을 쓰면 QA가 얻는 것이 생긴다.
feat(payment): 카카오페이 결제 수단 추가
fix(auth): 토큰 만료 시 재발급 실패 수정
refactor(order): 주문 상태 계산 로직 분리
perf(search): 색인 조회 캐시 추가
BREAKING CHANGE: 주문 API 응답에서 legacyId 필드 제거여기서 바로 읽어낼 수 있는 것들이다.
| 접두어 | QA가 판단할 것 |
|---|---|
feat | 새 기능 — 신규 테스트 케이스 필요 |
fix | 결함 수정 — 재검증 + 회귀 확인 |
refactor | 동작 변화 없어야 함 — 등가성 확인(모듈 4 레슨 6) |
perf | 성능 개선 — 부작용과 성능 지표 확인 |
BREAKING CHANGE | 계약 변경 — 연동 대상 전부 확인(모듈 12) |
refactor가 특히 중요하다. "동작은 그대로"라는 주장은 검증 대상 이다.
리팩터링 커밋이 많은 릴리즈에서 회귀 테스트를 줄이는 것은 위험하다.
QA 산출물을 저장소에 두기
테스트 케이스와 체크리스트를 위키나 스프레드시트에만 두면 코드와 따로 논다. 일부를 저장소로 옮기면 얻는 것이 있다.
| 저장소에 두기 좋은 것 | 얻는 것 |
|---|---|
| 자동화 테스트 코드 | 당연히 |
| API 테스트 컬렉션·스키마 | 코드 변경과 함께 리뷰된다 |
| 체크리스트(Markdown) | PR에서 함께 검토, 변경 이력 |
| 테스트 데이터 픽스처 | 브랜치와 동기화 |
| AI 프롬프트 템플릿 | 모듈 18b 레슨 10의 프롬프트 버전 관리 |
핵심 이득은 "코드와 같은 브랜치에 산다"는 것 이다. 기능이 바뀌는 PR에서 테스트도 함께 바뀌고, 함께 리뷰되고, 함께 롤백된다. 위키에 있으면 코드만 롤백되고 문서는 남는다.
다만 전부 옮기라는 뜻은 아니다. 모듈 22(레슨 9)에서 다룬 경계선과 같다 — 실행 이력과 회차 관리가 필요한 것은 테스트 관리 도구에 두고, 코드와 함께 움직여야 하는 것만 저장소에 둔다.
변경 경로로 테스트 범위 정하기
CI에서 "어떤 경로가 바뀌었는가"에 따라 다른 테스트를 돌릴 수 있다.
# 결제 코드가 바뀌었을 때만 결제 E2E를 돌린다
on:
pull_request:
paths:
- 'src/payment/**'
- 'src/order/**' # 주문도 결제에 영향을 준다전체 회귀를 매번 돌리면 느리고, 아무것도 안 돌리면 위험하다. 그 사이의 현실적인 답이다. 모듈 14(레슨 5)에서 배운 CI 신호 품질과도 맞물린다.
QA가 정할 것은 매핑 규칙이다. "이 디렉터리가 바뀌면 어떤 테스트가 필요한가"를 아는 사람은 QA다. 그리고 이 규칙에는 함정이 하나 있다 — 코드상 보이지 않는 결합. 서로 다른 모듈이 같은 캐시나 설정을 공유하면 경로만으로는 잡히지 않는다. 그래서 규칙은 정기적으로 재검토해야 한다.
핫픽스와 체리픽 — 조용히 사라지는 수정
운영 장애로 핫픽스를 냈다. main에 직접 반영했다. 그런데 develop 브랜치에는 반영이 안 됐다. 다음 정식 릴리즈에서 그 버그가 되살아난다.
이 사고는 생각보다 흔하고, QA가 잡아낼 수 있다.
# 핫픽스 커밋이 develop에도 들어갔는지 확인
git branch --contains abc1234
# 두 브랜치의 차이 — main에만 있는 커밋
git log develop..main --oneline릴리즈 전 체크리스트에 이 확인을 넣어두면 되살아나는 버그를 막을 수 있다. 모듈 6에서 결함 재오픈을 지표로 관리하는 것과 같은 맥락이다.
실무에서 왜 필요한가
QA가 Git을 업무에 쓰면 개발자에게 물어봐야 알 수 있던 것들을 스스로 알게 된다. 릴리즈 범위, 결함 유입 시점, 핫픽스 반영 여부. 이 정보들이 손에 있으면 테스트 범위를 스스로 판단할 수 있고, 회의에서 근거를 갖고 말할 수 있다.
특히 bisect 는 QA의 결함 리포트 품질을 한 단계 올린다. "3일 전에는
됐는데 지금 안 됩니다"와 "커밋 abc1234에서 유입됐고, 그 PR은 #482입니다"는
개발자가 받는 정보의 양이 다르다. 모듈 6(레슨 2)에서 배운 좋은 결함
리포트에 항목 하나가 더해지는 셈이다.
조직 관점에서는 PR을 게이트로 만드는 것 이 가장 큰 변화다. 릴리즈 직전에 문제를 발견하는 구조에서, 변경이 병합되기 전에 발견하는 구조로 옮겨간다. 모듈 1에서 배운 "결함은 일찍 발견할수록 싸다"가 프로세스로 구현되는 지점이다.