본문으로 건너뛰기
중급약 28분

Git으로 QA 업무 프로세스 개선하기: 브랜치 전략부터 결함 유입 추적까지

Git을 읽을 줄 아는 것에서 업무에 쓰는 단계로 넘어간다. 브랜치 전략에 테스트를 맞추고, PR을 게이트로 만들고, bisect로 결함이 언제 들어왔는지 찾아낸다.

  • #Git
  • #브랜치전략
  • #PR게이트
  • #git bisect
  • #릴리즈추적

개념

읽을 줄 아는 것과 쓰는 것은 다르다

레슨 7에서 커밋·브랜치·PR·diff를 배웠고, 레슨 8에서 diff로 리스크를 읽는 법을 익혔다. 거기까지가 Git 문해력 이다.

그런데 Git은 QA에게 읽기 도구만이 아니다. 업무 프로세스를 바꾸는 도구 이기도 하다. 이런 질문들에 Git이 답을 준다.

  • 이 버그는 언제부터 있었나? 어느 변경에서 들어왔나?
  • 이번 릴리즈에 정확히 무엇이 들어가나?
  • 저 핫픽스가 다음 정식 릴리즈에도 반영됐나?
  • 이 파일이 바뀌면 어떤 테스트를 돌려야 하나?
  • 우리 테스트 케이스는 어느 버전 기준 인가?

이 질문들에 매번 개발자에게 물어보는 QA와, 스스로 답을 찾는 QA는 일하는 방식이 다르다. 이 레슨은 그 차이를 만드는 도구들을 다룬다.

브랜치 전략이 테스트 전략을 결정한다

가장 먼저 알아야 할 것은 조직의 브랜치 전략 이다. 이게 "언제 무엇을 테스트하는가"를 사실상 결정하기 때문이다.

전략구조QA가 붙는 지점특징
Git Flowdevelop / release / main + featurerelease 브랜치에서 집중 테스트릴리즈 주기가 길다. QA 기간이 명확
GitHub Flowmain + feature 브랜치PR 단계에서 검증배포가 잦다. PR 게이트가 핵심
Trunk-basedmain 중심, 짧은 브랜치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.js

git blame — 이 줄은 누가, 언제, 어느 커밋에서

git blame -L 40,60 src/payment/coupon.js

blame을 "누구 탓인지 찾는 도구"로 쓰면 안 된다. 모듈 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이면 bad

QA에게 이게 왜 중요한가. 결함 리포트에 "유입 커밋: 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에서 배운 "결함은 일찍 발견할수록 싸다"가 프로세스로 구현되는 지점이다.

실습 과제

과제 1 — 우리 조직의 브랜치 전략 진단 (15분)

다음을 확인하고 적는다.

항목우리 조직
브랜치 전략(Git Flow / GitHub Flow / trunk-based / 기타)
배포 주기
QA가 개입하는 시점
"테스트 기간"이 존재하는가
기능 플래그를 쓰는가

그리고 판단한다 — 전략과 QA 개입 시점이 서로 맞는가?

해설 보기

가장 흔한 불일치는 배포는 잦아졌는데 QA 프로세스는 그대로인 경우 다. 주 2회 배포하면서 릴리즈마다 3일짜리 회귀를 돌리려 하면 QA가 병목이 된다.

이때 답은 "테스트를 줄이자"가 아니라 "테스트를 앞으로 옮기자" 다. PR 단계에서 자동화가 돌게 하고, 사람 검증은 위험이 큰 변경에만 집중한다. 모듈 4의 리스크 기반 테스트가 여기서 쓰인다.

기능 플래그를 쓰지 않는데 trunk-based를 한다면 그것도 신호다. 배포와 노출을 분리할 수단이 없으면, 덜 검증된 것이 곧바로 사용자에게 간다.

과제 2 — PR 템플릿 설계하기 (20분)

우리 팀에서 쓸 PR 템플릿을 작성한다. 다음을 반드시 포함한다.

  • 개발자가 채우는 QA 참고 섹션(무엇을 물을 것인가)
  • 위험 신호 체크박스(DB 마이그레이션, 계약 변경, 기능 플래그 여부)
  • 자동화 테스트 추가 여부

작성 후 검증한다 — 이 템플릿이 채워진 PR을 받으면, 무엇을 테스트할지 바로 정할 수 있는가?

해설 보기

템플릿을 만들 때 흔한 실수는 항목을 너무 많이 넣는 것 이다. 15개짜리 체크리스트는 아무도 채우지 않거나 전부 체크만 하고 넘어간다.

실무에서 잘 작동하는 크기는 5~7개 다. 그리고 "예/아니오"보다 서술을 요구하는 칸이 하나 있는 것 이 효과가 크다 — "중점적으로 봐야 할 부분"처럼. 체크박스는 형식적으로 채워지지만 서술 칸은 그렇게 하기가 어렵기 때문이다.

과제 3 — bisect 실습 (20분)

Git 저장소에서 실제로 해본다(연습용 저장소도 좋다).

  1. 지금 상태에서 확인 가능한 동작을 하나 고른다
  2. 그 동작이 정상이던 과거 커밋·태그를 찾는다
  3. git bisect 로 변화 지점을 찾는다
  4. 몇 번의 확인으로 찾았는지 기록한다

찾은 뒤 그 커밋의 PR을 열어 왜 그렇게 바꿨는지 확인한다.

과제 4 — 릴리즈 범위를 스스로 뽑기 (15분)

최근 두 릴리즈 태그(또는 두 시점) 사이를 분석한다.

git log <이전>..<현재> --oneline | wc -l          # 커밋 수
git diff --name-only <이전>..<현재> | wc -l        # 변경 파일 수
git diff --stat <이전>..<현재> | tail -1           # 변경량
git diff --name-only <이전>..<현재> | cut -d/ -f1-2 | sort | uniq -c | sort -rn

마지막 명령의 결과로 디렉터리별 변경 순위 를 만들고, 그 순위에 따라 회귀 테스트 배분을 제안한다.

자가 체크리스트

  • 브랜치 전략 세 가지를 구분하고, 각각에서 QA가 붙는 지점을 말할 수 있다
  • 배포가 잦아질 때 테스트를 앞으로 옮긴다는 것이 무슨 뜻인지 설명할 수 있다
  • PR 템플릿에 넣을 QA 항목을 설계할 수 있다
  • CODEOWNERS로 위험 영역의 리뷰를 강제하는 방법을 안다
  • git log -S 로 특정 코드가 언제 들어왔는지 찾을 수 있다
  • git blame 의 목적이 사람이 아니라 맥락이라는 점을 설명할 수 있다
  • git bisect 로 결함 유입 커밋을 이진 탐색으로 찾을 수 있다
  • 결함 리포트에 유입 커밋을 적는 것이 왜 가치 있는지 말할 수 있다
  • 두 태그 사이의 변경 파일·변경량으로 회귀 범위를 산정할 수 있다
  • 커밋 접두어별로 필요한 테스트 성격을 판단할 수 있다
  • refactor 커밋에 등가성 확인이 필요한 이유를 설명할 수 있다
  • QA 산출물 중 저장소에 둘 것과 테스트 관리 도구에 둘 것을 구분할 수 있다
  • 변경 경로 기반 테스트 선택의 이점과, 숨은 결합이라는 한계를 말할 수 있다
  • 핫픽스가 다음 릴리즈에서 되살아나는 사고를 Git 명령으로 예방할 수 있다

흔한 실수

  • 브랜치 전략 변경 논의에 QA가 빠진다. 그건 개발 방식이 아니라 테스트 프로세스의 변경이다.
  • 배포는 잦아졌는데 테스트 방식은 그대로 둔다. QA가 병목이 된다. 검증을 PR 단계로 앞당긴다.
  • PR 템플릿에 항목을 너무 많이 넣는다. 5~7개가 실제로 채워지는 크기다.
  • git blame 을 책임 소재 파악에 쓴다. 목적은 맥락이지 사람이 아니다.
  • "언제부터 안 됐는지"를 손으로 되돌려가며 찾는다. bisect는 1,000개 커밋을 10번 확인으로 좁힌다.
  • 릴리즈 범위를 매번 물어본다. 태그 사이 diff로 스스로 확인할 수 있다.
  • refactor 커밋을 안전하다고 가정한다. "동작 변화 없음"은 검증 대상이다.
  • 테스트 자산을 전부 위키에만 둔다. 코드와 함께 움직여야 하는 것은 저장소에 두어야 함께 리뷰되고 함께 롤백된다.
  • 경로 기반 테스트 선택 규칙을 만들고 방치한다. 숨은 결합은 경로로 잡히지 않는다. 정기적으로 재검토한다.
  • 핫픽스 반영 여부를 확인하지 않는다. 다음 정식 릴리즈에서 버그가 되살아난다.

참고 자료

낯선 말 풀이

이 레슨에 나온 약어입니다. 본문에서도 처음 나올 때 점선으로 표시해 두었습니다 (제목과 링크 글자는 그대로 둡니다).

PR — 풀 리퀘스트
"내가 고친 코드를 본체에 합쳐 달라"고 올리는 요청. 합치기 전에 사람들이 보고 의견을 단다.
QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
AI — 인공지능
사람이 규칙을 일일이 적지 않아도 데이터에서 규칙을 찾아 판단하는 소프트웨어.
CI — 지속적 통합
코드를 올릴 때마다 자동으로 합쳐서 빌드하고 시험까지 돌리는 방식.
DB — 데이터베이스
데이터를 넣어 두고 꺼내 쓰는 저장소. 보통 표 여러 개가 서로 연결된 형태다.

용어집 전체 보기