· 6AI

하네스 엔지니어링 알아보기(4)

3주차에서 코드와 테스트를 원하는 의도대로 돌아가도록 유도하는 작업을 했는데, 아직 문제가 조금 남아있다. 큰 문제는 아니고, 찌꺼기에 가까운 것들이다.

#AI#harness
목차 보기

3주차에서 코드와 테스트를 원하는 의도대로 돌아가도록 유도하는 작업을 했는데, 아직 문제가 조금 남아있다. 큰 문제는 아니고, 찌꺼기에 가까운 것들이다.

  1. 각 코드의 테스트는 통과하지만 실제 사용자 흐름에서는 동작하지 않을 수 있다.
  2. 실패 원인과 작업 판단 근거가 남아 있지 않아 같은 문제를 반복해서 조사한다.
  3. 세션이 끝난 뒤 깨진 빌드와 임시 코드가 남아 다음 세션의 작업을 방해한다.

이번 주제는 '마무리 잘 하기'다.

단위 테스트가 통과했다고 기능이 동작하는 것은 아니다

단위 테스트는 특정 함수나 컴포넌트를 독립적으로 확인하고, 의존하는 객체를 mock으로 대체하기 때문에 빠르고 실패 위치를 찾기도 쉽다. 근데 단위테스트만으로 모든 기능이 완전하다고 보장되진않는다.

예를 들어 파일 내보내기 기능을 구현했다고 하자.

UI에서 내보내기 버튼을 누른다.
프리로드 계층을 통해 파일 시스템에 접근한다.
서비스 계층에서 데이터를 변환한다.
파일을 저장한다.
진행 상태를 다시 UI에 표시한다.

UI에서는 상대 경로를 넘겼는데 파일 시스템 계층은 절대 경로를 기대할 수 있다. 서비스 계층에서 발생한 오류가 UI까지 전달되지 않을 수도 있고, 대용량 파일을 처리한 뒤 파일 핸들을 닫지 않을 수도 있다.

mock을 사용한 테스트에서는 각 계층이 기대하는 값을 정확히 넣어주기 때문에 이런 경계 문제가 잘 드러나지 않는다. 그래서 통합테스트랑 엔드투엔드 테스트가 이뤄져야한다.

## 검증 규칙

- 단위 테스트를 통과해야 한다.
- 외부 시스템과 연결되는 기능은 통합 테스트를 통과해야 한다.
- 여러 컴포넌트를 수정한 기능은 엔드투엔드 테스트를 통과해야 한다.
- 필요한 검증 단계를 생략하면 완료로 처리하지 않는다.

검증규칙에서 엔드투엔드를 끼워넣으면 에이전트가 구현계획 때 부터 검증규칙을 읽기 때문에, 다른 계층과의 연결을 고려한 구현으로 이어진다.

아키텍처 규칙도 실행할 수 있어야 한다

문서에 다음과 같은 규칙을 작성했다고 하자.

Renderer에서는 파일 시스템에 직접 접근하지 않는다.
모든 파일 작업은 preload bridge를 통해 실행한다.

요즘 프론티어 모델들은 이렇게만 해도 잘 돌아갈 것 같은데, 기본적으로 사람도 문서를 매번 읽지 않는데 에이전트가 매번 정확하게 지킬 것이라고 기대하면 안된다. 그래서 중요한 규칙이라면 lint나 테스트, 검증 스크립트로 확실하게 명시해야된다.

에러 메시지도 실패했다고만 출력하면 부족하고, 더 많은 정보를 뿜게 해야된다.

무엇이 잘못됐는지
왜 잘못된 것인지
어디를 어떻게 고쳐야 하는지

에이전트는 실패 메시지를 보고 다음 행동을 결정하기 때문에 이 세 가지를 함께 알려주는 편이 좋다. 에러를 출력한다고 해서 architecture violation처럼 모호하게 출력하면 원인을 다시 조사해야 하지만 수정 위치까지 알려주면 바로 self-review로 들어갈 수 있다.

에이전트가 무엇을 했는지 확인할 수 있어야 한다

테스트가 실패했는데 로그가 부족하면 에이전트도 결국 추측할 수밖에 없다. 추측하다보면 기반 근거가 없으니 실제 원인과 무관한 코드를 수정하고 다시 테스트하는 일이 반복되며 토큰만 빨리는 불상사가 일어난다.

그래서 관측 가능성은 에이전트가 작업하는 하네스 안에도 들어가야 한다.(운영쪽에서는 당연히 있을거고)

관측 가능성은 크게 두 가지로 나눠 볼 수 있다.

런타임 관측 가능성 - 실제로 시스템에서 무슨 일이 발생했는가?
프로세스 관측 가능성 - 어떤 기준으로 작업했고 왜 완료라고 판단했는가?

런타임 관측 가능성에는 다음과 같은 정보가 포함될 수 있다.

애플리케이션 시작과 종료 상태
주요 기능의 진입점과 종료 지점
컴포넌트 사이의 데이터 전달 과정
외부 API와 데이터베이스 호출
CPU와 메모리 등 리소스 사용량
오류 메시지와 전체 예외 정보

프로세스 관측 가능성은 실행 로그만으로 해결하기 어렵고 작업 범위와 제외 사항, 완료 기준, 평가 결과 같은 정보는 별도 문서나 구조화된 파일로 남겨야 한다.

작업 전에 간단한 계약을 만든다

강의에서는 이를 스프린트 계약이라고 표현한다. 거창한 건 아니고 무엇을 하고, 무엇을 하지 않으며, 무엇을 통과해야 끝나는지를 정하는 문서다.

# 작업 계약: 다크 모드 지원

## 범위

- 테마 전환 기능 추가
- 전역 색상 변수 수정
- 사용자 설정 저장

## 검증 기준

- 새로 실행한 앱에서 저장된 테마가 적용된다.
- 주요 화면의 시각적 회귀 테스트가 통과한다.
- 테마 전환 엔드투엔드 테스트가 통과한다.

## 제외 사항

- 인쇄 화면은 수정하지 않는다.
- 외부 라이브러리 내부 스타일은 수정하지 않는다.

이 문서가 있으면 구현하는 에이전트와 평가하는 에이전트가 서로 다른 기준을 들고 싸우는 일을 줄일 수 있고, 구현한 다음 평가자가 갑자기 새로운 기준을 꺼내는 것도 막을 수 있다. 중요한건 여기서 끝내면 안되고 평가 결과도 명확하게 남겨야 이후 작업도 일관성을 지킬 수 있다는 것이다.

## 평가 결과

### 기능 정확성

- 회원가입 성공 시 201 반환: PASS
- 중복 이메일 입력 시 409 반환: PASS
- 잘못된 이메일 형식 처리: FAIL

근거:
`./scripts/verify-feature.sh F01` 실행 결과 3번째 시나리오 실패

### 아키텍처 준수

- Controller에서 Repository 직접 접근 없음: PASS
- Service 계층 우회 없음: PASS

찌꺼기 청소하기

에이전트가 기능 구현을 마치고 테스트까지 실행했어도 저장소에 다음과 같은 상태를 남겼다면 세션을 정상적으로 끝낸 게 아니다.

빌드가 깨져 있다.
일부 테스트가 실패한다.
console.log와 임시 파일이 남아 있다.
기능 목록이 갱신되지 않았다.
실행 방법이 달라졌지만 문서가 수정되지 않았다.

이 상태로 다음 세션이 시작되면 새 작업보다 이전 세션의 흔적을 조사하는 데 더 많은 시간과 토큰을 쏟아붓는다.. sol같은 녀석은 내부에서 검증루프를 엄청나게 돌리는 것으로 보이기 때문에 이게 더 치명적인 토큰누수범이 되는 것이다.

특히 가장 문제가 되는 것은 의도적인 변경과 임시 변경을 구분하기 어렵다는 점이다.

// 임시로 우회
return true;

이 코드가 디버깅을 위해 잠깐 추가된 것인지, 실제 요구사항이 바뀐 것인지 다음 세션은 알 수 없어서 결국 추측해서 지우거나 그대로 두게 된다. 여기서 다시 한 번 되새겨야할 원칙. 에이전트도 ACID를 적용해야 좋다는 것이다.

검증 가능한 상태로 커밋하거나
마지막 정상 상태로 되돌린다.

중간 상태를 그대로 다음 세션에 넘기지 않는 것이다. 강의에서는 클린 상태를 다음 다섯 가지로 나눈다.

빌드
코드가 정상적으로 빌드되는가?

테스트
기존 테스트를 포함해 필요한 테스트가 통과하는가?

진행 상태
완료, 진행 중, 미시작 상태가 기록되어 있는가?

산출물
임시 파일과 디버그 코드가 남아 있지 않은가?

시작 경로
다음 세션이 정해진 명령으로 환경을 시작할 수 있는가?

이를 세션 종료 체크리스트로 만들어 관리하고, 에이전트가 체크포인트로 사용하도록 지시문에 넣으면 완성이다.

## 세션 종료 체크리스트

- [ ] 빌드가 통과한다.
- [ ] 기존 테스트와 신규 테스트가 통과한다.
- [ ] 현재 기능 상태를 업데이트했다.
- [ ] 실패했다면 실패 원인과 재현 방법을 기록했다.
- [ ] 임시 로그와 디버그 코드를 제거했다.
- [ ] TODO를 그대로 남겼다면 별도 작업으로 등록했다.
- [ ] 표준 실행 명령이 정상적으로 동작한다.
- [ ] 다음 세션이 시작할 위치를 기록했다.

여기서 테스트가 실패한 작업을 억지로 통과 상태로 만들 필요는 없다. 오히려 그러면 안되고, 기능이 완료되지 않았더라도 저장소 자체는 깨끗하게 남기는 걸 목적으로 해야된다.

기능 상태: active
실패 검증: 비밀번호 재설정 E2E 테스트
실패 원인: 메일 토큰 만료 시간이 설정되지 않음
재현 명령: ./scripts/e2e-password-reset.sh
다음 작업: TokenService 만료 시간 처리
빌드 상태: passing
기존 테스트: passing

즉, 클린 상태와 기능 완료는 같은 말이 아니다. 기능은 미완료일 수 있지만 다음 세션이 혼란 없이 이어받을 수 있는 상태여야 한다.

공유

댓글