요구사항에서 테스트 도출하기
65분 안팎
학습 목표
제목 길이의 바로 아래·경계·바로 위 입력을 독립 기대 결과로 판정합니다.
개념
왜 구현을 읽기 전에 기대값을 적을까요
AI에게 기능과 테스트를 함께 부탁하면 같은 오해가 두 곳에 들어갈 수 있습니다. 제목을 40자 미만으로 이해한 구현과 그 구현을 복사한 테스트는 서로 일치하지만 사용자의 40자 이하 요구를 어깁니다. 교육 담당자는 신입에게 구현을 잠시 덮고 입력과 기대 결과 표부터 쓰게 합니다. 이번 레슨에서는 앞 모듈의 할 일 생성 계약을 기준으로 테스트에 사용할 답을 스스로 정합니다. 테스트가 실행되었다는 사실과 잘못된 동작을 구별했다는 사실을 나눠 판단합니다.
실습 환경과 관찰 방법
이 모듈의 브라우저 실습은 Python 표준 입력의 JSON 한 개를 읽어 결과를 출력합니다. 코드 포인트 기준 길이를 사용하며 일반적인 한글 한 글자와 이모지 하나는 각각 하나로 셉니다. 로컬 실습은 JDK 17·Spring Boot 3.1.5와 제공된 Maven Wrapper를 사용합니다. starter와 solution을 다른 폴더에 풀고 pom.xml 위치에서 ./mvnw test를 실행합니다. 캐시가 준비된 환경의 검증 명령은 ./mvnw -o -q test입니다. 관찰 단계는 solution에서, 수정 과제는 starter에서 진행합니다.
계약의 단위를 먼저 고정합니다
앞 단계 명세는 제목의 앞뒤 공백을 정리한 뒤 1~40 코드 포인트를 허용합니다. 이모지의 화면 너비나 Java String.length의 UTF-16 길이를 뜻하지 않습니다. 조합된 글자가 눈으로 하나처럼 보여도 코드 포인트가 여럿일 수 있습니다. 이번에는 사용자 눈에 보이는 글자 수를 새로 정의하지 않습니다. 기존 명세의 단위를 유지해야 Python 모델과 Java 정책의 비교가 의미를 가집니다. 다른 길이 기준이 필요하면 테스트 수정 전에 요구 변경부터 결정합니다.
허용 구간을 작은 표로 바꿉니다
최솟값 주변은 0·1·2, 최댓값 주변은 39·40·41입니다. 39와 40의 결과는 OK, 41의 결과는 TITLE_TOO_LONG으로 직접 적습니다. 길이 0은 EMPTY_TITLE입니다. 숫자나 null은 길이 비교 전에 INVALID_TITLE_TYPE으로 분류합니다. 일반 정상 사례 한 개만으로는 등호를 빠뜨린 구현을 잡지 못합니다. 허용 구간 양끝을 포함하는 사례와 양끝 밖을 거절하는 사례가 함께 있어야 약속의 범위를 구분할 수 있습니다.
입력 생성과 답 생성은 다릅니다
테스트 입력을 만들 때 문자열 반복을 사용하는 것은 가능합니다. 가를 40번 반복하면 경계 입력을 쉽게 준비할 수 있습니다. 그러나 기대 결과를 현재 구현의 반환값에서 읽어 오면 검사 대상이 스스로 정답을 만들게 됩니다. expected에 문자열 OK를 적는 이유는 명세가 40까지 허용하기 때문입니다. 입력을 자동 생성하는 편의와 결과를 독립적으로 결정하는 책임을 구별합니다. 기대값 옆에 명세 문장을 남기면 나중에 변경 근거를 확인하기도 쉽습니다.
정리 전 길이와 정리 후 길이
공백을 붙인 한 글자는 OK여야 합니다. 공백만 있는 제목은 정리 뒤 빈 문자열이 되어 EMPTY_TITLE입니다. 40글자 앞뒤에 공백을 붙인 입력도 허용합니다. 원본 길이만 계산하는 구현은 이 사례를 거절할 수 있습니다. 반대로 내부 공백은 제거하지 않습니다. 복습과 정리 사이에 있는 공백은 제목의 일부입니다. 정리 범위가 앞뒤인지 전체인지 표에 적고, 단순히 공백 처리라고 적는 모호함을 줄입니다.
문자 종류를 바꾸어 같은 경계를 검사합니다
가 40개가 통과한다고 이모지 40개도 통과한다고 확정할 수 없습니다. Java의 코드 유닛 길이를 사용한 구현은 가 40개를 허용하면서 이모지 40개를 거절할 수 있습니다. 따라서 같은 숫자의 경계를 다른 문자로도 확인합니다. 이 사례는 새로운 상한을 추가하는 것이 아니라 길이 단위가 계약과 일치하는지 묻는 것입니다. 출력에는 허용 여부를 넣고 저장된 제목 보존은 별도의 서비스 검사로 연결합니다.
오류 우선순위도 기대값입니다
null은 빈 문자열이 아닙니다. 입력 타입이 잘못되었으므로 INVALID_TITLE_TYPE입니다. 빈 문자열은 타입은 맞지만 내용이 비어 EMPTY_TITLE입니다. 두 입력을 모두 오류라고만 처리하면 반환 코드가 바뀌어도 테스트가 통과할 수 있습니다. 요구된 오류 코드를 정확히 비교합니다. 단위 테스트의 예외 메시지와 HTTP의 error 필드는 같은 업무 원인을 표현하지만 상태 코드와 JSON 포장은 HTTP 경계 검사에서 따로 확인합니다.
사례의 이름으로 목적을 남깁니다
표의 이름을 case1 대신 upper_included, upper_excluded처럼 붙이면 실패했을 때 무엇이 달라졌는지 읽기 쉽습니다. 다만 이름만으로 검증이 생기는 것은 아닙니다. 입력, 기대 결과, 실제 비교가 함께 있어야 합니다. 한 테스트가 여러 입력을 반복할 경우 실패한 입력의 길이와 종류를 보고에 남깁니다. 큰 JSON 전체를 눈으로 찾는 대신 경계 이름을 먼저 보고 최소 재현 입력으로 내려가는 습관을 익힙니다.
브라우저 과제의 책임을 한정합니다
이번 실습은 제목 정책의 결과 코드만 판정합니다. 표준 입력에서 title 필드를 읽어 타입, 앞뒤 공백, 비어 있음, 상한을 순서대로 처리합니다. JSON 문법이 올바른 입력을 제공하며 HTTP 서버를 실행하지 않습니다. title 누락은 null과 같은 타입 오류로 취급합니다. 출력은 OK 또는 정의된 오류 코드 한 줄입니다. 안내 문구나 디버깅 로그를 표준 출력에 섞으면 채점 결과가 달라지므로 확인용 로그는 제출 전에 제거합니다.
실패한 결과를 명세와 대조합니다
40글자에서 expected OK, actual TITLE_TOO_LONG이면 기대값을 실제 결과에 맞추지 않습니다. 상한 포함이라는 약속을 다시 읽고 비교 연산이나 길이 단위를 살펴봅니다. 빈 공백에서 OK가 나오면 정리 시점과 빈 문자열 조건을 확인합니다. 숫자에서 프로그램이 예외로 중단되면 타입 확인 전에 문자열 함수를 호출했는지 봅니다. 오류 보고는 고쳐야 할 입력 경로를 좁히는 자료이며 테스트를 지우라는 신호가 아닙니다.
통과한 범위를 정확하게 말합니다
여덟 입력을 모두 통과했더라도 제목 정책 전체를 수학적으로 증명한 것은 아닙니다. 이번에 실행한 타입·공백·경계·문자 단위 사례가 요구와 일치했다고 기록합니다. 저장 부작용과 HTTP 응답은 아직 이 브라우저 판정기의 범위 밖입니다. 더 많은 무작위 입력보다 먼저 빠진 요구 항목이 무엇인지 검토합니다. 이번 제출물은 동작 코드와 경계 사례 표이며 기대값이 명세의 어느 문장에서 나왔는지 설명할 수 있어야 합니다.
따라하기
경계 기대 표를 출력합니다
명세에서 정한 결과를 먼저 표로 적습니다. 이 코드는 구현을 호출하지 않습니다.
cases = [(0,"EMPTY_TITLE"),(1,"OK"),(39,"OK"),(40,"OK"),(41,"TITLE_TOO_LONG")]
for n, expected in cases:
print(n, expected)실행 결과
0 EMPTY_TITLE 1 OK 39 OK 40 OK 41 TITLE_TOO_LONG
문자 단위를 확인합니다
Python 길이와 UTF-16 유닛 수를 비교합니다. Java 정책은 코드 포인트를 세어야 합니다.
print("한글 코드 포인트:", len("가" * 40))
print("이모지 코드 포인트:", len("😀" * 40))
print("이모지 UTF-16 유닛:", len(("😀" * 40).encode("utf-16-le")) // 2)실행 결과
한글 코드 포인트: 40 이모지 코드 포인트: 40 이모지 UTF-16 유닛: 80
기대값 복사의 한계를 재현합니다
40자 거절이라는 잘못된 실제 결과를 고정합니다. 자기 비교와 명세 비교의 차이를 실행합니다.
actual = "TITLE_TOO_LONG"
copied = actual
independent = "OK"
print("복사한 답 비교:", actual == copied)
print("명세의 답 비교:", actual == independent)실행 결과
복사한 답 비교: True 명세의 답 비교: False
확인 문제
실습
입력은 title을 가진 JSON 객체 한 개입니다. 명세의 타입→앞뒤 공백 정리→빈 제목→40 코드 포인트 상한 순서로 판정합니다. OK, INVALID_TITLE_TYPE, EMPTY_TITLE, TITLE_TOO_LONG 중 하나만 출력합니다. 경계 표의 기대를 직접 설명합니다. JSON 문법은 올바르게 제공됩니다.
모범 답안
import json, sys
value = json.load(sys.stdin).get("title")
if type(value) is not str:
result = "INVALID_TITLE_TYPE"
else:
clean = value.strip()
if not clean:
result = "EMPTY_TITLE"
elif len(clean) > 40:
result = "TITLE_TOO_LONG"
else:
result = "OK"
print(result)
더 읽기
면접 질문
- 구현과 같은 실수를 반복하지 않는 테스트를 만드는 방법을 설명해 주시면 됩니다.