본문으로 건너뛰기
고급약 40분

코드 리뷰를 테스트 활동으로 운영하기 — 체크리스트와 결함 유형 분류

코드 리뷰를 결함이 기록되고 유형이 집계되는 정적 테스트 활동으로 옮긴다. 결함 이력에서 체크리스트를 뽑는 절차, 역할별 분담, 리뷰 결과를 다음 체크리스트로 되먹이는 순환, 세면 안 되는 리뷰 지표, QA가 코드를 다 읽지 않고도 값을 만드는 네 개입 지점을 다룬다.

  • #코드리뷰
  • #정적테스트
  • #체크리스트
  • #결함유형분류
  • #리뷰지표
  • #관측가능성

개념

리뷰는 도구가 아니라 테스트 레벨 하나다

레슨 1~4는 도구가 숫자를 만드는 과정이었다. 마지막 레슨은 사람이고, 이 자리를 "문화"나 "매너"로 두면 아무것도 관리되지 않는다.

코드 리뷰는 실행 없이 결함을 검출하는 정적 테스트다. 테스트라고 부르면 질문이 따라붙는다 — 무엇을 검출 대상으로 삼는가(체크리스트), 검출한 것을 어떻게 기록하는가(결함 유형 분류), 검출력이 올라가는가(지표). 이 레슨은 그 셋을 코드 리뷰에 붙이는 일이다.

리뷰 기법 자체 — 체크리스트 기반 읽기·퍼스펙티브 기반 읽기(PBR)의 정의와 절차 — 는 s2-m58-l02가 요구사항·설계 문서 를 대상으로 이미 다뤘으니 반복하지 않는다. 리뷰 유형과 테스트웨어 리뷰 기준은 s2-m43-l02가 자리다. 여기 대상은 프로덕션 코드 리뷰의 운영 이다.

동적 테스트가 구조적으로 잡기 어려운 것

리뷰가 값을 내는 이유는 "사람이 꼼꼼해서"가 아니다. 동적 테스트에 원리적 사각지대가 있어서 다. 커버리지 90%·뮤테이션 점수 80%인 코드에도 다음이 남는다.

결함 유형왜 실행으로 안 잡히나리뷰에서는 어떻게 보이나
경계 누락개발자가 놓친 케이스는 테스트에도 없다if (amount > limit) 에 "같을 때는?"을 물으면 드러난다
오류 처리 부재정상 경로만 실행되면 초록색이다catch 가 삼키는지, 재시도 상한이 있는지 보인다
동시성 가정단일 스레드 테스트는 레이스를 만들지 않는다"요청 간 공유되는데 HashMap 인가?"는 읽으면 보인다
자원 해제 누락짧은 테스트에서는 릭이 증상을 안 낸다finally·defer 없는 획득 지점은 눈에 띈다
요구사항 오해잘못된 기대값을 잘못된 코드가 만족한다도메인 아는 사람이 "계산 기준이 틀렸다"를 짚는다

핵심은 첫 줄과 마지막 줄이다. 테스트와 코드를 같은 사람이 같은 이해로 만들면 그 오해는 검증되지 않는다. 리뷰는 다른 머리로 이해 자체를 검사한다.

범용 체크리스트는 왜 실패하는가

인터넷에서 가져온 "코드 리뷰 체크리스트 50항목"은 거의 예외 없이 죽는다. ① 길면 아무도 안 본다 — 40항목을 PR마다 돌리는 팀은 없고, 실제로 쓰이는 길이는 7~12항목이다. ② 우리 결함과 무관하다 — "변수명이 의미를 담는가"는 틀리지 않지만, 우리 장애가 타임존과 외부 API 타임아웃이었다면 체크리스트는 그 둘을 물어야 한다. ③ 도구가 이미 보는 것을 또 본다 — 포맷·미사용 변수·널 가능성은 린터·타입 체커가 잡는다(레슨 4).

그래서 체크리스트의 첫 작업은 추가가 아니라 삭제 다. 도구가 보는 것을 빼고, 남은 자리에 사람만 판정할 수 있는 질문을 넣는다.

우리 결함 이력에서 체크리스트를 뽑는 절차

체크리스트는 창작물이 아니라 데이터의 요약 이다. 다섯 단계로 만든다.

1단계 — 모집단. 최근 6개월의 운영 장애 + 리뷰 이후에 발견된 결함. 리뷰에서 이미 잡힌 것은 뺀다. 원하는 것은 리뷰를 빠져나간 결함 이다.

2단계 — 유형 집계. ODC·IEEE 1044를 참고해 8~12개로 줄인다. 20개를 넘으면 집계가 안 되고, 5개 미만이면 질문으로 바꿀 만큼 구체적이지 않다.

3단계 — 상위 유형만. 파레토를 그리면 보통 상위 34개가 전체의 6070%를 만든다.

4단계 — 유형을 질문으로 바꾼다. 이 변환이 핵심이다. 유형 이름은 체크리스트가 될 수 없고, 리뷰어가 코드를 보며 예/아니오로 답할 형태 여야 한다.

집계된 결함 유형건수체크리스트 질문으로 변환
경계값 오류(off-by-one, 등호 누락)14비교 연산자마다 "경계에서 같을 때" 동작이 명시돼 있는가
외부 호출 타임아웃·재시도 누락11네트워크 호출에 타임아웃·재시도 상한이 있는가. 멱등한가
시간·타임존 처리9날짜 경계 계산에 타임존이 명시됐는가. 서버 로컬 시간을 쓰는가
예외 삼킴·오류 정보 유실8catch 가 원인을 보존하고 상위에 신호를 주는가
자원 미해제5커넥션·스트림·락이 실패 경로에서도 해제되는가

5단계 — 만료. 분기마다 검토해 최근 두 분기 동안 그 질문으로 잡힌 결함이 0건이면 항목을 내린다. 이유는 둘뿐이다 — 팀 습관이 됐거나 도구가 대신 잡게 됐거나. 상한을 12개로 못 박으면 새 항목이 낡은 항목을 밀어낸다.

역할별 분담 — 같은 코드를 네 사람이 다르게 본다

한 PR을 네 사람이 같은 눈으로 읽으면 세 사람 몫이 낭비된다. 퍼스펙티브를 나눈다는 아이디어는 PBR과 같지만, 코드 리뷰에서는 역할마다 접근 가능한 정보와 판정 권한이 다르다.

역할무엇을 보는가전형적인 질문
작성자셀프 리뷰. 자기 diff를 남처럼 읽고 위험 지점을 PR 설명에 적는다이 변경의 위험은 어디인가. 어디를 봐 달라고 해야 하나
동료 개발자정확성·설계·유지보수성. 코드 전체를 읽는 유일한 역할이 추상화가 맞나. 중복인가. 동시 접근에 안전한가
테스터 / QA검증 가능성·경계·오류 처리·관측 가능성·영향 범위이 경계는 테스트되나. 실패하면 어떻게 아나. 회귀 위험은
도메인 전문가규칙의 정합성. 코드 문법은 안 봐도 된다이 정산 기준이 계약서와 같은가. 이 예외는 존재하나

이 넷 앞에 자동 검사(포맷·린트·타입·SAST·커버리지)가 한 겹 더 있다. 운영 규칙 하나: 자동 검사가 빨간 PR은 사람이 읽지 않는다. 기계가 잡을 것을 사람이 먼저 읽으면 가장 비싼 자원을 낭비한다.

나쁜 코멘트와 좋은 코멘트

같은 결함을 지적하는 코멘트도 형태에 따라 결함으로 기록되거나 감정으로 소비된다.

✗ "이 부분 좀 이상한데요?"      → 무엇이 어떻게 이상한지 없다
✗ "LGTM 👍" (400줄 결제 로직)  → 무엇을 확인했는지 없다
✗ "변수명을 camelCase로"       → 린터 룰의 자리다
 
✓ "amount == limit 일 때 통과인지 거절인지 어느 쪽인가요? 정책
   문서는 '한도 이하 허용'인데 여기는 초과 비교입니다. 의도라면
   경계 케이스 테스트로 고정해 두죠."
   → 결함 후보 + 근거(문서) + 재발 방지(테스트) 한 세트
 
✓ "[필수] 이 호출에 타임아웃이 없습니다. 상대가 멈추면 워커 풀이
   고갈됩니다(ISSUE-3102와 같은 형태)."   → 영향 + 이력 + 심각도
 
✓ "[질문] 이 캐시가 요청 간 공유되나요? 그렇다면 쓰기 경로에
   동기화가 필요합니다."   → 단정하지 않고 확인한다

[필수] · [제안] · [질문] 세 접두어 만으로 병합 대기가 줄어드는 팀이 많다. 작성자가 "무엇을 고쳐야 병합되는가"를 즉시 알기 때문이다.

리뷰에서 나온 것을 결함으로 기록한다

리뷰 코멘트는 대개 PR과 함께 묻힌다. 병합되면 아무도 다시 보지 않으니 리뷰가 무엇을 잡았는지 데이터가 없고, 그러면 체크리스트를 데이터로 만들 수 없다. [필수] 코멘트에만 라벨 하나를 붙이면 된다.

boundary        경계·등호·off-by-one     consistency  부분 실패 후 상태 불일치
error-handling  예외 삼킴·오류 전파 누락  security     권한·입력 검증·비밀값 노출
concurrency     공유 상태·락·원자성 가정  testability  검증 불가·관측 불가
resource        해제 누락·누수            domain-rule  도메인 규칙 오해

분기마다 집계하면 셋이 나온다. ① 상위 유형 — 다음 체크리스트의 재료. ② 유형별 검출 단계 — 리뷰에서도 잡히고 운영에서도 터지는 유형은 질문이 모호하다는 신호다. ③ 유형이 몰린 모듈 — 한 유형이 한 모듈에 집중되면 리팩터링이 답이다.

이게 이 레슨의 순환이다. 결함 이력 → 체크리스트 → 리뷰 → 분류된 결함 → 갱신된 체크리스트. 두세 바퀴 돌면 체크리스트가 우리 팀의 약점 지도가 된다.

리뷰 지표 — 무엇을 세고 무엇을 세면 안 되는가

리뷰 측정은 세기 쉬운 것을 세다가 망한다.

세도 되는 것왜
유형별 유출 결함 수(리뷰 이후 단계)리뷰의 목적 자체를 본다
리뷰 커버리지 — 리뷰를 거친 변경의 비율우회 경로(핫픽스 직접 푸시)를 드러낸다
첫 리뷰까지의 대기 시간병목인지 본다. 개인 평가가 아닌 흐름 지표다
PR 크기 분포큰 PR이 리뷰 형식화의 가장 큰 원인이다
체크리스트 항목별 검출 건수어떤 항목이 일하는지 본다. 만료 근거
목표로 삼으면 안 되는 것무슨 일이 벌어지나
리뷰어당 코멘트 수오타·취향 지적이 쏟아지고 경계 질문은 그대로다
리뷰 속도(시간당 PR 수)LGTM 이 늘어난다. 지표는 좋아지고 검출력은 0이 된다
개인별 결함 지적 순위·승인율작성자는 PR을 쪼개 숨기고, 리뷰어는 쉬운 것만 찾는다

원칙 한 줄: 리뷰 지표는 흐름과 결과에 붙이고 사람에게 붙이지 않는다. 개인에게 붙는 순간 지표는 측정 대상이 아니라 게임 대상이 되고, 게임하기 가장 쉬운 변수가 하필 코멘트 수와 속도다.

QA가 코드를 다 읽지 않고도 기여하는 법

여기가 이 레슨의 실용적 핵심이다. QA가 코드 리뷰에서 빠지는 이유는 거의 언제나 같다 — "코드를 개발자만큼 못 읽으니 낄 자리가 없다." 틀린 전제다. 코드 전체를 읽는 역할은 동료 개발자 몫이고, QA의 네 개입 지점은 전체 읽기를 요구하지 않는다.

① 테스트 코드를 리뷰한다. 테스트 코드는 QA의 전문 영역이고 볼 것도 정해져 있다 — 어설션이 실제 결과를 검증하는가(assertNotNull 만 있으면 뮤턴트가 다 살아남는다, 레슨 3), 이름이 조건과 기대를 담는가, 경계 케이스가 있는가, 공유 상태·순서 의존·sleep 이 있는가, 실패 메시지가 원인을 말하는가. diff의 테스트 파일만 봐도 된다.

② 관측 가능성을 요구한다. 릴리스 후 "무슨 일이 있었는지 알 수 없는" 상태는 리뷰에서 막을 수 있는 결함이다.

- 이 실패 경로에 로그가 있습니까. 식별자(주문 ID·사용자 ID)가 담깁니까
- 상관 ID(correlation ID)가 하위 서비스까지 전달됩니까
- 이 분기 중 어느 쪽으로 갔는지 운영에서 구분할 수 있습니까
- 이 재시도가 몇 번 일어났는지 세는 지표가 있습니까
- 로그에 비밀값·개인정보가 들어가지 않습니까

관측 가능성은 테스트 가능성의 운영판이다. 못 보는 것은 검증할 수 없고, 리뷰가 그것을 요구할 마지막 시점이다.

③ 경계 조건을 질문한다. 코드 전체를 이해하지 못해도 비교 연산자와 조건식은 찾을 수 있다. >·>=·!=·&&·||, 숫자 리터럴, 시간 계산, 컬렉션 인덱스. 각각에 "경계에서 어떻게 되나", "빈 값·0·음수·최댓값이면", "타임존이 다르면"을 묻는다. 대답이 코드에 없으면 결함 후보이고, 있으면 그게 테스트 케이스다.

④ 변경 영향 범위를 확인한다. diff가 건드린 함수가 어디서 호출되는가 는 IDE의 호출자 찾기나 grep 으로 몇 분이면 나온다. 이것만으로 회귀 범위를 정하고 "A·B·C 화면에 영향이 있는데 B의 회귀 케이스가 없다"를 남길 수 있다.

정리하면 QA의 리뷰 산출물은 코드 수정 요구가 아니라 세 목록 이다 — 답을 못 얻은 경계 질문(=결함 후보), 추가할 테스트 케이스, 회귀 범위. 코드 전체를 읽지 않아도 만들 수 있고, 리뷰 질문이 그대로 테스트 케이스가 되는 것 이 QA 참여의 가장 큰 배당이다.

실무에서 왜 필요한가

리뷰는 대부분의 조직에서 이미 강제돼 있다. 브랜치 보호 규칙에 승인 1건이 걸려 있고 모든 PR이 그 관문을 지난다. 그런데 그 관문이 무엇을 잡고 있는지 아는 팀은 드물다.

그래서 두 실패가 반복된다. 형식화 — 승인율은 100%인데 유출 결함 유형이 6개월째 그대로인 상태. 과부하 — 리뷰가 병목이 되어 PR이 3일씩 대기하다 팀이 승인 요건을 없애는 상태. 체크리스트도 분류도 지표도 없으면 둘 다 감지할 수 없다.

QA에게는 이 빈자리가 기회다. 리뷰를 계측 가능한 테스트 활동으로 만드는 역할은 코드를 가장 잘 읽는 사람이 아니라 결함 데이터를 가장 잘 읽는 사람에게 있다.

채용시장에서의 위치

국내 공고에서는 "코드 리뷰 문화 정착", "결함 분석 및 재발 방지"로 표현되고 시니어 QA·QA 리드 요건에 자주 들어간다. 약한 답은 "코드 리뷰에 참여했습니다"이고, 강한 답은 순환을 설명한다 — "운영 장애 6개월치를 8개 유형으로 집계해 상위 4개를 9항목 체크리스트로 만들고, 리뷰 코멘트에 유형 라벨을 붙여 분기마다 갱신했다. 타임존 유출 결함이 9건에서 1건으로 줄어 항목 2개를 만료시켰다." ISTQB CTAL-TTA에서도 정적 테스트와 리뷰 체크리스트가 정면 주제다.

실습 과제

과제 1 — 결함 이력에서 체크리스트 v1 뽑기 (35분)

결함 관리 시스템에서 리뷰 이후 단계에 발견된 결함 30건 이상 을 뽑는다(없으면 장애 회고 기록).

  1. 유형 8~12개를 정의하고 30건을 분류한다. 애매한 건은 판정 근거를 적는다
  2. 파레토를 그려 상위 60% 이상을 덮는 유형만 남긴다
  3. 남은 유형을 예/아니오로 답할 수 있는 질문 으로 바꾼다
  4. 린터·SAST 룰 목록과 대조해 도구가 이미 잡는 질문을 지운다
  5. 7~12항목으로 확정하고 각 항목에 만료 검토일(다음 분기)을 적는다

과제 2 — 같은 PR을 네 역할로 리뷰하기 (30분)

실제 PR 하나(200줄 이하)를 골라 네 사람이 역할을 나눠 리뷰한다. 혼자 하면 역할마다 타이머를 8분씩 두고 바꿔 쓴다.

  1. 작성자·동료 개발자·테스터·도메인 전문가 관점으로 각각 코멘트를 쓴다
  2. 모든 코멘트에 [필수]·[제안]·[질문] 접두어와 유형 라벨을 붙인다
  3. 역할별로 몇 건을, 어떤 유형을 찾았는지 표로 만들고 중복도 표시한다
  4. 테스터 산출물에서 테스트 케이스로 전환할 질문 을 골라 케이스 제목으로 적는다

검출 유형이 전부 겹치면 분담이 실패한 것이다. 원인은 대개 PR 설명에 위험 지점이 없어 모두가 처음부터 읽었기 때문이다.

과제 3 — QA 리뷰 체크리스트와 관측 가능성 요구서 (25분)

코드를 전부 읽지 않는다는 전제로 QA용 리뷰 절차 한 장 을 만든다.

  1. 테스트 코드 리뷰 항목 5개(어설션·이름·경계·독립성·실패 메시지)를 팀 상황에 맞게 쓴다
  2. 관측 가능성 질문 5개를 우리 로깅·추적 스택 기준으로 다시 쓴다 — 상관 ID 헤더 이름까지
  3. 경계 조건 질문 템플릿을 만든다 — diff에서 무엇을 찾아 무엇을 묻는가
  4. 영향 범위 확인 절차를 명령 수준으로 쓴다(호출자 찾기, 변경 파일과 테스트 매핑)
  5. 최근 PR에 적용해 소요 시간 을 재고 산출물 세 목록을 남긴다

자가 체크리스트

  • 코드 리뷰를 정적 테스트로 규정하고, 동적 테스트가 못 잡는 결함을 넷 이상 들 수 있다
  • 범용 체크리스트가 실패하는 세 이유를 설명하고 항목 수 상한을 말할 수 있다
  • 결함 이력 집계 → 상위 유형 → 질문 변환 → 도구 중복 제거 → 만료의 5단계를 수행하고, 역할별 분담을 설계할 수 있다
  • 코멘트를 [필수]·[제안]·[질문] 로 나누고 유형 라벨을 붙여 집계해 다음 체크리스트로 되먹일 수 있다
  • 세도 되는 리뷰 지표와 안 되는 지표를 구분하고, 코멘트 수·속도를 목표로 삼았을 때의 왜곡을 예측할 수 있다
  • 코드를 다 읽지 않고도 테스트 코드 리뷰·관측 가능성 요구·경계 질문·영향 범위 확인으로 기여할 수 있다

흔한 실수

  • 체크리스트를 계속 더한다. 30개가 되면 아무도 쓰지 않는다. 상한을 정해 새 항목이 낡은 항목을 밀어내게 한다.
  • 도구가 잡는 것을 체크리스트에 둔다. 포맷·미사용 변수·널 가능성은 린터의 일이다. 여기 주의력을 쓰면 경계 조건을 볼 여력이 없다.
  • 코멘트 수와 리뷰 속도를 목표로 삼는다. LGTM 은 속도 지표를 완벽히 만족시키며 검출력을 0으로 만든다.
  • 지표를 개인에게 붙인다. 리뷰가 평가 도구가 되면 [필수] 가 사라지고 PR이 쪼개져 숨는다.
  • 리뷰 질문을 테스트로 바꾸지 않는다. 답을 얻은 경계 케이스는 그대로 테스트 케이스다. 기록하지 않으면 다음 릴리스에 같은 질문을 또 한다.

참고 자료

  • ISO/IEC 20246 — 작업 산출물 리뷰 국제 표준. 리뷰 유형·역할·절차. 판·조항은 원문 확인
  • IEEE 1044 — 소프트웨어 이상(anomaly) 분류 표준. 결함 유형 체계의 출발점. 최신판은 원문 확인
  • Google Engineering Practices — Code Review — 리뷰어·작성자 가이드, PR 크기와 응답 시간 원칙
  • ISTQB CTAL-TTA 학습 목표 — 정적 테스트·리뷰 체크리스트. 최신 버전은 istqb.org 원문 확인
  • 레슨 s2-m58-l02 — 리뷰 기법(체크리스트 기반 읽기·PBR)의 정의와 절차. 문서 리뷰 대상
  • 레슨 s2-m43-l02 — 테스트웨어 리뷰 기준과 코멘트 3분류
  • 레슨 s3-m53-l04 — 정적 분석이 잡는 것과 못 잡는 것. 체크리스트에서 도구 중복을 지우는 기준

낯선 말 풀이

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

PR — 풀 리퀘스트
"내가 고친 코드를 본체에 합쳐 달라"고 올리는 요청. 합치기 전에 사람들이 보고 의견을 단다.
API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
IEEE — 전기전자기술자협회
전기·전자·컴퓨터 분야의 세계 최대 기술자 단체. 규격도 만든다.
QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
SAST — 정적 보안 분석
프로그램을 돌리지 않고 코드 자체를 읽어 위험한 부분을 찾는 검사.
IDE — 통합 개발 환경
코드를 쓰고 돌려 보고 오류를 찾는 일을 한 창에서 하게 해 주는 프로그램.
ISTQB — 국제 소프트웨어 테스팅 자격 위원회
테스터 자격 시험을 만들고 용어를 정리하는 국제 단체.
ID — 식별자
무엇 하나를 다른 것과 구별하기 위해 붙인 이름이나 번호.
ISO/IEC — 국제표준화기구·국제전기기술위원회 공동 표준
두 국제 단체가 함께 낸 표준이라는 표시. 뒤에 붙는 숫자가 문서 번호다.

용어집 전체 보기