Devin.KR

실습 준비 - 기획서·작업 기록·실행 명령 고정

개발자KR 조회 0

이 장에서 배우는 것

동아리와 스터디 모임이 함께 쓰는 공간 예약 서비스를 만든다. 첫 작업은 예약 버튼을 만드는 일이 아니다. 누가 어떤 일을 할 수 있어야 하는지 적고, 이번에 만들지 않을 일도 정하는 일이다. 목표가 글로 남아 있어야 AI가 내놓은 코드가 요청에 맞는지 확인할 수 있다.

이 장에서는 서비스 기획서, 작업 기록, 실행 명령을 준비한다. 마지막에는 기획서의 기능 목록을 읽어 작업 단위와 진행 상태를 출력하는 도구를 만든다. 아직 예약을 저장하거나 화면을 제공하지는 않는다. 앞으로 만들 서비스의 범위를 확인하는 작은 프로그램부터 실행해 보는 것이다.

  • 사용자, 제공할 기능, 제외 범위, 완료 기준을 한 장 기획서로 정한다.
  • 화면 목록과 기능 목록을 연결해 빠뜨린 요구를 찾는다.
  • 요청, 받은 결과, 검증, 수정 내용을 작업 기록으로 남긴다.
  • 지침 파일에 실행 명령과 테스트 명령을 고정한다.
  • 기획서의 기능 목록을 읽고 진행 상태를 출력하는 프로그램을 검증한다.

문제 상황

작은 모임 세 곳이 같은 공간을 사용한다고 가정한다. 지금은 대화방에 예약 시간을 남긴다. 누군가는 지난 메시지를 읽지 못하고 같은 시간에 예약한다. 다른 사람은 취소 메시지를 보지 못해 비어 있는 공간을 사용할 수 없다고 생각한다. 운영자는 메시지를 다시 읽으며 예약 목록을 정리한다.

이 문제를 해결하려고 AI에게 “공간 예약 서비스를 만들어 달라”고 요청한다. 돌아온 결과에는 회원 가입, 관리자 화면, 알림 기능까지 들어 있다. 화면은 많지만 같은 공간의 예약이 겹칠 때 어떤 일이 일어나는지는 분명하지 않다. 실행 방법도 답변마다 달라진다. 파일을 고칠수록 무엇을 확인해야 하는지 기억에 의존하게 된다.

처음 요청이 넓어서 생긴 문제다. “예약 서비스”라는 말만으로는 사용자의 행동, 필요한 입력, 성공 조건을 알 수 없다. AI가 빈 부분을 채울 수는 있지만, 그 선택이 우리 모임의 필요와 일치한다고 볼 근거는 없다. 먼저 작은 범위를 정하고, 그 범위를 확인할 방법을 함께 적어야 한다.

첫 결과물도 작게 잡는다. 기획서의 기능 다섯 개가 작업 표에 나타나고, 각 작업의 상태와 상태별 개수가 정확히 출력되면 된다. 실제 예약 기능이 없어도 준비 작업의 누락과 모순을 발견할 수 있다. 이 도구의 완료와 예약 서비스의 완료는 구분해서 기록한다.

한 장 기획서로 범위를 정한다

기획서는 프로그램이 해결할 문제와 해결하지 않을 문제를 적은 문서다. 여기서 “한 장”은 종이 한 면에 반드시 맞추라는 뜻보다, 처음 읽는 사람이 핵심을 한 번에 파악할 만큼 짧게 쓰라는 뜻이다. 길이보다 중요한 것은 판단에 필요한 내용이다.

사용자를 “누구나”라고 적으면 입력 방식과 권한을 결정하기 어렵다. 이 서비스의 사용자는 공간을 공유하는 모임의 구성원으로 한정한다. 운영자도 같은 예약 목록을 확인한다. 계정과 권한을 구분하는 기능은 이번 범위에서 제외한다. 사용자 역할을 정하는 것과 로그인 기능을 구현하는 것은 서로 다른 결정이다.

공간 예약 서비스의 한 장 기획서
항목정한 내용확인할 질문
누가공간을 공유하는 동아리·스터디 구성원과 운영자이 사람이 직접 할 일이 기능 목록에 있는가
무엇을공간 확인, 예약 등록, 예약 목록 확인, 예약 취소대화방 메시지를 다시 찾는 일을 줄이는가
핵심 규칙같은 공간에서 시간이 겹치는 예약은 등록하지 않는다겹친다는 판단을 이후 테스트로 확인할 수 있는가
제외 범위로그인, 결제, 반복 예약, 외부 알림, 여러 시간대 지원이번 요청에 제외한 기능이 섞이지 않았는가
서비스 완료 기준등록한 예약이 목록에 보이고 취소 결과가 반영되며 겹치는 예약이 거절된다실행 결과와 테스트로 기준 충족을 확인했는가

완료 기준은 “잘 작동한다”처럼 쓰지 않는다. “등록한 예약이 목록에 보인다”처럼 행동과 관찰할 결과를 함께 쓴다. 다만 이 장에서는 그 기준을 문서에 남길 뿐 실제 예약 기능을 구현하지 않는다. 저장 방식과 시간 충돌 판단을 미리 코드로 끌어오면 준비 작업의 목적이 흐려진다.

화면 목록은 사용자가 어느 자리에서 일을 하는지 보여 준다. 기능 목록은 프로그램이 어떤 행동을 제공하는지 보여 준다. 화면 하나에 기능 여러 개가 들어갈 수 있다. 이 단계에서 색상이나 버튼 모양보다 화면과 행동의 연결을 먼저 확인한다.

화면과 기능을 연결한 초기 목록
번호화면기능완료 기준
F01공간 목록공간 목록 확인이름과 이용 안내가 보인다
F02예약 등록예약 입력공간·모임·시작·종료를 입력한다
F03예약 등록예약 충돌 확인겹치는 예약의 등록을 거절한다
F04예약 목록예약 목록 확인등록된 예약을 확인한다
F05예약 목록예약 취소취소한 예약이 목록에서 빠진다

기능 번호는 이름을 조금 고쳐도 같은 기능을 가리킬 수 있도록 붙인다. 예를 들어 “예약 입력”을 “예약 등록 요청”으로 바꾸더라도 F02를 유지하면 작업 기록을 찾기 쉽다. 삭제한 번호를 곧바로 다른 기능에 다시 붙이면 이전 기록이 무엇을 뜻하는지 혼동할 수 있다.

사용자의 문제를 화면과 기능으로 나누고 완료 기준으로 확인한다

이 장의 도구는 기능 한 개를 작업 한 개로 옮긴다. 실제 구현에서는 기능 하나가 여러 작업으로 나뉠 수 있다. 지금은 기능 번호, 화면, 기능 이름을 가진 초기 작업 목록을 만드는 데 집중한다. 아직 하지 않은 설계를 세밀한 작업으로 미리 나누기보다, 빠진 기능이 없는지 확인하는 편이 유용하다.

작업 기록과 지침 파일을 준비한다

작업 기록은 대화 내용을 모두 복사한 문서가 아니다. 어떤 요청을 했고, 무엇을 받았으며, 어떻게 확인했고, 무엇을 바꿨는지 남기는 문서다. 나중에 같은 문제가 나타났을 때 판단의 근거를 찾을 수 있을 만큼 적는다. 실행하지 않은 결과는 “확인하지 않음”이라고 쓴다.

작업 기록 한 건에 남길 내용과 작성 예시
항목남길 내용이번 작업의 예시
요청목표, 범위, 실행 조건기능 목록을 작업 표로 출력하고 외부 패키지는 사용하지 않는다
받은 결과파일과 제공된 동작main.py와 기능 목록 해석 함수, 상태 집계 함수가 제공되었다
검증실행 명령, 실제 결과, 기준과의 비교작업 다섯 개의 순서와 상태별 개수를 예상 결과와 대조했다
수정발견한 문제와 반영한 변경기획서에 없는 번호가 상태 목록에 있으면 오류를 내도록 고쳤다

표의 예시는 기록 방식의 견본이다. 실제 기록에는 자신이 실행한 결과를 적는다. AI가 “테스트를 통과했다”고 답했다는 사실과, 독자가 같은 명령을 실행해 통과를 확인했다는 사실은 다르다. 전자는 받은 결과에, 후자는 검증에 들어간다.

AI에게 보내는 첫 요청은 다음처럼 작성할 수 있다. 요청문에 실행 조건과 확인 조건을 함께 넣으면 결과를 검토할 기준도 생긴다.

공간 예약 서비스의 준비 도구를 main.py 한 파일로 작성한다. 파일 안에 넣은 기획서 문자열의 기능 목록을 읽고, 기능 번호·화면·작업·상태를 출력한다. 상태는 미시작, 진행 중, 완료만 허용한다. 상태가 없는 기능은 미시작으로 처리한다. 기능 번호 중복과 알 수 없는 상태는 오류로 처리한다. Python 3.12 이상 표준 라이브러리만 사용한다. 기본 실행은 바로 끝나고 출력은 항상 같아야 한다. --test로 검사할 수 있게 한다.

받은 답변이 다음과 같더라도 확인은 이어져야 한다. 설명이 요청을 충족했다는 주장이라면, 실행 결과는 그 주장을 확인할 자료다.

기획서에서 기능 목록을 읽는 함수와 상태를 집계하는 함수를 작성했다. 기본 실행은 작업 표를 출력하고, --test를 붙이면 입력과 상태에 대한 검사를 실행한다.

지침 파일은 매번 반복할 작업 조건을 저장하는 파일이다. 이 책에서는 AGENTS.md라는 이름을 쓴다. 확장자가 .md인 파일은 제목이나 목록을 간단한 기호로 적는 마크다운(Markdown) 문서다. 도구마다 지침 파일을 읽는 방식은 다를 수 있으므로 자동으로 적용된다고 가정하지 않는다. 요청할 때 파일을 읽도록 명시하고, 중요한 실행 조건은 요청문에도 적는다.

AGENTS.md에는 아래 내용을 평문으로 적는다. 이 내용은 프로그램 코드가 아니라 작업 약속이므로 인용문으로 제시한다. 완성 프로그램은 이 파일을 읽지 않으며, main.py만 있어도 실행된다.

작업 대상: 공간 예약 서비스의 준비 도구.

환경: macOS/Linux, Python 3.12 이상. 표준 라이브러리만 사용한다. 네트워크 호출과 API 키는 사용하지 않는다.

기본 실행: python3 main.py

테스트 실행: python3 main.py --test

구문과 경고 확인: python3 -W error -m py_compile main.py

출력 조건: 기본 실행은 곧바로 종료한다. 현재 시각이나 난수에 따라 출력이 바뀌지 않는다.

수정 조건: 요청한 범위만 바꾼다. 변경 전후의 차이를 검토하고 실제 실행 결과를 작업 기록에 남긴다.

명령을 고정하면 실행 방법을 매번 새로 추측하지 않아도 된다. py_compile은 파일을 실행하는 대신 Python이 읽을 수 있는 문법인지 검사하는 표준 라이브러리 도구다. -W error는 검사 중 경고가 나오면 오류로 다루게 한다. 이 검사는 출력 내용이나 기능의 정확성까지 증명하지 않으므로 기본 실행과 테스트도 함께 한다.

변경 전후의 차이(diff)는 추가·삭제·수정된 부분을 비교한 결과다. 편집기의 비교 기능으로 살펴봐도 된다. 기능 하나를 고쳤는데 실행 명령이나 기획서의 제외 범위까지 바뀌었다면 그 이유를 확인한다. 코드가 실행된다는 이유만으로 관계없는 변경까지 받아들이지는 않는다.

받은 코드는 실행과 테스트와 변경 비교를 거쳐 작업 기록으로 남긴다

기획서를 작업 표로 바꾸는 규칙

완성 코드에는 기획서를 문자열로 넣는다. 별도 문서 파일을 읽게 만들 수도 있지만, 여기서는 main.py 한 파일로 재현할 수 있게 한다. 프로그램은 파일 안의 기획서에서 “## 기능 목록” 제목을 찾고 그 아래 표를 읽는다. “## 제외 범위”처럼 다음 제목을 만나면 기능 읽기를 끝낸다.

이 프로그램은 모든 마크다운 문서를 해석하는 도구가 아니다. 기능 표에는 네 칸이 있고, 칸은 세로줄로 구분한다는 약속만 처리한다. 각 칸의 내용에 세로줄을 넣지 않는다. 입력 형식을 좁게 정하면 짧은 코드로도 틀린 입력을 발견할 수 있다.

읽어 들인 기능은 사전으로 표현한다. 사전은 이름표에 해당하는 키와 그 키의 값을 연결하는 Python 자료형이다. 여기서는 번호, 화면, 기능, 기준이라는 키를 쓴다. 기능 사전 여러 개는 목록에 담는다. 문서의 순서대로 목록에 추가하므로 출력 순서도 기획서와 같다.

진행 상태는 기획서와 별도로 관리한다. 기획서는 무엇을 만들지 정하고, 상태 목록은 그 일을 어디까지 했는지 나타낸다. 데모에서는 출력 형식을 확인하기 위해 F01을 완료, F02를 진행 중으로 설정한다. 실제 예약 기능이 구현되었다는 뜻은 아니다. 나머지 기능은 상태 목록에 없으므로 미시작으로 처리한다.

허용하지 않은 상태를 조용히 미시작으로 바꾸지는 않는다. “완료됨”이라는 오타가 들어왔는데 미시작으로 출력되면 실제 기록과 다른 표를 만들기 때문이다. 기획서에 없는 번호가 상태 목록에 들어 있어도 오류로 처리한다. 잘못된 값을 받아들이기보다 수정할 위치를 알려 주는 것이 이 도구의 역할이다.

완성 코드

다음 내용을 main.py로 저장한다. 기획서와 데모 상태까지 파일에 포함되어 있으므로 앞서 만든 파일은 필요하지 않다. 상태 표시는 출력 예시를 위한 값이며, 코드 주석에도 그 목적을 남긴다.

import sys


PLAN = """# 공간 예약 서비스
사용자: 공간을 공유하는 동아리와 스터디 구성원
목적: 예약과 취소 결과를 같은 목록에서 확인한다.

## 기능 목록
| 번호 | 화면 | 기능 | 완료 기준 |
| --- | --- | --- | --- |
| F01 | 공간 목록 | 공간 목록 확인 | 이름과 이용 안내가 보인다 |
| F02 | 예약 등록 | 예약 입력 | 공간·모임·시작·종료를 입력한다 |
| F03 | 예약 등록 | 예약 충돌 확인 | 겹치는 예약의 등록을 거절한다 |
| F04 | 예약 목록 | 예약 목록 확인 | 등록된 예약을 확인한다 |
| F05 | 예약 목록 | 예약 취소 | 취소한 예약이 목록에서 빠진다 |

## 제외 범위
로그인, 결제, 반복 예약, 외부 알림, 여러 시간대 지원
"""

STATES = ("미시작", "진행 중", "완료")

# 출력 확인용 데모 상태다. 실제 예약 기능의 구현 상태가 아니다.
DEMO_PROGRESS = {
    "F01": "완료",
    "F02": "진행 중",
}


def read_features(plan):
    features = []
    seen = set()
    in_features = False

    for raw_line in plan.splitlines():
        line = raw_line.strip()

        if line == "## 기능 목록":
            in_features = True
            continue

        if in_features and line.startswith("## "):
            break

        if not in_features or not line:
            continue

        if not (line.startswith("|") and line.endswith("|")):
            raise ValueError("기능 목록에는 표 행만 쓸 수 있다.")

        cells = [cell.strip() for cell in line[1:-1].split("|")]
        if len(cells) != 4:
            raise ValueError("기능 행은 네 칸이어야 한다.")

        if cells == ["번호", "화면", "기능", "완료 기준"]:
            continue

        if cells == ["---", "---", "---", "---"]:
            continue

        feature_id, screen, name, criterion = cells
        if not all(cells):
            raise ValueError("기능 행에는 빈 칸을 둘 수 없다.")

        if not (
            len(feature_id) == 3
            and feature_id.startswith("F")
            and feature_id[1:] in {
                f"{number:02d}" for number in range(1, 100)
            }
        ):
            raise ValueError("기능 번호는 F01부터 F99까지 사용한다.")

        if feature_id in seen:
            raise ValueError(f"중복 기능 번호: {feature_id}")

        seen.add(feature_id)
        features.append({
            "번호": feature_id,
            "화면": screen,
            "기능": name,
            "기준": criterion,
        })

    if not features:
        raise ValueError("기능 목록이 비어 있거나 제목이 없다.")

    return features


def make_tasks(features, progress):
    feature_ids = {feature["번호"] for feature in features}
    unknown_ids = sorted(set(progress) - feature_ids)
    if unknown_ids:
        raise ValueError(
            "기획서에 없는 상태 번호: " + ", ".join(unknown_ids)
        )

    tasks = []
    for feature in features:
        feature_id = feature["번호"]
        state = progress.get(feature_id, "미시작")
        if state not in STATES:
            raise ValueError(f"허용하지 않은 상태: {feature_id}={state}")

        tasks.append({
            "번호": feature_id,
            "화면": feature["화면"],
            "작업": feature["기능"],
            "기준": feature["기준"],
            "상태": state,
        })

    return tasks


def count_states(tasks):
    counts = {state: 0 for state in STATES}
    for task in tasks:
        counts[task["상태"]] += 1
    return counts


def print_tasks(tasks):
    print("공간 예약 서비스 - 준비 도구")
    print("상태는 출력 확인용 데모 값이다.")
    print("번호 | 화면 | 작업 | 상태")
    for task in tasks:
        print(
            f"{task['번호']} | {task['화면']} | "
            f"{task['작업']} | {task['상태']}"
        )

    counts = count_states(tasks)
    print(f"전체 작업: {len(tasks)}개")
    print(
        f"미시작: {counts['미시작']}개 | "
        f"진행 중: {counts['진행 중']}개 | "
        f"완료: {counts['완료']}개"
    )


def check(condition, message):
    if not condition:
        raise AssertionError(message)


def expect_value_error(function, *args):
    try:
        function(*args)
    except ValueError:
        return
    raise AssertionError("ValueError가 발생해야 한다.")


def run_tests():
    features = read_features(PLAN)
    check(
        [feature["번호"] for feature in features]
        == ["F01", "F02", "F03", "F04", "F05"],
        "기능 번호와 순서가 다르다.",
    )

    tasks = make_tasks(features, DEMO_PROGRESS)
    check(
        [task["상태"] for task in tasks]
        == ["완료", "진행 중", "미시작", "미시작", "미시작"],
        "작업 상태가 다르다.",
    )
    check(
        count_states(tasks) == {"미시작": 3, "진행 중": 1, "완료": 1},
        "상태별 개수가 다르다.",
    )

    duplicate_plan = PLAN.replace("| F02 |", "| F01 |")
    expect_value_error(read_features, duplicate_plan)

    empty_cell_plan = PLAN.replace("| 예약 입력 |", "|  |")
    expect_value_error(read_features, empty_cell_plan)

    expect_value_error(make_tasks, features, {"F01": "완료됨"})
    expect_value_error(make_tasks, features, {"F99": "완료"})

    print("테스트 통과: 7개")


def main():
    if sys.argv[1:] == ["--test"]:
        run_tests()
        return

    if sys.argv[1:]:
        raise SystemExit("사용법: python3 main.py [--test]")

    features = read_features(PLAN)
    tasks = make_tasks(features, DEMO_PROGRESS)
    print_tasks(tasks)


if __name__ == "__main__":
    main()

줄별 해설

첫 줄의 sys는 실행할 때 전달한 인자를 읽기 위해 가져온다. 인자는 명령 뒤에 붙이는 값이다. 여기서는 --test가 있으면 테스트를 실행하고, 없으면 작업 표를 출력한다. 외부 패키지를 설치할 필요가 없다.

PLAN은 여러 줄 문자열이다. 시작과 끝의 따옴표 세 개 사이에 기획서를 담는다. 문서에는 사용자와 목적도 들어 있지만, read_features는 기능 목록만 읽는다. 필요한 부분을 정해서 읽기 때문에 제외 범위가 작업으로 섞이지 않는다.

STATES는 허용할 상태 세 가지를 담은 튜플이다. 튜플은 정해진 값들을 묶는 자료형으로, 여기서는 상태의 이름과 출력 순서를 정하는 데 쓴다. DEMO_PROGRESS는 기능 번호를 상태에 연결한 사전이다. 모든 기능을 넣을 필요는 없다.

read_features의 처음 세 줄은 결과 목록, 이미 본 번호 집합, 기능 목록 안에 있는지를 나타내는 값을 준비한다. 집합은 같은 값을 중복해서 보관하지 않는 자료형이다. seen에 번호가 있는지 확인하면 동일한 번호를 다시 쓰는 실수를 찾을 수 있다.

splitlines는 문서 문자열을 줄별로 나눈다. strip은 각 줄과 각 칸의 앞뒤 공백을 없앤다. 공백이 조금 달라도 “F01”과 “ F01 ”을 같은 번호로 읽게 된다. 제목을 만나면 in_features를 참으로 바꾸고, 다음 제목을 만나면 반복을 끝낸다.

표 행은 양쪽 끝에 세로줄이 있어야 한다. line[1:-1]은 그 두 글자를 제외한 부분이다. 이를 세로줄로 나누면 네 칸이 나온다. cells를 만드는 대괄호 안의 표현은 목록을 만드는 짧은 반복문이다. 각 칸에서 공백을 제거한 결과를 새 목록에 담는다.

네 칸인지 먼저 확인한 뒤 제목 행과 구분 행을 건너뛴다. 이 프로그램은 구분 행을 정확히 네 개의 “---”로 작성한다는 약속을 사용한다. 일반 문서 편집기의 모든 표 형식을 받아들이는 것은 아니다. 약속 밖의 행이 들어오면 입력 문서를 고쳐야 한다.

feature_id, screen, name, criterion에 네 값을 나누어 담는다. all(cells)는 모든 칸이 비어 있지 않은지 확인한다. 번호는 F01부터 F99까지 허용한다. f 문자열의 :02d는 정수를 두 자리로 표시하면서 빈 앞자리를 0으로 채운다. 이 번호 검사는 번호 형식만 확인하며 번호가 연속해야 한다고 요구하지는 않는다.

번호가 중복되면 ValueError를 발생시킨다. ValueError는 값이 정해진 조건에 맞지 않을 때 사용하는 예외다. 이 도구는 잘못된 기획서로 그럴듯한 작업 표를 출력하는 대신 실행을 멈춘다. 정상 행은 사전으로 만들어 features에 추가한다.

make_tasks는 상태를 붙이기 전에 기획서에 없는 번호를 검사한다. 집합의 뺄셈은 왼쪽에만 있는 값을 찾는다. sorted는 그 번호를 정렬한다. 오류 번호가 여러 개여도 메시지 순서가 일정해진다.

progress.get은 해당 번호의 상태를 가져오고, 번호가 없으면 미시작을 반환한다. 그 뒤 허용 상태인지 확인한다. 기준도 작업 사전에 보관하지만 현재 출력에서는 생략한다. 표가 너무 길어지지 않도록 번호, 화면, 작업, 상태만 보여 준다.

count_states는 세 상태의 개수를 0으로 시작해 작업마다 1씩 더한다. print_tasks는 작업을 기획서 순서대로 출력하고 마지막에 전체 개수와 상태별 개수를 출력한다. 글자 폭에 맞춘 정렬을 하지 않아 터미널 글꼴이 달라도 칸 구분을 읽을 수 있다.

check는 조건이 거짓이면 테스트를 실패시킨다. expect_value_error는 잘못된 입력이 ValueError를 일으키는지 검사한다. 오류가 나지 않아도 실패다. run_tests는 정상 입력의 번호 순서, 상태, 집계를 확인하고 중복 번호, 빈 칸, 잘못된 상태, 없는 번호를 거절하는지 확인한다. 일곱 검사를 모두 통과한 뒤에만 통과 문구를 출력한다.

마지막 조건은 파일을 직접 실행했을 때 main을 호출한다. main은 --test와 기본 실행을 구분한다. 모르는 인자가 들어오면 사용법을 보여 주고 끝낸다. 기본 실행에는 입력 대기, 현재 시각 조회, 임의의 값 생성이 없어 결과가 일정하다.

실행 결과

main.py를 저장한 디렉터리에서 다음 명령을 실행한다. 아래 출력은 코드에 포함된 기획서와 데모 상태를 기준으로 한다. 자신의 결과에서 행 순서나 개수가 다르면 코드와 문서 변경 부분부터 비교한다.

python3 main.py
공간 예약 서비스 - 준비 도구
상태는 출력 확인용 데모 값이다.
번호 | 화면 | 작업 | 상태
F01 | 공간 목록 | 공간 목록 확인 | 완료
F02 | 예약 등록 | 예약 입력 | 진행 중
F03 | 예약 등록 | 예약 충돌 확인 | 미시작
F04 | 예약 목록 | 예약 목록 확인 | 미시작
F05 | 예약 목록 | 예약 취소 | 미시작
전체 작업: 5개
미시작: 3개 | 진행 중: 1개 | 완료: 1개

테스트는 같은 파일에 --test를 붙여 실행한다. 통과 문구가 보이면 일곱 조건을 확인한 것이다. 화면 기능이나 실제 예약 충돌 처리까지 검사했다는 뜻은 아니다. 테스트가 확인하는 범위도 작업 기록에 남긴다.

python3 main.py --test
테스트 통과: 7개

구문과 경고 검사는 다음 명령으로 실행한다. 정상 완료 시 출력은 없다. 보통 __pycache__ 디렉터리에 검사 과정에서 생성한 파일이 남지만, 그 파일은 기본 실행에 필요한 기획서나 데이터가 아니다.

python3 -W error -m py_compile main.py

검증 기록에는 “기본 출력 일치, 테스트 일곱 개 통과, 구문 검사 출력 없음”처럼 관찰한 결과를 적는다. 코드를 수정했다면 변경 비교에서 요청과 관계없는 부분이 없는지도 확인한다. 실행, 테스트, 변경 비교는 서로 다른 질문에 답하므로 한 가지만으로 나머지를 대신하지 않는다.

실무에서 자주 틀리는 것

상태가 없는 기능을 빠뜨린다

다음 코드는 상태 사전에 있는 기능만 작업으로 취급한다. 처음 시작하는 기능은 상태를 아직 적지 않았을 수 있으므로 작업 표에서 사라진다. 짧은 예제의 기능 목록에서도 F02를 빠뜨린다.

features = [{"번호": "F01"}, {"번호": "F02"}]
progress = {"F01": "완료"}

for feature_id in progress:
    print(feature_id, progress[feature_id])

작업의 기준은 기획서의 기능 목록이어야 한다. 기능을 순회하고 상태가 없으면 미시작으로 처리한다. 아래 예제는 두 기능을 모두 출력한다.

features = [{"번호": "F01"}, {"번호": "F02"}]
progress = {"F01": "완료"}

for feature in features:
    feature_id = feature["번호"]
    print(feature_id, progress.get(feature_id, "미시작"))

잘못 쓴 상태를 임의로 바꾼다

“완료됨”을 허용하지 않은 상태라는 이유로 미시작으로 바꾸면 오타가 감춰진다. 사용자는 원래 적은 상태가 반영되지 않았다는 사실을 알아차리기 어렵다.

states = ("미시작", "진행 중", "완료")
state = "완료됨"

if state not in states:
    state = "미시작"

print(state)

허용하지 않은 값은 거절하고 원래 입력을 고치게 한다. 아래 예제는 잘못된 값에 대한 오류 메시지를 출력한다. 완성 프로그램에서는 make_tasks가 이 검사를 담당한다.

states = ("미시작", "진행 중", "완료")


def validate_state(state):
    if state not in states:
        raise ValueError(f"허용하지 않은 상태: {state}")
    return state


try:
    print(validate_state("완료됨"))
except ValueError as error:
    print(error)

완료라고 표시했다는 이유로 테스트가 통과했다고 쓴다

다음 코드는 상태만 확인한 뒤 테스트 통과라고 출력한다. 작업 상태는 사람이 적은 값이지 프로그램을 검증한 결과가 아니다. 완료로 표시된 일이 있어도 테스트는 별도로 실행해야 한다.

progress = {"F01": "완료"}

if progress["F01"] == "완료":
    print("테스트 통과")

검증할 동작과 예상 결과를 직접 비교해야 한다. 다음 코드는 상태가 없는 번호에 기본값이 적용되는지 확인한다. 완성 코드의 테스트도 같은 방식으로 도구의 행동을 검사한다.

progress = {"F01": "완료"}
actual = progress.get("F02", "미시작")

if actual != "미시작":
    raise AssertionError("상태가 없는 기능의 기본값이 다르다.")

print("기본 상태 검사 통과")

이 검사 하나가 통과했다고 전체 프로그램이 검증되었다고 쓰지는 않는다. “기본 상태 검사 통과”처럼 무엇을 확인했는지 결과에 드러내야 한다. 작업 기록에도 같은 범위를 적는다.

한눈에 보기

준비 산출물과 확인 방법
산출물담는 내용확인 방법
한 장 기획서사용자, 기능, 제외 범위, 완료 기준각 기능이 사용자의 행동과 관찰할 결과로 설명되는지 읽는다
화면·기능 목록기능 번호와 화면의 연결빠진 행동과 중복 번호를 찾는다
작업 기록요청, 받은 결과, 검증, 수정직접 확인한 사실과 아직 확인하지 않은 내용을 구분한다
AGENTS.md환경, 실행·테스트 명령, 수정 조건적힌 명령을 같은 디렉터리에서 실행한다
main.py기획서 해석, 상태 연결, 집계와 검사예상 출력 대조, 테스트, 구문 검사, 변경 비교를 수행한다

이 장의 도구는 예약 서비스를 구현하기 위한 목록을 눈에 보이게 만든다. 다음에는 이 목록을 바탕으로 공간과 예약을 어떤 표에 담을지 정한다. sqlite3로 표와 관계를 만드는 작업에서도 기획서의 범위와 완료 기준을 판단의 근거로 사용한다.

연습 문제

  1. 기획서의 F05 상태를 완료로 바꾼다. 기본 실행에서 전체 작업 수와 상태별 개수가 어떻게 바뀌는지 먼저 적고 실행해 확인한다. 기존 테스트에서 어떤 검사를 수정해야 하는지도 설명한다.
  2. 기능 목록 끝에 F06을 추가한다. 화면은 예약 목록, 기능은 예약 상세 확인, 완료 기준은 모임과 시작·종료를 확인한다로 정한다. 상태는 따로 넣지 않는다. 기능 수, 마지막 작업의 상태, 상태별 개수를 예상하고 확인한다.
  3. 기능 목록 제목이 빠진 기획서를 거절하는 테스트를 추가한다. 성공 안내의 테스트 개수도 바꾸고 실행한다.
  4. AI에게 받은 수정안에서 외부 패키지 설치 명령과 현재 날짜를 출력하는 코드가 추가되었다고 가정한다. 지침의 어떤 조건과 어긋나는지 쓰고, 요청·검증·수정 항목에 남길 기록을 작성한다.

정답과 해설

1. 상태를 바꾸면 예상 결과도 바뀐다

DEMO_PROGRESS에 F05를 추가한다. 전체 작업은 다섯 개로 유지된다. 미시작은 두 개, 진행 중은 한 개, 완료는 두 개가 된다. 기본 출력의 F05 행은 완료로 바뀐다.

DEMO_PROGRESS = {
    "F01": "완료",
    "F02": "진행 중",
    "F05": "완료",
}

run_tests의 상태 목록 예상값은 마지막 값을 완료로 바꾼다. 집계 예상값도 바꾼다. 번호 순서 검사와 잘못된 입력 검사는 그대로 둔다. 테스트가 실패했다고 실제 결과를 무조건 정답으로 복사하지 말고, 의도한 상태 변경에서 계산한 값인지 확인한다.

expected_states = ["완료", "진행 중", "미시작", "미시작", "완료"]
expected_counts = {"미시작": 2, "진행 중": 1, "완료": 2}

위 두 값은 바뀌어야 할 예상값을 보여 주는 실행 가능한 Python 문장이다. 완성 코드에서는 각각 해당 check의 비교 대상에 반영한다.

2. 기능이 늘어도 상태 기본값은 유지된다

이 문제는 원래 코드의 데모 상태를 기준으로 푼다. F06 행은 F05 다음, “## 제외 범위” 앞에 넣는다. PLAN 안의 표에는 번호, 화면, 기능, 완료 기준 네 칸을 유지한다. 추가한 기능은 초기 기획서의 범위 변경이므로 작업 기록에도 기능 추가를 남긴다.

전체 작업은 여섯 개가 된다. F06은 상태가 없으므로 미시작이다. 상태별 개수는 미시작 네 개, 진행 중 한 개, 완료 한 개다. 번호 순서 검사에는 F06을, 상태 목록 예상값에는 마지막 미시작을 추가한다. 집계 예상값의 미시작도 4로 바꾼다.

화면 이름을 기존 예약 목록으로 정했으므로 화면 개수는 늘지 않는다. 기능 개수와 화면 개수가 항상 같은 것은 아니라는 점도 확인할 수 있다.

3. 제목 누락도 검사 대상이다

run_tests 안에서 통과 문구를 출력하기 전에 다음 두 줄을 추가한다. 제목을 다른 이름으로 바꾸면 read_features가 기능 구역을 찾지 못하고 ValueError를 발생시켜야 한다.

missing_heading_plan = PLAN.replace("## 기능 목록", "## 작업 후보")
expect_value_error(read_features, missing_heading_plan)

성공 안내는 “테스트 통과: 8개”로 바꾼다. 이 검사는 표 내용이 있어도 약속한 제목이 없으면 거절하는지 확인한다. 빈 목록을 정상 결과로 받아들이면 기획서가 잘못된 상태에서도 작업이 없는 것처럼 보일 수 있다.

4. 요청 범위와 실행 조건을 다시 확인한다

외부 패키지 설치는 표준 라이브러리만 사용한다는 조건과 어긋난다. 현재 날짜를 출력하는 코드는 항상 같은 출력이라는 조건과 어긋난다. 날짜가 꼭 필요한 요구가 새로 생긴 것이 아니라면 추가된 두 부분을 제거하고 기존 명령으로 다시 확인한다.

요청: 기능 목록에서 상태가 없는 작업을 미시작으로 표시하도록 수정한다. 기존 환경과 출력 조건을 유지한다.

검증: 변경 비교에서 외부 패키지 설치 안내와 현재 날짜 출력이 추가된 것을 확인했다. 두 변경은 요청 범위에 없고 지침의 실행 조건에도 맞지 않는다.

수정: 추가된 설치 안내와 날짜 출력을 제거했다. 기본 실행의 예상 출력, 테스트 결과, 구문 검사를 다시 확인하고 실제 결과를 기록한다.

수정 뒤 실행하기 전에는 마지막 문장의 확인 결과를 완료로 적지 않는다. AI의 답변, 코드에서 읽은 사실, 실행으로 확인한 사실을 나누어 남기는 습관이 이후 작업의 근거가 된다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.