오류 주입 시험표
65분 안팎
학습 목표
센서 분리·NACK·시간 초과·큐 초과·시각 순환의 재현 조건과 기대 동작을 시험표로 제출합니다.
개념
왜 통과 화면 대신 실패 조건을 남기는가
릴리스 직전에는 기능을 더 넣는 일보다 다른 사람이 같은 실패와 복구를 재현할 수 있게 만드는 일이 중요합니다. 센서 기록기가 정상 온도를 한 번 출력했다는 사실은 분리 중 오래된 값을 전송하지 않는지 설명하지 못합니다. 이번 레슨의 제출물은 시험표입니다. 입력 조건·주입 시각·관찰 지점·기대 상태·금지 동작·판정 명령을 연결하여 실패를 일부러 만들고 올바르게 처리했는지 판단합니다.
이번 모듈은 앞 모듈의 C11 소스·PC HAL·pthread 큐·자원 회귀를 포함한 독립 압축을 사용합니다. gcc 명령·make·Python 3 표준 라이브러리가 필요합니다. 압축 해제 후 Makefile이 있는 폴더에서 실행하고 gcc --version과 python3 --version을 기록합니다. 맥의 gcc가 Apple Clang인지 함께 적습니다. 제품 서버나 실물 센서에 접속하지 않으며 입력은 hal_sim의 가상 시각과 SensorBus 필드로 만듭니다.
미션 starter의 release.c에는 두 함수가 미완성입니다. 먼저 release-test에서 컴파일 성공 뒤 조건 실패를 관찰하고 함수를 완성합니다. 그 다음 make evidence로 관찰 파일을 생성하고 evidence 문서 다섯 개를 작성한 뒤 make test를 실행합니다. solution에는 같은 테스트와 실행 근거가 있습니다. 레슨별 문서 과제는 이 압축의 evidence 폴더에 합칩니다. 브라우저 집계 과제의 입력 계약은 해당 레슨에서 별도로 정합니다.
시험 행의 출발점은 상태와 남아 있는 데이터입니다
시험표의 첫 열에 F01 같은 식별자를 붙입니다. 초기 상태만 IDLE이라고 적는 대신 recording·valid·기록 길이·SPI 길이·메시지 큐 길이도 적습니다. 분리 직전 이미 저장된 한 건과 아직 전송하지 않은 값은 소유권이 다릅니다. 저장 이력은 유지하고 미처리 데이터는 폐기해야 하는 요구를 길이와 값으로 확인합니다. 실험 전 새 객체와 HAL 초기화를 사용하여 앞 시험의 필터나 큐가 섞이지 않게 합니다.
F01은 정상 한 건을 저장한 뒤 다음 측정의 TRANSMIT 직전에 connected를 0으로 바꿉니다. 기대는 RECOVER·valid 0·필터 count 0·이전 저장 길이 유지입니다. 케이블이 돌아온 상황을 connected 1로 표현하더라도 이 모형은 명시적 reconnect 전에는 재개하지 않습니다. 재연결 뒤 새 온도를 읽어 과거 필터가 섞이지 않는지도 검사합니다. 단순히 마지막 상태가 IDLE인지 확인하면 이 중요한 데이터 정책을 놓칩니다.
F02의 NACK는 센서 모델이 응답을 거부하는 입력입니다. nack를 1로 유지하면 시작 시도 포함 세 번 후 ERROR에 머뭅니다. 실패 중 기존 UART 배열과 길이가 변하지 않는지 sentinel로 확인합니다. ERROR가 나왔다는 이유만으로 시험 실패라고 쓰지 않습니다. 지속 고장에 무한 재시도하지 않고 예산을 소진한 뒤 중단하는 동작이 기대 결과입니다. 일시적 고장을 제거하고 재시도 성공하는 경우와 표를 나눕니다.
F03은 delay_ms 20과 21을 비교합니다. 앞 모듈 계약에서는 정확히 20ms 안에 준비된 값은 허용하며 21ms 응답은 마감에 늦습니다. 타임아웃 후 100ms 대기하고 재시도합니다. test_recovery.c의 100·120·220·240·340·360ms 사건을 따라 각 시도의 시작과 실패를 적습니다. 최종 상태만 보고 세 번의 예산을 검증했다고 말하지 않고 attempts와 last_error를 함께 관찰합니다.
숫자 경계와 저장 실패를 따로 주입합니다
F04는 메시지 큐 용량 16에 20건을 넣어 DROP_NEW 4를 확인합니다. 새 입력을 버리는 정책이므로 이미 받아 둔 16건의 순서가 바뀌면 실패입니다. 이어 분리를 주입하면 backlog를 취소하고 cancelled가 증가합니다. dropped는 수용하지 않은 사건이고 cancelled는 받아 둔 뒤 폐기한 사건이라 더해서 하나의 오류 종류로 이름 붙이지 않습니다. 정상 재생의 최고점 1은 이 과부하 시험의 최고점 16을 대체하지 않습니다.
F05는 uint32_t 시각이 순환하는 짧은 구간을 다룹니다. 시작 UINT32_MAX-9에서 시각 9까지는 19ms이고 10까지는 20ms입니다. signed 비교나 now가 started보다 작다는 단순 검사로 실패를 판단하면 순환을 오판합니다. unsigned 차이와 짧은 관찰 간격 계약을 유지합니다. 가상 하루 86400000ms는 32비트 밀리초 전체 범위보다 작으므로 하루 재생만으로 순환 시험을 수행한 것으로 계산하지 않습니다.
F06은 데이터 나이 299ms와 300ms를 나눕니다. UART 전송 지연이 299ms이면 허용하고 300ms부터 stale로 거부합니다. 여기서 기준은 센서의 측정 timestamp이며 타이머 due가 아닙니다. 과부하에서 오래된 값이 나가지 않는지 출력 길이와 저장 길이도 검사합니다. 마감 20ms 경계와 신선도 300ms 경계는 서로 다른 계약이므로 둘 다 같은 부등호를 쓰는 방식으로 구현하지 않습니다.
F07은 SPI 저장 길이 250바이트에서 13바이트를 쓰거나 기록 버퍼 16건을 채운 뒤 전송하거나 UART capacity 12를 전달합니다. 기대는 용량 오류·부분 발행 없음·SPI CS 해제입니다. 반환이 실패했는데 UART 일부가 새 값으로 바뀌었다면 다운스트림이 불완전한 데이터를 정상으로 볼 수 있습니다. 장시간 재생의 시험 sink는 확인 후 공간을 돌려주지만 이 별도 fixture에서는 공간을 돌려주지 않아 기존 한계를 그대로 검증합니다.
메시지를 시험표와 연결하여 읽습니다
테스트의 CHECK가 실패하면 FAIL line과 조건식이 나옵니다. 해당 줄에서 state·length·attempts 중 무엇이 어긋났는지 확인하고 fixture ID에 적습니다. gcc의 error는 실행 전 빌드 실패이므로 상태 전이를 관찰한 결과가 아닙니다. 반대로 make가 Error 1로 끝났더라도 그 앞의 조건 실패가 의도한 starter 결함인지 확인해야 합니다. 종료 코드만 캡처하고 상세 조건을 버리면 수정 방향을 찾기 어렵습니다.
recovery_tick이 BUS_OK를 반환했다는 사실은 센서 읽기가 성공했다는 뜻이 아닙니다. 이 반환은 tick 처리를 나타내며 고장 상태는 r.state와 last_error에 남습니다. 분리 시험에서 반환 BUS_OK·state RECOVER가 함께 나오는 것은 모순이 아닙니다. 시험표에 관찰 필드를 지정해 오류 의미를 분리합니다. 연결 실패를 찾을 때는 실행한 HAL 종류와 주소·길이·센서 필드부터 확인하며 PC 입력만으로 전기적 원인을 확정하지 않습니다.
제출 전 동료에게 시험표 한 행만 전달하여 재현해 보게 합니다. 시험 파일·주입 값·tick 시각·기대 출력 또는 조건식이 빠져 질문이 필요하면 행을 보완합니다. 성공 관찰과 미검증 사항은 다른 칸에 적습니다. 실제 보드의 전원·배선·pull-up·파형·watchdog는 후속 실물 항목입니다. 로그 비용과 물리 관찰 도구의 자세한 설명은 더 읽기에서 보고 이번 표에는 우리 기록기의 실행 가능한 실패 조건을 남깁니다.
따라하기
시험표를 현재 소스에서 시작하기
미션 압축의 test_recovery.c와 test_pipeline.c를 읽고 각 주석 바로 뒤의 초기화·필드 변경·tick 시각·CHECK를 F01부터 F07까지 연결합니다. 새 고장을 임의로 상상한 기대값과 기존 구현의 계약을 구분합니다.
python3 -c "from pathlib import Path; print(Path('test_recovery.c').read_text()); print(Path('test_pipeline.c').read_text())"오류 fixture의 판정 실행
starter에서도 이미 구현된 기존 회귀입니다. 세 행의 실패 수0을 확인하고 개별 조건은 시험표에서 파일과 연결합니다. ERROR를 기대한 테스트도 이 통과 수에 들어갑니다.
make -s recovery-test pipeline-test retry-test실행 결과
state-recovery: 126 cases, 0 failures pipeline: 55 checks, 0 failures; DROP_NEW=4 STALE=1 CANCEL=17 sent=2 retry-budget: 11 cases, 0 failures
순환 경계의 기대 시각 계산
uint32_t 모듈러 차이를 Python으로 대조합니다. 시작점에서9와10까지의 차이는 각각19와20ms입니다. 이 계산은 C 상태 검사를 대체하지 않습니다.
start = 2**32 - 10
for now in [9, 10]:
print(f"now={now} elapsed_ms={(now-start) % 2**32}")
실행 결과
now=9 elapsed_ms=19 now=10 elapsed_ms=20
시험표를 제출 가능한 행으로 마무리하기
FAULTS.md의 각 행에서 명령만으로 알 수 없는 초기 저장값·주입 시점·재연결 정책을 보완합니다. 특히 F03의 delay20 허용과 delay21 거부, F04의 DROP_NEW와CANCEL, F06의299/300 경계가 별도 조건으로 드러나는지 검토합니다. 파일의 TODO를 제거하고 실제 관찰과 실물 후속을 다른 칸으로 남깁니다.
확인 문제
실습
evidence/FAULTS.md에 F01분리·F02NACK·F03시간 초과·F04큐 초과·F05시각 순환을 포함한 시험표를 제출합니다. 각 행에는 초기 상태와 저장 길이, 필드 주입값, 가상 tick 시각, 기대 state·last_error·valid·큐/기록 길이, 금지되는 부분 발행, 재개 조건, 테스트 파일과 CHECK를 적습니다. 나이299/300ms와 SPI/기록/UART 용량 한계도 추가합니다. 실제 실행 명령과 출력·판정 결과를 연결하고 실물 미검증 항목을 따로 표시합니다.
더 읽기
면접 질문
- 센서가 응답하지 않을 때 점검할 순서를 설명해 주시면 됩니다.