/cc-flutter:bdd:refactor-steps — 테스트 문장 중복 정리 도우미#
| 항목 | 내용 |
|---|---|
| 실행 명령 | /cc-flutter:bdd:refactor-steps |
| 분류 | 테스트 품질 |
| 난이도 | ●●○ 보통 |
한마디로#
여러 테스트에 흩어진 같은 뜻의 문장을 하나의 표준 문장으로 정리해 주는 명령입니다. 회사 문서마다 제각각 쓰던 표현("확인 바랍니다" / "확인 부탁드립니다" / "체크 요망")을 하나의 공식 표현으로 통일하는 것과 같아요.
누가·언제 쓰나요#
- 테스트 코드를 정리·유지보수할 때
- 새 기능 테스트를 작성하기 전에, 이미 있는 문장을 재사용할 수 있는지 점검할 때
- 같은 테스트 문장이 여기저기 중복된 것 같을 때
- 새 테스트를 생성하는
/cc-flutter:bdd:generate명령을 돌리기 전 사전 점검용으로
무엇을 해주나요#
- 프로젝트 전체에 흩어진 테스트 문장(step 파일)을 모아 중복·유사 표현을 찾아냅니다.
- 어떤 문장이 몇 번 중복됐는지, 표준으로 바꿀 대상은 무엇인지 정리한 재사용성 보고서를 만들어 줍니다.
-
--fix모드에서는.feature파일의 문장을 표준 표현으로 바꾸고, 중복된 step 파일을 정리한 뒤, 테스트가 그대로 잘 통과하는지까지 확인합니다.
어떻게 쓰나요#
/cc-flutter:bdd:refactor-steps # 전체 프로젝트 분석
/cc-flutter:bdd:refactor-steps store # 특정 feature만 분석
/cc-flutter:bdd:refactor-steps --fix # 자동 수정 모드
/cc-flutter:bdd:refactor-steps --report # 보고서만 출력
/cc-flutter:bdd:refactor-steps --scope console # 콘솔 feature만
- 뒤에
store같은 이름을 붙이면 그 기능 하나만 봅니다 (생략하면 전체). -
--report는 고치지 않고 보고서만,--fix는 분석 후 실제 수정까지,--dry-run은 바꿀 내용을 미리보기만 합니다. -
--scope로 범위를 좁힐 수 있습니다 (application,console,common).
안에서 무슨 일이 벌어지나요#
- 모으기 — 프로젝트 전체의 테스트 문장 파일을 긁어모아 목록(인벤토리)을 만듭니다.
- 중복 찾기 — 완전히 똑같은 문장, 살짝만 다른 비슷한 문장, "버튼 이름만 다른" 문장 등을 분류합니다.
- 보고서 만들기 — 중복 횟수, 표준화 대상, 하나로 합칠 후보를 표로 정리합니다.
-
자동 수정(
--fix) — 표준 표현으로 문장을 바꾸고, 중복 파일을 공용 라이브러리로 옮긴 뒤 정리하고, 빌드와 테스트를 다시 돌려 이상 없는지 확인합니다.
⚙️ 상세 옵션·실행 명세 (개발자 / AI 에이전트용)
Triggers#
- BDD 테스트 유지보수 시
- 새 feature 작성 전 기존 step 재사용 확인
- step 중복이 의심될 때
/cc-flutter:bdd:generate실행 전 사전 점검
사용법#
/cc-flutter:bdd:refactor-steps # 전체 프로젝트 분석
/cc-flutter:bdd:refactor-steps store # 특정 feature만 분석
/cc-flutter:bdd:refactor-steps --fix # 자동 수정 모드
/cc-flutter:bdd:refactor-steps --report # 보고서만 출력
/cc-flutter:bdd:refactor-steps --scope console # 콘솔 feature만Parameters#
| Parameter | Required | Description | Example |
|---|---|---|---|
feature_name | No | 특정 feature 분석 (생략 시 전체) | store |
--fix | No | 분석 + 자동 수정 | true |
--report | No | 분석 보고서만 출력 | true |
--scope | No | feature 범위 제한 | application, console, common |
--dry-run | No | 변경 내용 미리보기 (수정 없음) | true |
실행 흐름#
Phase 1: Step 인벤토리 수집#
프로젝트 전체의 step 파일을 스캔하여 인벤토리를 구축합니다. BDD 관련 파일은 feature 패키지가
아니라 앱별 app/{app}/integration_test/step/ 한 곳에만 있습니다(정본:
plugins/cc-flutter/skills/bdd-testing/SKILL.md → "디렉토리 구조").
# 1. 모든 step 파일 수집
find app -path " */integration_test/step/*.dart " -exec basename {} \;
# 2. 중복 파일명 집계
find app -path " */integration_test/step/*.dart " -exec basename {} \; | sort | uniq -c | sort -rn
# 3. 앱별 step 수 집계
for dir in app/*/; do
count=$(find " $dir " -path " */integration_test/step/*.dart " | wc -l)
echo " $count $dir "
done | sort -rn수집 항목:
- step 파일명 (= step 표현)
- step 함수 시그니처
- 사용하는 feature 목록
- 구현 상태 (UnimplementedError / no-op / 실구현)
Phase 2: 중복 및 유사 step 분석#
2.1 정확히 동일한 step (Exact Duplicates)
동일한 파일명이 2개 이상의 feature에 존재하는 경우:
the_error_message_should_be_displayed.dart → 33개 feature
the_loading_indicator_should_be_displayed.dart → 27개 feature
the_search_results_should_be_displayed.dart → 16개 feature2.2 유사한 step (Near Duplicates)
Canonical Step Dictionary 기준으로 정규화 가능한 변형:
| 현재 표현 | 표준 표현 | 변환 규칙 |
|---|---|---|
the X is loaded | the X has loaded | tense 통일 |
the X is in loading state | the X is loading | 축약 |
an error message should be displayed | the error message should be displayed | 관사 통일 |
the book detail screen should be displayed | the book detail page should be displayed | screen→page |
I click the X button | I tap the X button | click→tap |
2.3 파라미터화 가능한 step
구체적인 이름만 다르고 구조가 동일한 step:
# 현재: 각각 별도 step
i_tap_the_search_button.dart (14개)
i_tap_the_save_button.dart (9개)
i_tap_the_delete_button.dart (7개)
i_tap_the_submit_button.dart (4개)
# 개선: 하나의 파라미터화된 step
# Gherkin: I tap the { ' search ' } button
# Step: iTapTheButton(TestDriver driver, String buttonName)Phase 3: 보고서 생성 (--report)#
## BDD Step 재사용성 보고서
### 요약
- 총 step 파일: {N}개
- 고유 step: {N}개
- 중복 step: {N}개 ({%}%)
- 제거 가능 파일: {N}개
### Top 20 중복 step
| Step | 중복 횟수 | 공유 라이브러리 후보 |
|------|:---------:|:------------------:|
| ... | ... | Yes/No |
### 표현 정규화 대상
| 현재 | 표준 | 영향 feature 수 |
|------|------|:---------------:|
| ... | ... | ... |
### 파라미터화 후보
| 패턴 | 현재 개별 step 수 | 통합 시 |
|------|:-----------------:|:------:|
| ... | ... | 1개 |Phase 4: 자동 수정 (--fix)#
4.1 .feature 파일 정규화
Canonical Step Dictionary에 따라 step 텍스트를 표준화:
# Before
Given the store is loaded
Then the error message should be displayed
# After
Given the store has loaded
Then the error message should be displayed # (이미 표준)4.2 중복 step 파일 정리
공유 step과 동일한 구현을 가진 로컬 step 파일 제거 (직접 import로 교체 후):
- 공유 step 라이브러리(
package/test_driver/lib/shared_steps.dart)에 canonical step 추가 - 로컬 중복 step 파일을 참조하던
app/{app}/integration_test/scenarios/*_test.dart의 호출부를 공유 step 직접 import(import 'package:test_driver/shared_steps.dart';)로 교체 - 로컬 중복 step 파일 삭제
patrol test --target integration_test/scenarios/{name}_test.dart실행하여 테스트 통과 확인
4.3 검증 재실행
⛔ melos run build / melos run test:bdd(:select) 는 폐지된 코드생성 파이프라인 명령입니다 —
BDD에는 재실행할 빌드가 없습니다.
cd app/{app}
patrol test --target integration_test/scenarios/{name}_test.dart
# 전수 순차 실행
./integration_test/run_all_scenarios.shCanonical Step Dictionary#
AI Agent가 .feature 파일 작성 시 반드시 참조해야 하는 표준 step 표현입니다.
Given (Setup) Steps#
| 표준 표현 | 용도 | 파라미터 |
|---|---|---|
I am on the {page} page | 페이지 설정 | {page}: snake_case |
the {page} has loaded | 데이터 로드 완료 | {page} |
the {page} is loading | 로딩 중 상태 | {page} |
the {page} is initialized | 초기화 완료 | {page} |
the {page} has an error | 에러 상태 | {page} |
the {page} is empty | 빈 상태 | {page} |
금지 변형:
→the X is loadedthe X has loaded→the X is in loading statethe X is loading→the X has been initializedthe X is initialized
When (Action) Steps — 범용 Key 기반#
| 표준 표현 | 용도 | 파라미터 | 공유 step |
|---|---|---|---|
I tap the {'widget_key'} widget | 모든 위젯 탭 | Widget Key | Yes |
I tap the {'text'} text | 텍스트 기반 탭 | 화면 텍스트 | Yes |
I enter {'value'} in the {'key'} widget | 모든 입력 | 값 + Key | Yes |
I wait for {'N'} seconds | 대기 | 초 | Yes |
I scroll down | 스크롤 | - | No |
I upload a file | 파일 업로드 | - | No |
금지 변형 (도메인 특화 step → 범용 step 사용):
→I tap the search buttonI tap the {'search_button'} widget→I tap the save buttonI tap the {'save_button'} widget→I select a book filterI tap the {'book_filter'} widget→I enter a search keywordI enter {'검색어'} in the {'search_field'} widget→I confirm deletionI tap the {'confirm_delete'} widget
Then (Assertion) Steps — 범용 Key 기반#
| 표준 표현 | 용도 | 파라미터 | 공유 step |
|---|---|---|---|
the {'widget_key'} widget should be displayed | 모든 위젯 표시 확인 | Widget Key | Yes |
the {'text'} text should be displayed | 텍스트 표시 확인 | 화면 텍스트 | Yes |
금지 변형 (도메인 특화 → 범용 사용):
→should be visibleshould be displayed(disabled/enabled과 구분)→should be shownshould be displayed→the X screen should be displayedthe X page should be displayed→an error message should be displayedthe error message should be displayed
공유 Step 라이브러리 구조#
공유 step은 test_driver 패키지의 shared_step/ 디렉토리에 위치합니다.
package/test_driver/
├── lib/
│ ├── test_driver.dart # 기존 Driver/Key/Step export
│ ├── shared_steps.dart # ★ 공유 step barrel export
│ └── src/
│ ├── driver/ # TestDriver 추상화
│ ├── key/ # Widget Key 상수 (K 클래스)
│ ├── step/ # 도메인별 Step 클래스
│ └── shared_step/ # ★ BDD 호환 공유 step 함수
│ ├── then/
│ │ ├── the_error_message_should_be_displayed.dart
│ │ ├── the_loading_indicator_should_be_displayed.dart
│ │ └── ... (11개)
│ └── when/
│ ├── i_tap_the_search_button.dart
│ ├── i_confirm_deletion.dart
│ └── ... (13개)
└── pubspec.yaml공유 Step Import (build.yaml 없음)#
⛔ co_test_gen/bdd_test_gen(|dual_test_gen) 빌더 설정은 더 이상 없습니다 — BDD 파이프라인
전체가 코드 생성을 쓰지 않으므로 sharedStepNames 로 등록할 대상 자체가 없습니다. 공유 step을
쓰려면 app/{app}/integration_test/scenarios/{name}_{scenario}_test.dart 상단에서 직접
import합니다:
import 'package:test_driver/shared_steps.dart';
shared_steps.dart 의 export 목록에 있는 이름은 전부 이렇게 바로 호출 가능합니다 —
the_error_message_should_be_displayed, the_loading_indicator_should_be_displayed,
the_success_message_should_be_displayed, i_confirm_deletion, i_tap_the_next_page_button 등
(전체 목록은 bdd-canonical-steps 참조).
/cc-flutter:bdd:generate와의 연동#
/cc-flutter:bdd:generate 실행 시 자동으로 Canonical Step Dictionary를 참조합니다:
- 새 step 작성 전: 기존 step 목록 스캔
- 유사 표현 감지: Dictionary와 비교하여 표준 표현 추천
- 공유 step 우선:
shared_steps.dart에 이미 있는 이름이면 로컬 파일을 만들지 않고 직접 import - 보고: 새로 작성된 step vs 재사용된 step 비율 표시
검증#
# 1. step 중복 검사
find app -path " */integration_test/step/*.dart " -exec basename {} \; | sort | uniq -c | sort -rn | head -20
# 2. Patrol E2E 실행 (⛔ melos run build / melos run test:bdd 는 폐지 — 재생성할 빌드가 없다)
cd app/{app}
patrol test --target integration_test/scenarios/{name}_test.dart
# 3. 재사용률 측정
total=$(find app -path " */integration_test/step/*.dart " | wc -l)
unique=$(find app -path " */integration_test/step/*.dart " -exec basename {} \; | sort -u | wc -l)
echo " 재사용률: $(( (total - unique) * 100 / total ))% "참조#
.claude/rules/bdd-test-patterns.md— BDD 테스트 패턴 규칙.claude/rules/patrol-bdd-conventions.md— Patrol BDD 통합 규칙cc-flutter/commands/bdd/generate.md— BDD 생성 커맨드docs/discovery-bdd-step-reusability.md— Discovery 분석 보고서