문서 목차
이 페이지에서목차

Feature Test Bundle — unit + BDD(Patrol E2E) + E2E 3-layer 동시 스캐폴드

<!-- humanized:non-dev -->

명시적 수기 Patrol legacy 전용 참고. 저장소가 이 방식을 선언한 경우에만 아래의 Patrol 배치·리셋·step 템플릿을 사용한다. co_bdd feature-local BDD에는 적용하지 않는다. unit/widget/golden/Widgetbook/BDD의 전체 완료 계약은 feature-verification을 우선 적용한다. 아래 3-layer 설명은 legacy 실행 구조이며 필수 5종 검증의 면제가 아니다.

Feature Test Bundle — unit + BDD(Patrol E2E) + E2E 3-layer 동시 스캐폴드#

한마디로#

새 기능을 만들면 "이게 제대로 작동하는지 확인하는 검사 도구(테스트)"도 같이 있어야 합니다. 이 스킬은 그 검사 도구 3종 세트를 명령어 한 번으로 한꺼번에 준비해 주는 자동 조립 라인입니다. 마치 새 가전제품을 출고하기 전, 부품 검사·기능 검사·실사용 검사를 한 번에 세팅해 주는 품질 관리 라인과 같습니다. 검사의 '틀'을 만들어 주고, 세부 내용은 사람이나 다른 도구가 채워 넣습니다.

무엇을·언제#

  • 무엇을 해주나요
    • 작은 부품 단위 검사(Unit), 사용자 시나리오 검사(BDD), 여러 화면을 넘나드는 실제 흐름 검사(E2E) 세 가지 검사 틀을 한 번에 만들어 줍니다.
    • 검사 틀만 자동으로 만들고, 비워 둔 부분(채워야 할 곳)을 목록으로 알려 줍니다.
    • 만든 직후 자동으로 한 번 검사를 돌려서, 빠진 부분이 없는지 미리 점검합니다.
  • 언제 작동하나요
    • /cc-e2e:kick-off {기능이름} 명령을 실행했을 때
    • 새 기능 구현을 막 끝내고 검사 초안을 빠르게 갖추고 싶을 때
    • 코드 제출(PR) 전에 "검사 3종이 모두 걸려 있는지" 확인하고 싶을 때

핵심 용어#

용어쉬운 설명
테스트(test)코드가 의도대로 작동하는지 자동으로 검사하는 절차
Unit(단위) 테스트가장 작은 부품 하나하나가 제대로 도는지 보는 검사
BDD"이렇게 하면 이렇게 된다"는 사용자 행동 문장으로 적는 시나리오 검사 방식
E2E시작부터 끝까지, 여러 화면을 넘나드는 실제 사용 흐름을 통째로 보는 검사
스캐폴드(scaffold)내용은 비어 있지만 뼈대만 갖춘 틀(시작용 양식)
feature(기능)앱에서 하나의 기능 묶음(예: 로그인, 검색)
.feature 파일사용자 시나리오를 사람이 읽기 쉬운 문장으로 적어 둔 파일
step(스텝)시나리오 문장 하나를 실제로 실행하는 작은 동작 정의
Patrol실제 앱을 켜서 사람처럼 눌러 보며 검사하는 도구
widget(위젯)Flutter 앱 화면을 이루는 UI 조각
mock(목)진짜 대신 끼워 넣는 가짜 부품(검사용 대역)
로컬 풀스택내 컴퓨터에서 서버·DB·앱을 한꺼번에 띄워 통째로 돌려 보는 환경
PR작성한 코드를 합치자고 올리는 변경 요청(Pull Request)
TODO"여기는 나중에 채워야 함"이라고 표시해 둔 빈칸
오케스트레이션여러 도구·단계를 순서대로 묶어 지휘하는 일

목적#

기능 구현 완료 직후 다음을 하나의 명령 으로 준비:

  1. Unit 레이어 — UseCase, Repository, BLoC 단위 테스트 (Mocktail + bloc_test)
  2. BDD 레이어(Patrol E2E 전용, 손으로 작성) — .feature + TestDriver step + Patrol 테스트를 전부 app/{app}/integration_test/에 마련합니다. 코드 생성기를 쓰지 않고, 위젯 테스트도 만들지 않습니다 — BDD 시나리오의 유일한 실행 경로는 손으로 쓴 Patrol E2E 입니다. feature 패키지 안에는 BDD 관련 파일이 하나도 남지 않습니다.
  3. 비-BDD 위젯 레이어(선택, Gherkin 과 무관) — 필요하면 feature 패키지 안에 통상적 testWidgets 위젯 테스트를 손으로 스캐폴드합니다. 이건 BDD .feature 에서 파생되지 않고, cc-flutter:widget-testing 이 다루는 완전히 별개의 테스트입니다.

각 레이어는 기존 조직 스킬에 위임 — 본 스킬은 오케스트레이션만 담당합니다.

트리거#

  • /cc-e2e:kick-off {feature} 실행
  • 기능 구현 직후 테스트 초안을 빠르게 갖추고 싶을 때
  • PR 제출 전에 "테스트 3 레이어가 모두 걸려 있는지" 확인할 때

전제#

  • feature 역할 패키지 이미 존재 (feature/{name}/{app,console,server,client}/) — 없으면 cc-bricks 또는 cc-flutter:feature:*로 먼저 scaffold
  • 해당 feature 의 도메인/데이터/프레젠테이션 3 레이어 코드가 최소 작성됨
  • package/test_driver 의존성이 앱(app/{app}/pubspec.yaml)에 반영됨. ⛔ bdd_test_gen/co_test_gen 은 더 이상 의존성으로 필요하지 않습니다 — 코드 생성기를 쓰지 않습니다.
  • (선택) flutter-skill MCP 사용 가능 — 탐색 단계 수행 시에만 필요

동작#

0. 배치 판정과 모듈/호스트 경계 (AD-2)#

먼저 .cob/feature-layout.json을 읽고, 없으면 cob의 기존 legacy 트리 판정 → 나머지는 bundle을 따른다. marker를 보존하고 생성 배치 계약을 적용한다. legacy 안내는 해당 판정인 프로젝트에만 적용한다.

검사 대상소유 위치실행
UI UseCase/BLoC/Repository·일반 위젯·골든 feature/{name}/{app,console}/test/ 각 <name>·console_<name> 패키지의 flutter test
순수 서버 서비스·정책·검증 (Session/DB 없음) feature/{name}/server/test/unit/ <name>_server 모듈의 dart test test/unit/
Session/DB/withServerpod·dispatcher·HTTP/SDK·교차 기능 backend/{project}_server/test/integration/ 실행 호스트의 통합 러너
Patrol BDD/E2Eapp/{app}/integration_test/실제 실행 앱의 Patrol 러너

bundle.json은 조립 선언이고 hostTests에 호스트 검증을 등재한다. host-integration.json은 포트·식별자·이름 등의 결합 계약이다. 호스트가 <Feature>HostBindings.bind(serverpod, …) / forSession(session)으로 세션 인지 포트를 연결한다. 바인딩 부재·미구현 어댑터가 fail-closed로 실패하는 검증도 호스트에 둔다. 통합 실행은 프로젝트의 격리 DB·세션별 포트/base-URL 계약을 사용하며 순수 모듈 테스트가 DB를 띄우지 않는다. 신규 기본 공식 4.0.3, donor 순수 테스트는 TZ=UTC에서도 실행한다.

1. 사전 조사#

  1. feature/{name}/{app,console}/lib/를 읽어 UseCase, BLoC, Repository 목록 수집; 서버 순수 코드와 호스트 결합도 확인
  2. feature/{name}/{app,console}/test/·server/test/unit/·호스트 test/integration/의 기존 테스트 조사 — 중복 생성 방지
  3. app/{app}/integration_test/features/{name}.feature 유무 판단 (⛔ feature 패키지 안이 아니라 항상 app 레벨을 본다)
  4. .claude/e2e-lock.json 에서 해당 feature 의 lock 된 시나리오 파악 (재저작 방지)

2. Unit 레이어 스캐폴드 (cc-flutter:unit-testing 위임)#

각 UseCase / BLoC / Repository 에 대해 테스트 파일 생성:

  • UseCase: test/src/domain/usecase/{use_case_name}_test.dart

    • Mocktail 로 Repository mock
    • should return entity when repository succeeds / should return failure when repository fails 기본 2 테스트
  • BLoC: test/src/presentation/bloc/{bloc_name}_test.dart

    • bloc_test 의 blocTest() 사용
    • emits [Loading, Loaded] / emits [Loading, Error] 기본 시나리오
  • Repository: test/src/data/repository/{repository_name}_test.dart

    • DataSource mock
    • 주요 메서드별 성공/실패 케이스

실제 스캐폴드는 cc-flutter/commands/test.md 의 /cc-flutter:test unit {target} 템플릿을 호출하여 일관성 유지.

3. BDD 레이어 (app/{app}/integration_test/, 손으로 작성 — 코드 생성기 없음)#

⛔ feature 패키지 안에는 아무것도 만들지 않습니다. .feature·step·Patrol 테스트 전부 app/{app}/integration_test/ 하나에 모입니다. 예전엔 "cross-feature 일 때만" 이관했지만, 이제는 애초에 그쪽에만 생깁니다 — 이관이라는 개념 자체가 없습니다.

3a. .feature 파일 확보

flag { app/{app}/integration_test/features/{name}.feature 존재 여부 }
├── 존재 → 그대로 사용 (경고 주석 제거 상태여야 함)
└── 없음
    ├── flutter-skill 세션 실행 가능 → /cc-e2e:explore → /cc-e2e:draft 연속 호출 제안
    └── 아니면 → 최소 skeleton 생성 (영어 + 한글 주석 포맷)

Skeleton 예시 (app/{app}/integration_test/features/{name}.feature):

# DRAFT — auto-generated skeleton by feature-test-bundle
# generated: {timestamp}
# ⚠️ Replace placeholder steps with actual scenarios before /cc-e2e:lock.

@smoke
@{feature_domain}
Feature: {Feature PascalCase} # {한글 이름}
  {one-line user value} # {한글 번역}

  Background:
    Given I am on the {feature} page # {feature} 페이지에 있다

  Scenario: Happy path for {feature} # {feature} 정상 흐름
    When I tap the {primary_action} button # {primary_action} 버튼 탭
    Then the {expected_state} should be displayed # {expected_state} 표시

3b. TestDriver step 파일 스캐폴드

.feature 의 Given/When/Then 을 파싱해 각 문장에 대응하는 step 파일 템플릿을 app/{app}/integration_test/step/에 생성:

  • 파일명: snake_case 영어 (i_tap_the_sign_in_button.dart)
  • 함수명: camelCase (iTapTheSignInButton)
  • 본문은 TestDriver 인자 + TODO 주석 (사람이 채움). 이미 bdd-canonical-steps 에 있는 표준 표현이면 로컬 step 을 새로 만들지 말고 package:test_driver/shared_steps.dart 를 import 해서 그대로 씁니다.
  • 필요한 K 상수가 package/test_driver/lib/src/key/k.dart 에 없으면 key-harden-targets.md 에 추가 등록

3c. Patrol 시나리오 파일 스캐폴드 — 배치가 기본값

.feature 의 Scenario 를 Background 기준으로 묶어 파일을 배분합니다 (rules/feature-file-conventions.md §5.1):

같은 Background 를 공유하는 Scenario 수 · @isolated 여부
├── 3건 이상 & @isolated 아님 → scenarios/{name}_{screen}_batch_test.dart  (배치, 기본)
└── 그 외 (@isolated · 2건 이하) → scenarios/{name}_{scenario}_test.dart   (1파일 1시나리오)

배치 템플릿 (코드 생성기 없음 — 이 스킬이 틀을 만들고 TODO 만 사람이 채웁니다):

void main() {
  patrolTest(
    '''{Screen} — batch ({N} scenarios)''',
    config: patrolConfig,
    ($) async {
      final driver = PatrolTestDriver($);

      // Background — 배치당 1회. .feature 의 Background step 순서대로
      await theAppIsInitialized($);
      // TODO: .feature 의 Background 를 여기 채운다 (도달까지만)

      await runScreenBatch(
        $,
        screen: '{screen_slug}',
        reset: () => the{Screen}ScreenIsReset($, driver),
        scenarios: {
          '{Scenario 이름 — .feature 그대로}': () async {
            // TODO: 이 Scenario 의 When/Then step 호출을 채운다
          },
          // ... 나머지 Scenario
        },
      );
    },
  );
}

단독 템플릿 (@isolated · 2건 이하):

void main() {
  patrolTest(
    '''{Scenario 이름 — .feature 그대로}''',
    config: patrolConfig,
    ($) async {
      final driver = PatrolTestDriver($);
      await theAppIsInitialized($);
      // TODO: .feature 의 Given/When/Then 순서대로 step 호출을 채운다
    },
  );
}

배치 파일을 만들 때는 화면 리셋 step 스텁도 함께 생성합니다 — step/the_{screen}_screen_is_reset.dart 에 3항 의무(닫기 · 되돌리기 · 앵커 단정)를 TODO 로 남기고, 앵커 Key 는 Background 마지막 Then 의 Key 를 기본 제안값으로 씁니다. helpers/screen_batch.dart 가 없으면 러너 스텁도 함께 만듭니다.

test_bundle.dart 에 import 추가, run_all_scenarios.sh 갱신.

배치 단위·리셋 계약·실패/BLOCKED 의미론의 정본은 cc-flutter/skills/patrol-bdd-conventions 의 "화면 단위 배치" 절입니다.

백엔드 정책 — Staging 을 먼저 고려, 로컬 풀스택은 조건부. app/{app}/integration_test/scenarios/ 의 Patrol 테스트가 실 서버와 통신해야 한다면, 프로젝트가 Serverpod 4+ 로 새로 시작한 것이 아니면 Staging을 기본으로 검토한다 — 앱의 기존 client/OpenApiService/flavor 폴백 설정을 따른다(base-URL 하드코딩 금지). 로컬 풀스택(serverpod start 한 명령으로 백엔드 서버 + 내장 Postgres + Flutter 앱을 함께 기동, http://localhost:8080/)은 Serverpod 4+ 신규 프로젝트에 최적화된 기능이며, 기존 Serverpod 3.x 프로젝트에서 흉내 내려면 비공식 서드파티 포크가 필요해 리스크가 있다 — 판단 기준은 patrol-bdd-conventions 의 "백엔드 대상" 절 참조. unit 레이어(+ BDD 와 무관한 일반 위젯 테스트)의 mock 은 그대로 유지. 이 노트는 Patrol· integration 레이어에만 적용된다. 로컬 풀스택을 쓰기로 했다면 셋업·MCP 연동은 serverpod-local-fullstack, serverpod-mcp-guide 스킬 참조. (3.x / legacy: 로컬 DB 는 docker compose up -d 로 기동, 통합테스트는 withServerpod + dart test -t integration — API 동일.)

4. 검증 (/cc-dev:pr:preflight 위임)#

스캐폴드 생성 직후 cc-dev:pr:preflight --scope={feature-packages} 를 자동 호출:

레이어/cc-dev:pr:preflight 실행 명령책임
Unit melos exec --scope=... -- flutter test --exclude-tags=integration /cc-dev:pr:preflight
Widget (BDD 와 무관한 일반 위젯 테스트만)위 명령에 포함/cc-dev:pr:preflight
Integration @smoke (조건부, BDD 시나리오 포함) patrol test --target=@smoke (디바이스 있을 때) /cc-dev:pr:preflight

각 레이어 실패 원인은 preflight 리포트에 그룹화해 제시. /cc-dev:pr:preflight 의 정책은 cc-dev/rules/pr-preflight-policy.md 참조.

5. 리포트#

사용자에게 출력:

  • 생성된 테스트 파일 수 (레이어별)
  • TODO 주석이 남은 step 파일 목록 (사람이 채워야 함)
  • 다음 단계 제안:
    • TODO 해결 → /cc-e2e:explore 로 실제 조작 확인 → /cc-e2e:draft → /cc-e2e:lock
    • 또는 빠른 경로: cc-flutter:test 에이전트로 unit/widget 완성

출력#

Unit 레이어#

  • feature/{name}/{app,console}/test/src/domain/usecase/*_test.dart
  • feature/{name}/{app,console}/test/src/presentation/bloc/*_test.dart
  • feature/{name}/{app,console}/test/src/data/repository/*_test.dart
  • feature/{name}/server/test/unit/*_test.dart (순수 서버 테스트)

호스트 DB 통합 레이어#

  • backend/{project}_server/test/integration/{name}_endpoint_test.dart
  • backend/{project}_server/test/integration/{name}_sdk_test.dart
  • bundle.json.hostTests 등재; 세션 인지 포트·미구현 fail-closed·실제 DB/SDK 계약 검증

BDD 레이어 (Patrol E2E 전용, app/{app}/integration_test/ 하나에만 생성)#

  • app/{app}/integration_test/features/{name}.feature (신규 or 기존 — 정본, 빌드 대상 아님)
  • app/{app}/integration_test/step/*.dart — TestDriver step 템플릿
  • app/{app}/integration_test/step/the_{screen}_screen_is_reset.dart — 배치용 리셋 스텁 (배치 1개당 1개)
  • app/{app}/integration_test/helpers/screen_batch.dart — 러너 스텁 (없을 때만)
  • app/{app}/integration_test/scenarios/{name}_{screen}_batch_test.dart — 배치 템플릿 (기본)
  • app/{app}/integration_test/scenarios/{name}_{scenario}_test.dart — 단독 템플릿 (@isolated·2건 이하)
  • test_bundle.dart, run_all_scenarios.sh 업데이트

위임 매트릭스#

레이어위임 대상호출 방식
Unit - UseCase cc-flutter:unit-testing /cc-flutter:test unit {UseCase} --feature {name}
Unit - BLoC cc-flutter:unit-testing + cc-flutter:bloc /cc-flutter:test bloc {Bloc} --feature {name}
Unit - Repository cc-flutter:unit-testing /cc-flutter:test unit {Repository} --feature {name}
Widget 개별 (BDD 와 무관, 손으로 작성) cc-flutter:widget-testing /cc-flutter:test widget {Widget} --feature {name}
BDD (Patrol E2E 전용, 손으로 작성) cc-flutter:bdd-testing 본 스킬이 스캐폴드만, 코드 생성기 없음
Patrol step 구현 cc-e2e:step-implement + patrol_mcp 후속 단계
공유 step 추출 cc-flutter:bdd-step-reuse-agent 2+ feature 에서 반복되는 step
검증(Unit+Widget+Integration@smoke) cc-dev:pr:preflight 스캐폴드 직후 자동 호출, 실패 시 PR 차단

금지 사항#

  • ❌ 기존 조직 스킬(cc-flutter:*) 의 템플릿을 재정의
  • ❌ feature 패키지 안에 .feature/test/src/bdd//builder 블록을 두는 것 — BDD 는 항상 app/{app}/integration_test/
  • ❌ BDD .feature 로부터 위젯 테스트를 만드는 것 (코드 생성이든 수동이든)
  • ❌ 배치 파일을 만들면서 리셋 step 스텁을 빠뜨리는 것 — 리셋 없는 배치는 상태 누수를 방치한다
  • ❌ 같은 시나리오를 배치 파일과 단독 파일에 양쪽 다 스캐폴드하는 것 (두 번 돌게 된다)
  • ❌ .feature 를 Korean-only 로 생성 (영어 + 한글 주석 필수)
  • ❌ flutter-skill MCP 를 본 스킬 내에서 직접 호출 — /cc-e2e:explore 커맨드를 통해서만
  • ❌ lock 된 feature 의 기존 시나리오 덮어쓰기 — draft 로 별도 생성

실패 케이스#

원인대응
feature 모듈 없음cc-bricks / cc-flutter:feature:* 안내
scenarios/*_test.dart 컴파일 실패 (step 이름 불일치) step 파일명/함수명 매핑 재검토, dart analyze — 코드 생성기가 없으므로 이 실수는 빌드가 아니라 dart analyze /리뷰에서 잡아야 한다
feature 패키지 안에 레거시 test/src/bdd/ 스캐폴드 발견 rules/bdd-test-patterns.md 마이그레이션 절차대로 제거 — Patrol scenarios/ 와 손으로 쓴 위젯 테스트로 분리 이관
K 상수 누락key-hardening 재실행 안내
.feature skeleton 만으로는 탐색 부족/cc-e2e:explore 로 실제 앱 탐색 권유

참고#

  • 조직 테스트 원전:
    • cc-flutter/skills/unit-testing/SKILL.md
    • cc-flutter/skills/widget-testing/SKILL.md
    • cc-flutter/skills/bdd-testing/SKILL.md
    • cc-flutter/skills/integration-testing/SKILL.md
  • 패턴 룰: cc-flutter/rules/bdd-test-patterns.md
  • 공유 step: cc-flutter/agents/bdd-step-reuse-agent.md
  • Patrol/E2E 백엔드 정책(Staging 우선, 로컬 풀스택 조건부): patrol-bdd-conventions, serverpod-local-fullstack, serverpod-mcp-guide
  • 본 플러그인 연계: skills/exploration, skills/scenario-draft, skills/key-hardening, skills/step-implement, skills/scenario-lock

검색어를 입력하면 문서를 찾습니다.

↑ ↓ 이동 · Enter 열기Esc 닫기