반복 결과와 실패 양상
110분 안팎
학습 목표
반복 통과율과 실패 원인을 구분합니다.
개념
통과율은 출발점입니다
동아리 서비스의 프로필 저장 검사가 열 번 중 여덟 번 통과했습니다. 두 번의 실패를 지우고 성공 로그만 전달하면 다음 변경에서 같은 불안정성이 돌아옵니다. 시니어 QA는 평균을 보기 전에 모든 시도의 조건과 첫 실패를 보존하도록 안내합니다. 이번 레슨은 실행 기록을 집계하여 어떤 조건을 다음 재현에 사용할지 결정하는 연습입니다.
불안정 테스트는 같은 검증 대상과 통제하려는 조건에서 통과와 실패가 섞이는 검사입니다. 다만 앱 버전이 달랐거나 요구사항이 바뀌었다면 두 결과를 같은 반복으로 묶을 수 없습니다. 테스트 문제라는 이름을 먼저 붙이지 않고 앱 결함, 시간 의존, 데이터 간섭, 환경 차이라는 가설을 열어 둡니다. 재시도 후 성공한 사실만으로 제품이 정상이라고 결론 내리지 않습니다.
이번 모듈의 브라우저 실습은 Node 방식 JavaScript로 유효 JSON을 표준 입력에서 읽습니다. 실제 사이트나 DOM을 호출하지 않습니다. 로컬 자료는 압축을 푼 실습 루트에서 bash check.sh로 실행하며 Node와 Python 3를 사용합니다. 합성 계정만 다룹니다. 모든 실습의 출력은 관찰한 범위에 한정하고 실제 브라우저 결과와 구분하여 기록합니다.
원시 시도의 단위를 정합니다
실행 기록 한 행에는 seed, delay, failure를 넣습니다. seed는 재현 입력을 정하는 정수이고 delay는 가상 시계가 진행해야 할 tick 수입니다. failure가 NONE이면 해당 시도가 통과한 것입니다. SAVE_NOT_READY와 STATE_LEAK는 각각 저장 전 관찰과 공유 상태 간섭의 증거 코드입니다. 이름은 조사 단서이며 실제 운영 원인을 확정한 진단 코드가 아닙니다.
실습은 하나의 검사 결과를 한 행으로 주는 계약입니다. 같은 seed가 여러 번 나와도 각 행은 별도 시도이므로 제거하지 않습니다. 세 번 실패한 seed를 한 건으로 줄이면 반복 횟수와 통과율이 바뀝니다. 반대로 여러 검사 결과를 한 행에 혼합하면 무엇을 분모에 넣었는지 설명하기 어렵습니다. 집계 전에 행의 의미부터 합의하는 습관을 익힙니다.
빈 입력 목록은 실행되지 않은 상태입니다. 결과를 100%로 채우면 미검증을 통과로 바꾸게 됩니다. 브라우저 출력은 total=0 pass=0 fail=0 rate=NA로 정합니다. NA는 분모가 없다는 표시입니다. 실제 보고서에도 실행 없음과 실패 없음은 다른 상태로 남깁니다. 실행 대상이 선택되었지만 환경이 없어 건너뛴 경우에는 별도 SKIPPED 이유를 기록합니다.
조건별로 나눠 관찰합니다
전체 실패율이 같아도 특정 지연에서만 실패하는지 모든 조건에서 실패하는지는 대응이 다릅니다. delay와 failure의 조합으로 실패 행을 묶으면 지연 2의 SAVE_NOT_READY 두 건과 지연 0의 STATE_LEAK 한 건을 분리할 수 있습니다. 통과 행은 실패 그룹에 넣지 않지만 전체 분모에는 포함합니다. 원시 기록을 유지한 채 작은 집계표를 만들어 조사 순서를 정합니다.
지연이 길 때만 실패하면 기다린 상태와 제한 시간을 조사합니다. 정방향에서 실패하고 역방향에서 통과하면 계정 또는 저장값을 공유하는지 봅니다. 특정 실행 환경에서만 실패하면 런타임, 브라우저, 앱 설정을 비교합니다. 이 관찰은 원인과의 관련성을 보여 줍니다. 지연을 고정한 재현과 한 가지 수정 후 재검증이 있어야 인과 설명이 강해집니다.
seed는 임의 문자열이 아니라 재현 절차의 입력입니다. 이번 모형은 seed를 지연과 순서로 변환하므로 같은 코드와 seed이면 같은 조건이 나옵니다. 실제 테스트에서 seed만 보관해도 재현이 보장되는 것은 아닙니다. 시간대, 데이터 초기 상태, 실행 버전과 병렬도처럼 seed 바깥의 조건을 함께 남겨야 다른 작업자가 같은 실패를 관찰할 가능성이 높아집니다.
집계 규칙을 코드로 옮깁니다
먼저 records 길이를 total에 넣고 NONE인 행 수를 pass로 셉니다. fail은 total에서 pass를 뺀 값입니다. 통과율은 pass를 total로 나눈 뒤 100을 곱하고 toFixed(1)로 소수 한 자리를 출력합니다. 전부 통과한 두 건도 100이 아니라 100.0으로 씁니다. 표와 출력의 표현을 맞춰 두면 자동 채점과 보고서 비교가 단순해집니다.
실패 그룹은 Map에 저장하며 문자열 key는 delay와 failure를 붙여 만듭니다. 여기서는 지연이 0 이상의 정수이고 실패 코드는 제공된 세 값으로 제한되어 구분자가 모호하지 않습니다. 일반적인 자유 문자열을 묶을 때에는 튜플 직렬화 등 충돌을 피하는 방법을 선택합니다. 집계 편의를 위해 기록의 잘못된 타입을 조용히 숫자로 변환하지 않습니다.
출력 순서는 delay 숫자 오름차순, 같은 지연이면 failure 코드의 사전식 순서입니다. 입력 순서대로 출력하면 같은 관찰의 기록 순서가 바뀔 때 보고서 차이가 생깁니다. 숫자 비교를 생략한 문자열 정렬은 지연 10을 지연 2보다 앞에 둘 수 있습니다. 코드는 a.delay와 b.delay 차이를 먼저 비교하고 그 값이 같을 때만 실패 코드를 비교합니다.
비교가 가능한 반복을 설계합니다
수정 전후 반복에는 같은 seed 목록과 같은 버전을 식별할 수 있는 기록을 붙입니다. 수정 전은 열 개 중 몇 개가 실패했는지와 오류 코드를 남기고 수정 후는 같은 조건에서 최초 시도가 통과했는지 봅니다. 새 seed 탐색은 그 다음에 추가합니다. 실패 seed를 빼거나 재시도 결과만 남기는 방법은 수정 효과를 비교하기 어렵게 합니다.
열 번 통과는 열 조건에서 관찰한 증거입니다. 모든 지연과 모든 브라우저에서 안정적이라는 뜻은 아닙니다. 이번 모형의 지연은 0~3이고 실제 네트워크와 렌더링 시간은 검사하지 않습니다. 보고서의 첫 문장에 가상 시계 조건에서 통과했다고 적습니다. 다음 레슨에서는 지연 4를 넣어 최대 대기가 끝나는 경계도 확인하여 무한 대기를 막습니다.
집계가 맞는지 확인할 때에는 하나만 실패한 기록과 같은 실패가 두 번 나온 기록을 손으로 셉니다. 예를 들어 지연 2의 SAVE_NOT_READY가 두 건이면 count는 2가 되어야 합니다. 모든 행이 NONE인 입력에서는 그룹 줄이 하나도 없어야 합니다. 한 건 실패, 빈 배열, 전부 성공은 평균 계산에서 자주 놓치는 경계이므로 자동 테스트로 남깁니다.
오류 메시지에서 조사 위치를 찾습니다
JSON.parse에서 SyntaxError가 나오면 원인 집계보다 입력 문법을 먼저 확인합니다. 따옴표 누락이나 마지막 쉼표를 보며 실습 계약은 유효 JSON임을 기억합니다. 그룹 수가 기대보다 작으면 중복 seed 제거 코드가 있는지 읽습니다. rate가 NaN 또는 Infinity이면 빈 배열의 나눗셈을 처리하지 않은 것입니다. 오류 종류마다 확인할 코드 위치가 다릅니다.
테스트가 출력 순서만 다르다고 알려 주면 숫자 지연 정렬과 동일 지연의 코드 정렬을 차례로 확인합니다. 예상 총계가 네 건인데 실제 세 건이면 통과 행을 분모에서 제외했는지 조사합니다. 정답 출력에 맞추려고 특정 seed를 하드코딩하지 않습니다. 입력을 바꿔도 동일한 집계 계약이 유지되는 함수를 작성하는 것이 레슨의 완료 기준입니다.
최종 산출물은 집계 코드와 원시 기록, 다음 재현에 사용할 seed 두 개입니다. 저장 준비 실패와 상태 간섭을 각각 대표하는 seed를 고르고 선택 이유를 한 문장씩 씁니다. 배열과 객체를 다루는 일반 문법은 더 읽기로 보완합니다. 여기서는 숫자를 예쁘게 만드는 것보다 집계 결과로 다음 조사 행동을 정할 수 있는지를 평가합니다.
따라하기
분모와 최초 시도를 셉니다
새 파일에 입력하여 실행합니다. 성공한 재시도만 모으지 않고 세 시도를 모두 계산합니다.
const rows=['NONE','SAVE_NOT_READY','NONE']; const pass=rows.filter(x=>x==='NONE').length; console.log('total='+rows.length+' pass='+pass+' rate='+(100*pass/rows.length).toFixed(1));실행 결과
total=3 pass=2 rate=66.7
빈 반복은 NA로 표시합니다
시도가 없는 상태에서 나눗셈을 피합니다.
const total=0,pass=0; console.log(total?(100*pass/total).toFixed(1):'NA');실행 결과
NA
같은 seed의 두 시도를 보존합니다
seed 중복은 제거하지 않습니다. 이 두 행은 같은 조건에서 두 번 실행한 기록입니다.
const rows=[{seed:2,failure:'SAVE_NOT_READY'},{seed:2,failure:'SAVE_NOT_READY'}]; console.log(rows.length); console.log(rows.filter(x=>x.failure!=='NONE').length);실행 결과
2 2
브라우저 집계를 완성합니다
실습에 입력 배열을 넣고 총계와 실패 그룹을 출력합니다. 빈 목록과 지연 10·2의 순서를 함께 확인합니다. 실제 실패 재현 명령은 다음 레슨의 로컬 자료에 있습니다.
확인 문제
실습
유효 JSON 배열을 읽습니다. 각 행은 seed(정수), delay(0 이상 정수), failure(NONE·SAVE_NOT_READY·STATE_LEAK)입니다. 행 하나가 한 시도이며 중복 seed도 셉니다. 첫 줄은 total=N pass=P fail=F rate=R입니다. R은 통과율을 소수 한 자리로 쓰고 빈 배열은 NA입니다. 이후 실패만 delay 숫자 오름차순·failure 코드 사전식 순서로 delay failure count를 출력합니다. 전부 성공이면 그룹 줄은 없습니다.
모범 답안
const fs=require('node:fs');
const rows=JSON.parse(fs.readFileSync(0,'utf8'));
const total=rows.length,pass=rows.filter(r=>r.failure==='NONE').length;
console.log(`total=${total} pass=${pass} fail=${total-pass} rate=${total?(100*pass/total).toFixed(1):'NA'}`);
const groups=new Map();
for(const r of rows){if(r.failure==='NONE')continue;const k=JSON.stringify([r.delay,r.failure]);const g=groups.get(k)||{delay:r.delay,failure:r.failure,count:0};g.count++;groups.set(k,g);}
for(const g of [...groups.values()].sort((a,b)=>a.delay-b.delay||(a.failure<b.failure?-1:a.failure>b.failure?1:0)))console.log(`${g.delay} ${g.failure} ${g.count}`);
더 읽기
면접 질문
- 실행 순서에 따라 결과가 달라지는 테스트를 확인하는 방법을 설명해 주시면 됩니다.