CI 결과 요약
50분 안팎
학습 목표
통과·실패·건너뜀을 서로 다르게 집계합니다.
개념
초록 표시에 실행 건수를 더합니다
안내판 테스트가 모두 건너뛰어졌는데 실패가 0건이라고 해서 배포를 허용하면 검증 없는 변경이 다음 단계로 넘어갑니다. 결과 요약은 통과·실패·건너뜀을 분리하고 실제 실행 건수가 있는지 판단하는 자료입니다. 이번 레슨은 JSON 배열의 상태들을 읽어 수량을 계산하고 배포 가능 여부를 출력합니다. 정책을 작은 함수로 만들면 서비스와 CI 화면이 달라도 같은 입력에 같은 판단을 반복할 수 있습니다.
입력 계약부터 정합니다
표준 입력은 passed, failed, skipped 문자열만 담은 JSON 배열입니다. 대소문자를 임의로 고치거나 알 수 없는 값을 통과로 취급하지 않습니다. 공백만 있거나 입력이 아예 없으면 빈 배열로 간주합니다. JSON 문법이 깨졌거나 배열 대신 객체가 오면 INVALID를 출력합니다. 출력은 한 줄이며 정상 입력에서는 passed, failed, skipped, deploy를 이 순서로 표시합니다. 정해진 계약을 먼저 적으면 테스트 작성과 오류 설명이 분명해집니다.
Python 준비 문법을 확인합니다
sys.stdin.read는 입력 전체를 문자열로 읽습니다. raw.strip은 양 끝 공백을 제거하여 입력이 비었는지 확인합니다. json.loads는 JSON 문자열을 Python 값으로 바꾸며 배열은 list가 됩니다. 사전 counts에는 세 상태를 키로 두고 수량을 0으로 시작합니다. for status in rows는 원소를 하나씩 꺼내고 counts[status] += 1은 해당 수를 하나 늘립니다. 이 네 동작을 이해한 뒤 배포 조건을 붙이면 낯선 문법 때문에 정책을 놓치지 않습니다.
건너뜀은 성공의 다른 이름이 아닙니다
skipped는 그 검사가 실행되지 않았다는 기록입니다. 환경 조건에 맞지 않거나 외부 접속 검사를 제외했을 수 있지만 통과 증거로 합산하지 않습니다. executed는 passed와 failed의 합입니다. passed 1건과 skipped 2건이면 이번 교육 정책에서 실행 증거가 있어 ALLOW가 될 수 있습니다. 그러나 무엇이 제외되었는지는 별도 요약에 남깁니다. 이 규칙은 모든 조직의 배포 정책이라는 주장이 아니라 본 프로젝트에서 정한 최소 관문입니다.
두 조건을 함께 읽습니다
deployable은 failed가 0이고 executed가 0보다 큰 경우에만 참입니다. 두 조건 중 하나라도 거짓이면 BLOCK입니다. 실패 0건과 실행 0건을 혼동하지 않아야 합니다. 빈 배열은 오류 없이 파싱되지만 실행을 입증하지 못하므로 BLOCK입니다. 전체 건너뜀도 같은 이유로 막습니다. 실패가 1건 있으면 통과가 많이 있어도 BLOCK이며 실패율이 낮다는 이유로 허용하는 규칙은 이 계약에 없습니다.
정상 입력과 잘못된 입력의 결과를 나눕니다
BLOCK은 입력이 유효하지만 정책상 진행할 수 없다는 판단입니다. INVALID는 판단에 사용할 입력 자체가 잘못되었다는 뜻입니다. unknown이나 null을 failed로 바꾸면 집계 원인이 섞이고 상위 수집기의 오류를 찾기 어렵습니다. INVALID에서는 임의 수량을 출력하지 않고 입력 계약 위반을 먼저 해결합니다. 실무에서는 오류 로그를 별도 채널에 남길 수 있지만 브라우저 채점의 표준 출력에는 지정한 한 줄만 보냅니다.
문자열 여부를 먼저 검사합니다
입력이 배열이라도 내부에 배열이나 사전이 들어갈 수 있습니다. status가 문자열인지 확인한 뒤 counts에 있는 키인지 검사합니다. 그렇지 않으면 사전 조회 과정에서 의도하지 않은 TypeError가 날 수 있습니다. 이처럼 예상한 구조를 바깥에서 안쪽으로 좁혀 확인하면 알 수 없는 입력도 정해진 INVALID로 처리할 수 있습니다. 값의 의미와 자료형을 둘 다 확인하는 습관은 이후 릴리스 명세 검증에도 그대로 이어집니다.
출력 표기는 판정기와의 약속입니다
정상 결과는 예를 들어 passed=1 failed=0 skipped=1 deploy=ALLOW 형식입니다. Boolean의 True나 False를 그대로 출력하면 사람이 이해해도 채점 계약과 다릅니다. deployable 값을 ALLOW 또는 BLOCK 문자열로 변환해 출력합니다. 수량은 정수여서 0.0으로 나타내지 않습니다. 디버깅을 위한 중간 counts 출력도 제출 코드에서는 제거합니다. 입력 판단이 정확해도 표준 출력에 추가 줄이 있으면 다른 도구가 잘못 읽을 수 있습니다.
경계 사례를 테스트로 남깁니다
최소 테스트는 전부 통과, 실패 포함, 전체 건너뜀, 빈 배열, 빈 입력, 알 수 없는 상태, 잘못된 JSON을 포함합니다. 통과와 건너뜀이 섞인 경우도 넣어 정책을 구체화합니다. 모든 테스트를 정상 입력으로만 만들면 실패 경로를 빠뜨린 구현을 발견하지 못합니다. 기대 결과는 출력 형식까지 함께 비교합니다. 자신이 추가한 경계 사례를 설명할 때 어떤 잘못된 구현이 그 사례에서 드러나는지 말해 봅니다.
실제 보고서의 단위와 연결합니다
브라우저 입력은 이해를 위해 테스트별 상태로 단순화한 형식입니다. 미션은 Maven Surefire XML의 tests, failures, errors, skipped 속성을 읽습니다. 그 경우 executed는 tests에서 skipped를 뺀 값이며 errors도 차단 조건입니다. 보고서가 하나도 없으면 입력이 충분하지 않다는 이유로 실패합니다. 두 입력 형식의 수식을 섞지 말고 먼저 해당 값이 전체 수인지 성공 수인지 구분한 뒤 정책을 적용합니다.
합계가 맞지 않는 보고서를 의심합니다
XML에서 전체 테스트가 1인데 실패가 2라고 하면 의미가 모순됩니다. 미션의 파서는 음수 수량과 failures·errors·skipped 합이 tests를 넘는 경우를 거부합니다. 숫자가 존재한다는 이유만으로 신뢰하지 않고 관계를 검사하는 것입니다. 본 레슨의 배열 집계는 원소를 직접 세므로 그런 모순을 만들지 않습니다. 집계 대상의 형태가 달라지면 데이터 유효성 조건도 달라진다는 점을 이해해야 합니다.
예외를 허용으로 바꾸지 않습니다
try 블록은 JSON 파싱과 입력 검사를 수행하고 ValueError 또는 TypeError이면 INVALID를 출력합니다. except에서 ALLOW를 출력하면 파서가 깨질수록 배포가 쉬워지는 역전이 생깁니다. 오류를 숨기려고 빈 배열로 대체하는 것도 계약을 흐릴 수 있습니다. 허용할 빈 입력은 명시적으로 처리하고, 잘못된 JSON은 오류로 구분합니다. 예외를 넓게 잡는 방식보다 예상 입력 실패만 처리하면 개발 결함이 눈에 띕니다.
starter를 정책 함수로 바꿉니다
시작 코드는 항상 0건 ALLOW를 출력하여 테스트 일부만 우연히 맞거나 모두 실패합니다. 먼저 counts 초기화와 반복 집계를 작성하고 그다음 두 조건을 연결합니다. 마지막으로 입력 유효성과 출력 문자열을 완성합니다. 답안의 함수 이름을 외우기보다 passed 0건, failed 0건, skipped 1건을 손으로 계산하고 자신이 작성한 조건과 비교합니다. 수정 뒤 기존 정상 사례가 계속 통과하는지도 확인합니다.
요약이 보증하는 범위를 설명합니다
ALLOW는 이 입력 계약과 최소 실행 조건을 통과했다는 뜻이며 배포 성공이나 코드 무결함을 보장하지 않습니다. 테스트 내용이 부실하면 집계는 정확해도 검증 가치는 낮습니다. 후속 레슨은 이 요약을 실제 빌드와 같은 변경 ID에 묶습니다. 제어문의 기본 동작은 더 읽기로 보완하고 여기서는 통과·실패·건너뜀의 의미를 보존하여 진행 여부를 결정하는 데 집중합니다.
따라하기
상태를 세기
조건을 붙이기 전에 통과·실패·건너뜀의 수량을 확인합니다.
rows = ['passed', 'failed', 'skipped', 'passed']
counts = {'passed': 0, 'failed': 0, 'skipped': 0}
for status in rows:
counts[status] += 1
print(counts)실행 결과
{'passed': 2, 'failed': 1, 'skipped': 1}
실행 없음 판정
전체 건너뜀은 실패 0건이어도 실행을 입증하지 못합니다.
passed, failed, skipped = 0, 0, 2
executed = passed + failed
print('ALLOW' if failed == 0 and executed > 0 else 'BLOCK')실행 결과
BLOCK
입출력 계약 실행
표준 입력에 제공한 JSON을 읽어 혼합 결과를 출력합니다. 같은 코드를 브라우저에 넣어 경계 사례도 실행합니다.
import json, sys
def summarize(rows):
if not isinstance(rows, list):
raise ValueError('results must be a list')
counts = {'passed': 0, 'failed': 0, 'skipped': 0}
for status in rows:
if not isinstance(status, str) or status not in counts:
raise ValueError('unknown status')
counts[status] += 1
counts['executed'] = counts['passed'] + counts['failed']
counts['deployable'] = counts['failed'] == 0 and counts['executed'] > 0
return counts
if __name__ == '__main__':
try:
raw = sys.stdin.read()
result = summarize(json.loads(raw) if raw.strip() else [])
print('passed={passed} failed={failed} skipped={skipped} deploy={deployable}'.format(
**dict(result, deployable='ALLOW' if result['deployable'] else 'BLOCK')))
except (ValueError, TypeError):
print('INVALID')
실행 결과
passed=1 failed=0 skipped=1 deploy=ALLOW
확인 문제
실습
표준 입력의 JSON 상태 배열을 집계합니다. passed·failed·skipped만 허용합니다. 빈 입력은 빈 배열입니다. 실패가 없고 실행이 1건 이상일 때만 ALLOW이며 그 외 유효 입력은 BLOCK입니다. 배열 이외의 값·잘못된 상태·JSON 문법 오류는 INVALID 한 줄을 출력합니다. 출력 순서는 passed, failed, skipped, deploy이며 디버그 출력은 넣지 않습니다.
모범 답안
import json, sys
def summarize(rows):
if not isinstance(rows, list):
raise ValueError('results must be a list')
counts = {'passed': 0, 'failed': 0, 'skipped': 0}
for status in rows:
if not isinstance(status, str) or status not in counts:
raise ValueError('unknown status')
counts[status] += 1
counts['executed'] = counts['passed'] + counts['failed']
counts['deployable'] = counts['failed'] == 0 and counts['executed'] > 0
return counts
if __name__ == '__main__':
try:
raw = sys.stdin.read()
result = summarize(json.loads(raw) if raw.strip() else [])
print('passed={passed} failed={failed} skipped={skipped} deploy={deployable}'.format(
**dict(result, deployable='ALLOW' if result['deployable'] else 'BLOCK')))
except (ValueError, TypeError):
print('INVALID')
더 읽기
면접 질문
- CI에서 실패한 테스트 이후의 배포 동작을 설명합니다.