| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /bdd:refactor-steps |
| Aliases | /bdd:refactor, /bdd:step-audit |
| Category | test-quality |
| Complexity | moderate |
/bdd:refactor-steps โ ํ ์คํธ ๋ฌธ์ฅ ์ค๋ณต ์ ๋ฆฌ ๋์ฐ๋ฏธ#
ํ๋ง๋๋ก#
์ฌ๋ฌ ํ ์คํธ์ ํฉ์ด์ง ๊ฐ์ ๋ป์ ๋ฌธ์ฅ์ ํ๋์ ํ์ค ๋ฌธ์ฅ์ผ๋ก ์ ๋ฆฌํด ์ฃผ๋ ๋ช ๋ น์ ๋๋ค. ํ์ฌ ๋ฌธ์๋ง๋ค ์ ๊ฐ๊ฐ ์ฐ๋ ํํ("ํ์ธ ๋ฐ๋๋๋ค" / "ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค" / "์ฒดํฌ ์๋ง")์ ํ๋์ ๊ณต์ ํํ์ผ๋ก ํต์ผํ๋ ๊ฒ๊ณผ ๊ฐ์์.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ํ ์คํธ ์ฝ๋๋ฅผ ์ ๋ฆฌยท์ ์ง๋ณด์ํ ๋
- ์ ๊ธฐ๋ฅ ํ ์คํธ๋ฅผ ์์ฑํ๊ธฐ ์ ์, ์ด๋ฏธ ์๋ ๋ฌธ์ฅ์ ์ฌ์ฌ์ฉํ ์ ์๋์ง ์ ๊ฒํ ๋
- ๊ฐ์ ํ ์คํธ ๋ฌธ์ฅ์ด ์ฌ๊ธฐ์ ๊ธฐ ์ค๋ณต๋ ๊ฒ ๊ฐ์ ๋
- ์ ํ
์คํธ๋ฅผ ์์ฑํ๋
/bdd:generate๋ช ๋ น์ ๋๋ฆฌ๊ธฐ ์ ์ฌ์ ์ ๊ฒ์ฉ์ผ๋ก
๋ฌด์์ ํด์ฃผ๋์#
- ํ๋ก์ ํธ ์ ์ฒด์ ํฉ์ด์ง ํ ์คํธ ๋ฌธ์ฅ(step ํ์ผ)์ ๋ชจ์ ์ค๋ณตยท์ ์ฌ ํํ์ ์ฐพ์๋ ๋๋ค.
- ์ด๋ค ๋ฌธ์ฅ์ด ๋ช ๋ฒ ์ค๋ณต๋๋์ง, ํ์ค์ผ๋ก ๋ฐ๊ฟ ๋์์ ๋ฌด์์ธ์ง ์ ๋ฆฌํ ์ฌ์ฌ์ฉ์ฑ ๋ณด๊ณ ์๋ฅผ ๋ง๋ค์ด ์ค๋๋ค.
-
--fix๋ชจ๋์์๋.featureํ์ผ์ ๋ฌธ์ฅ์ ํ์ค ํํ์ผ๋ก ๋ฐ๊พธ๊ณ , ์ค๋ณต๋ step ํ์ผ์ ์ ๋ฆฌํ ๋ค, ํ ์คํธ๊ฐ ๊ทธ๋๋ก ์ ํต๊ณผํ๋์ง๊น์ง ํ์ธํฉ๋๋ค.
์ด๋ป๊ฒ ์ฐ๋์#
/bdd:refactor-steps # ์ ์ฒด ํ๋ก์ ํธ ๋ถ์
/bdd:refactor-steps store # ํน์ feature๋ง ๋ถ์
/bdd:refactor-steps --fix # ์๋ ์์ ๋ชจ๋
/bdd:refactor-steps --report # ๋ณด๊ณ ์๋ง ์ถ๋ ฅ
/bdd:refactor-steps --scope console # ์ฝ์ feature๋ง
- ๋ค์
store๊ฐ์ ์ด๋ฆ์ ๋ถ์ด๋ฉด ๊ทธ ๊ธฐ๋ฅ ํ๋๋ง ๋ด ๋๋ค (์๋ตํ๋ฉด ์ ์ฒด). -
--report๋ ๊ณ ์น์ง ์๊ณ ๋ณด๊ณ ์๋ง,--fix๋ ๋ถ์ ํ ์ค์ ์์ ๊น์ง,--dry-run์ ๋ฐ๊ฟ ๋ด์ฉ์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ง ํฉ๋๋ค. -
--scope๋ก ๋ฒ์๋ฅผ ์ขํ ์ ์์ต๋๋ค (application,console,common).
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
- ๋ชจ์ผ๊ธฐ โ ํ๋ก์ ํธ ์ ์ฒด์ ํ ์คํธ ๋ฌธ์ฅ ํ์ผ์ ๊ธ์ด๋ชจ์ ๋ชฉ๋ก(์ธ๋ฒคํ ๋ฆฌ)์ ๋ง๋ญ๋๋ค.
- ์ค๋ณต ์ฐพ๊ธฐ โ ์์ ํ ๋๊ฐ์ ๋ฌธ์ฅ, ์ด์ง๋ง ๋ค๋ฅธ ๋น์ทํ ๋ฌธ์ฅ, "๋ฒํผ ์ด๋ฆ๋ง ๋ค๋ฅธ" ๋ฌธ์ฅ ๋ฑ์ ๋ถ๋ฅํฉ๋๋ค.
- ๋ณด๊ณ ์ ๋ง๋ค๊ธฐ โ ์ค๋ณต ํ์, ํ์คํ ๋์, ํ๋๋ก ํฉ์น ํ๋ณด๋ฅผ ํ๋ก ์ ๋ฆฌํฉ๋๋ค.
-
์๋ ์์ (
--fix) โ ํ์ค ํํ์ผ๋ก ๋ฌธ์ฅ์ ๋ฐ๊พธ๊ณ , ์ค๋ณต ํ์ผ์ ๊ณต์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ฎ๊ธด ๋ค ์ ๋ฆฌํ๊ณ , ๋น๋์ ํ ์คํธ๋ฅผ ๋ค์ ๋๋ ค ์ด์ ์๋์ง ํ์ธํฉ๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
Triggers#
- BDD ํ ์คํธ ์ ์ง๋ณด์ ์
- ์ feature ์์ฑ ์ ๊ธฐ์กด step ์ฌ์ฌ์ฉ ํ์ธ
- step ์ค๋ณต์ด ์์ฌ๋ ๋
/bdd:generate์คํ ์ ์ฌ์ ์ ๊ฒ
์ฌ์ฉ๋ฒ#
/bdd:refactor-steps # ์ ์ฒด ํ๋ก์ ํธ ๋ถ์
/bdd:refactor-steps store # ํน์ feature๋ง ๋ถ์
/bdd:refactor-steps --fix # ์๋ ์์ ๋ชจ๋
/bdd:refactor-steps --report # ๋ณด๊ณ ์๋ง ์ถ๋ ฅ
/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 ํ์ผ์ ์ค์บํ์ฌ ์ธ๋ฒคํ ๋ฆฌ๋ฅผ ๊ตฌ์ถํฉ๋๋ค.
# 1. ๋ชจ๋ step ํ์ผ ์์ง
find . -path " */test/src/bdd/step/*.dart " -exec basename {} \;
# 2. ์ค๋ณต ํ์ผ๋ช
์ง๊ณ
find . -path " */test/src/bdd/step/*.dart " -exec basename {} \; | sort | uniq -c | sort -rn
# 3. feature๋ณ step ์ ์ง๊ณ
for dir in feature/*/; do
count=$(find " $dir " -path " */test/src/bdd/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(WidgetTester tester, 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 ํ์ผ ์ ๊ฑฐ (externalSteps ์ค์ ํ):
- ๊ณต์ step ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ canonical step ์ถ๊ฐ
- ๊ฐ feature์
build.yaml์externalSteps์ถ๊ฐ - ๋ก์ปฌ ์ค๋ณต step ํ์ผ ์ญ์
melos run build์คํํ์ฌ ์์ฑ ์ฝ๋ ๊ฐฑ์melos run test:bdd์คํํ์ฌ ํ ์คํธ ํต๊ณผ ํ์ธ
4.3 build_runner ์ฌ์์ฑ
melos run build:clean & & melos run build
melos run test:bdd # ๋๋ test:bdd:selectCanonical 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 ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ตฌ์กฐ (co_test_gen ํตํฉ)#
๊ณต์ step์ test_driver (๋๋ co_test_gen) ํจํค์ง์ 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.yamlbuild.yaml ์ค์ (co_test_gen ์ฌ์ฉ ์)#
co_test_gen|dual_test_gen:
enabled: true
generate_for:
- test/src/bdd/*.feature
options:
stepFolder: step
sharedSteps: true # โ
ํ์ฑํ
sharedStepsImport: " package:test_driver/shared_steps.dart " # โ
import ๊ฒฝ๋ก
sharedStepNames: # โ
๋งค์นญ ๋ชฉ๋ก
- the_error_message_should_be_displayed
- the_loading_indicator_should_be_displayed
- the_success_message_should_be_displayed
- the_search_results_should_be_displayed
- i_enter_a_search_keyword
- i_confirm_deletion
- i_tap_the_next_page_button
- i_tap_the_search_button/bdd:generate์์ ์ฐ๋#
/bdd:generate ์คํ ์ ์๋์ผ๋ก Canonical Step Dictionary๋ฅผ ์ฐธ์กฐํฉ๋๋ค:
- ์ step ์์ฑ ์ : ๊ธฐ์กด step ๋ชฉ๋ก ์ค์บ
- ์ ์ฌ ํํ ๊ฐ์ง: Dictionary์ ๋น๊ตํ์ฌ ํ์ค ํํ ์ถ์ฒ
- ๊ณต์ step ์ฐ์ :
sharedStepNames์ ์ด๋ฏธ ์๋ step์ ๋ก์ปฌ ์์ฑ ์คํต - ๋ณด๊ณ : ์๋ก ์์ฑ๋ step vs ์ฌ์ฌ์ฉ๋ step ๋น์จ ํ์
๊ฒ์ฆ#
# 1. step ์ค๋ณต ๊ฒ์ฌ
find . -path " */test/src/bdd/step/*.dart " -exec basename {} \; | sort | uniq -c | sort -rn | head -20
# 2. build ํ์ธ
melos run build:clean & & melos run build
# 3. BDD ํ
์คํธ ์คํ
melos run test:bdd
# 4. ์ฌ์ฌ์ฉ๋ฅ ์ธก์
total=$(find . -path " */test/src/bdd/step/*.dart " | wc -l)
unique=$(find . -path " */test/src/bdd/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-dev/commands/bdd/generate.mdโ BDD ์์ฑ ์ปค๋งจ๋docs/discovery-bdd-step-reusability.mdโ Discovery ๋ถ์ ๋ณด๊ณ ์