Feature Test Bundle โ unit + BDD dual + E2E 3-layer ๋์ ์ค์บํด๋#
ํ๋ง๋๋ก (๋น๊ฐ๋ฐ์์ฉ)#
์ ๊ธฐ๋ฅ์ ๋ง๋ค๋ฉด "์ด๊ฒ ์ ๋๋ก ์๋ํ๋์ง ํ์ธํ๋ ๊ฒ์ฌ ๋๊ตฌ(ํ ์คํธ)"๋ ๊ฐ์ด ์์ด์ผ ํฉ๋๋ค. ์ด ์คํฌ์ ๊ทธ ๊ฒ์ฌ ๋๊ตฌ 3์ข ์ธํธ๋ฅผ ๋ช ๋ น์ด ํ ๋ฒ์ผ๋ก ํ๊บผ๋ฒ์ ์ค๋นํด ์ฃผ๋ ์๋ ์กฐ๋ฆฝ ๋ผ์ธ์ ๋๋ค. ๋ง์น ์ ๊ฐ์ ์ ํ์ ์ถ๊ณ ํ๊ธฐ ์ , ๋ถํ ๊ฒ์ฌยท๊ธฐ๋ฅ ๊ฒ์ฌยท์ค์ฌ์ฉ ๊ฒ์ฌ๋ฅผ ํ ๋ฒ์ ์ธํ ํด ์ฃผ๋ ํ์ง ๊ด๋ฆฌ ๋ผ์ธ๊ณผ ๊ฐ์ต๋๋ค. ๊ฒ์ฌ์ 'ํ'์ ๋ง๋ค์ด ์ฃผ๊ณ , ์ธ๋ถ ๋ด์ฉ์ ์ฌ๋์ด๋ ๋ค๋ฅธ ๋๊ตฌ๊ฐ ์ฑ์ ๋ฃ์ต๋๋ค.
๋ฌด์์ยท์ธ์ (๋น๊ฐ๋ฐ์์ฉ)#
-
๋ฌด์์ ํด์ฃผ๋์
- ์์ ๋ถํ ๋จ์ ๊ฒ์ฌ(Unit), ์ฌ์ฉ์ ์๋๋ฆฌ์ค ๊ฒ์ฌ(BDD), ์ฌ๋ฌ ํ๋ฉด์ ๋๋๋๋ ์ค์ ํ๋ฆ ๊ฒ์ฌ(E2E) ์ธ ๊ฐ์ง ๊ฒ์ฌ ํ์ ํ ๋ฒ์ ๋ง๋ค์ด ์ค๋๋ค.
- ๊ฒ์ฌ ํ๋ง ์๋์ผ๋ก ๋ง๋ค๊ณ , ๋น์ ๋ ๋ถ๋ถ(์ฑ์์ผ ํ ๊ณณ)์ ๋ชฉ๋ก์ผ๋ก ์๋ ค ์ค๋๋ค.
- ๋ง๋ ์งํ ์๋์ผ๋ก ํ ๋ฒ ๊ฒ์ฌ๋ฅผ ๋๋ ค์, ๋น ์ง ๋ถ๋ถ์ด ์๋์ง ๋ฏธ๋ฆฌ ์ ๊ฒํฉ๋๋ค.
-
์ธ์ ์๋ํ๋์
/e2e:kick-off {๊ธฐ๋ฅ์ด๋ฆ}๋ช ๋ น์ ์คํํ์ ๋- ์ ๊ธฐ๋ฅ ๊ตฌํ์ ๋ง ๋๋ด๊ณ ๊ฒ์ฌ ์ด์์ ๋น ๋ฅด๊ฒ ๊ฐ์ถ๊ณ ์ถ์ ๋
- ์ฝ๋ ์ ์ถ(PR) ์ ์ "๊ฒ์ฌ 3์ข ์ด ๋ชจ๋ ๊ฑธ๋ ค ์๋์ง" ํ์ธํ๊ณ ์ถ์ ๋
ํต์ฌ ์ฉ์ด (๋น๊ฐ๋ฐ์์ฉ)#
| ์ฉ์ด | ์ฌ์ด ์ค๋ช |
|---|---|
| ํ ์คํธ(test) | ์ฝ๋๊ฐ ์๋๋๋ก ์๋ํ๋์ง ์๋์ผ๋ก ๊ฒ์ฌํ๋ ์ ์ฐจ |
| Unit(๋จ์) ํ ์คํธ | ๊ฐ์ฅ ์์ ๋ถํ ํ๋ํ๋๊ฐ ์ ๋๋ก ๋๋์ง ๋ณด๋ ๊ฒ์ฌ |
| BDD | "์ด๋ ๊ฒ ํ๋ฉด ์ด๋ ๊ฒ ๋๋ค"๋ ์ฌ์ฉ์ ํ๋ ๋ฌธ์ฅ์ผ๋ก ์ ๋ ์๋๋ฆฌ์ค ๊ฒ์ฌ ๋ฐฉ์ |
| E2E | ์์๋ถํฐ ๋๊น์ง, ์ฌ๋ฌ ํ๋ฉด์ ๋๋๋๋ ์ค์ ์ฌ์ฉ ํ๋ฆ์ ํต์งธ๋ก ๋ณด๋ ๊ฒ์ฌ |
| ์ค์บํด๋(scaffold) | ๋ด์ฉ์ ๋น์ด ์์ง๋ง ๋ผ๋๋ง ๊ฐ์ถ ํ(์์์ฉ ์์) |
| feature(๊ธฐ๋ฅ) | ์ฑ์์ ํ๋์ ๊ธฐ๋ฅ ๋ฌถ์(์: ๋ก๊ทธ์ธ, ๊ฒ์) |
.feature ํ์ผ | ์ฌ์ฉ์ ์๋๋ฆฌ์ค๋ฅผ ์ฌ๋์ด ์ฝ๊ธฐ ์ฌ์ด ๋ฌธ์ฅ์ผ๋ก ์ ์ด ๋ ํ์ผ |
| step(์คํ ) | ์๋๋ฆฌ์ค ๋ฌธ์ฅ ํ๋๋ฅผ ์ค์ ๋ก ์คํํ๋ ์์ ๋์ ์ ์ |
| Patrol | ์ค์ ์ฑ์ ์ผ์ ์ฌ๋์ฒ๋ผ ๋๋ฌ ๋ณด๋ฉฐ ๊ฒ์ฌํ๋ ๋๊ตฌ |
| widget(์์ ฏ) | Flutter ์ฑ ํ๋ฉด์ ์ด๋ฃจ๋ UI ์กฐ๊ฐ |
| mock(๋ชฉ) | ์ง์ง ๋์ ๋ผ์ ๋ฃ๋ ๊ฐ์ง ๋ถํ(๊ฒ์ฌ์ฉ ๋์ญ) |
| ๋ก์ปฌ ํ์คํ | ๋ด ์ปดํจํฐ์์ ์๋ฒยทDBยท์ฑ์ ํ๊บผ๋ฒ์ ๋์ ํต์งธ๋ก ๋๋ ค ๋ณด๋ ํ๊ฒฝ |
| PR | ์์ฑํ ์ฝ๋๋ฅผ ํฉ์น์๊ณ ์ฌ๋ฆฌ๋ ๋ณ๊ฒฝ ์์ฒญ(Pull Request) |
| TODO | "์ฌ๊ธฐ๋ ๋์ค์ ์ฑ์์ผ ํจ"์ด๋ผ๊ณ ํ์ํด ๋ ๋น์นธ |
| ์ค์ผ์คํธ๋ ์ด์ | ์ฌ๋ฌ ๋๊ตฌยท๋จ๊ณ๋ฅผ ์์๋๋ก ๋ฌถ์ด ์งํํ๋ ์ผ |
๋ชฉ์ #
๊ธฐ๋ฅ ๊ตฌํ ์๋ฃ ์งํ ๋ค์์ ํ๋์ ๋ช ๋ น ์ผ๋ก ์ค๋น:
- Unit ๋ ์ด์ด โ UseCase, Repository, BLoC ๋จ์ ํ ์คํธ (Mocktail + bloc_test)
-
BDD Dual ๋ ์ด์ด โ
.feature+ TestDriver step +bdd_test_genโ*.widget_test.dart+*.patrol_test.dart -
E2E ์๋๋ฆฌ์ค ๋ ์ด์ด โ cross-feature ๊ฐ ํ์ํ๋ฉด
integration_test/features/๋ก ์ด๊ด +test_bundle.dart๋ฑ๋ก
๊ฐ ๋ ์ด์ด๋ ๊ธฐ์กด ์กฐ์ง ์คํฌ์ ์์ โ ๋ณธ ์คํฌ์ ์ค์ผ์คํธ๋ ์ด์ ๋ง ๋ด๋นํฉ๋๋ค.
ํธ๋ฆฌ๊ฑฐ#
/e2e:kick-off {feature}์คํ- ๊ธฐ๋ฅ ๊ตฌํ ์งํ ํ ์คํธ ์ด์์ ๋น ๋ฅด๊ฒ ๊ฐ์ถ๊ณ ์ถ์ ๋
- PR ์ ์ถ ์ ์ "ํ ์คํธ 3 ๋ ์ด์ด๊ฐ ๋ชจ๋ ๊ฑธ๋ ค ์๋์ง" ํ์ธํ ๋
์ ์ #
-
feature ๋ชจ๋ ์ด๋ฏธ ์กด์ฌ (
feature/{type}/{name}/) โ ์์ผ๋ฉดcc-bricks-feature๋๋cc-flutter-dev:feature:*๋ก ๋จผ์ scaffold - ํด๋น feature ์ ๋๋ฉ์ธ/๋ฐ์ดํฐ/ํ๋ ์ ํ ์ด์ 3 ๋ ์ด์ด ์ฝ๋๊ฐ ์ต์ ์์ฑ๋จ
package/test_driver,bdd_test_gen์์กด์ฑ pubspec ์ ๋ฐ์- (์ ํ) flutter-skill MCP ์ฌ์ฉ ๊ฐ๋ฅ โ ํ์ ๋จ๊ณ ์ํ ์์๋ง ํ์
๋์#
1. ์ฌ์ ์กฐ์ฌ#
feature/{type}/{name}/lib/๋ฅผ ์ฝ์ด UseCase, BLoC, Repository ๋ชฉ๋ก ์์งfeature/{type}/{name}/test/src/์ ๊ธฐ์กด ํ ์คํธ ํ์ผ ์กฐ์ฌ โ ์ค๋ณต ์์ฑ ๋ฐฉ์งfeature/{type}/{name}/test/src/bdd/{name}.feature์ ๋ฌด ํ๋จ.claude/e2e-lock.json์์ ํด๋น feature ์ lock ๋ ์๋๋ฆฌ์ค ํ์ (์ฌ์ ์ ๋ฐฉ์ง)
2. Unit ๋ ์ด์ด ์ค์บํด๋ (cc-uiux: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.dartbloc_test์blocTest()์ฌ์ฉemits [Loading, Loaded]/emits [Loading, Error]๊ธฐ๋ณธ ์๋๋ฆฌ์ค
-
Repository:
test/src/data/repository/{repository_name}_test.dart- DataSource mock
- ์ฃผ์ ๋ฉ์๋๋ณ ์ฑ๊ณต/์คํจ ์ผ์ด์ค
์ค์ ์ค์บํด๋๋ cc-flutter-dev/commands/test.md ์ /test unit {target} ํ
ํ๋ฆฟ์ ํธ์ถํ์ฌ ์ผ๊ด์ฑ ์ ์ง.
3. BDD ๋ ์ด์ด ์ค์บํด๋#
3a. .feature ํ์ผ ํ๋ณด
flag { .feature ์กด์ฌ ์ฌ๋ถ }
โโโ ์กด์ฌ โ ๊ทธ๋๋ก ์ฌ์ฉ (๊ฒฝ๊ณ ์ฃผ์ ์ ๊ฑฐ ์ํ์ฌ์ผ ํจ)
โโโ ์์
โโโ flutter-skill ์ธ์
์คํ ๊ฐ๋ฅ โ /e2e:explore โ /e2e:draft ์ฐ์ ํธ์ถ ์ ์
โโโ ์๋๋ฉด โ ์ต์ skeleton ์์ฑ (์์ด + ํ๊ธ ์ฃผ์ ํฌ๋งท, @both ํ๊ทธ)
Skeleton ์์:
# DRAFT โ auto-generated skeleton by feature-test-bundle
# generated: {timestamp}
# โ ๏ธ Replace placeholder steps with actual scenarios before /e2e:lock.
@smoke
@{feature_domain}
Feature: {Feature PascalCase} # {ํ๊ธ ์ด๋ฆ}
{one-line user value} # {ํ๊ธ ๋ฒ์ญ}
Background:
Given I am on the {feature} page # {feature} ํ์ด์ง์ ์๋ค
@both
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 ํ์ผ ํ
ํ๋ฆฟ ์์ฑ:
- ํ์ผ๋ช
: snake_case ์์ด (
i_tap_the_sign_in_button.dart) - ํจ์๋ช
: camelCase (
iTapTheSignInButton) - ๋ณธ๋ฌธ์
TestDriver์ธ์ +TODO์ฃผ์ (์ฌ๋์ด ์ฑ์) -
ํ์ํ K ์์๊ฐ
package/test_driver/lib/src/key/k.dart์ ์์ผ๋ฉดkey-harden-targets.md์ ์ถ๊ฐ ๋ฑ๋ก
3c. build.yaml ํ์ธยท๊ฐฑ์
ํด๋น feature ํจํค์ง์ build.yaml ์ bdd_test_gen|dual_test_gen ํ์ฑ ํ์ธ. ์์ผ๋ฉด ํ์ค ๋ธ๋ก ์ถ๊ฐ:
targets:
$default:
builders:
bdd_test_gen|dual_test_gen:
enabled: true
generate_for:
- test/src/bdd/*.feature
options:
stepFolder: step
3d. ์ฝ๋ ์์ฑ
cd feature/{type}/{name}
dart run build_runner build --delete-conflicting-outputs
๊ฒฐ๊ณผ: *.widget_test.dart + *.patrol_test.dart (ํ์ ์ *.widget_test.dart
๋ง).
๋ฐฑ์๋ ์ ์ฑ โ Patrol/E2E ๋ ์ด์ด ๊ธฐ๋ณธ๊ฐ = ๋ก์ปฌ ํ์คํ. ์๋ ์์ฑ๋๋
*.patrol_test.dart(BDD dual ์ Patrol ์ธก) ์ ์๋ 4 ์ cross-feature E2E ๋ ์ด์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ก์ปฌ ํ์คํ ๋ฐฑ์๋๋ฅผ ๋์์ผ๋ก ํ๋ค โserverpod startํ ๋ช ๋ น์ด ๋ฐฑ์๋ ์๋ฒ + ๋ด์ฅ Postgres + Flutter ์ฑ์ ํจ๊ป ๊ธฐ๋ํ๋ฉฐ(Dockerยทdocker-compose ๋ถํ์), ์ฑ์ dev flavor ์์http://localhost:8080/๋ก ์ฐ๊ฒฐ๋๋ค. ํซ๋ฆฌ๋ก๋๊ฐ ๋งํ๋ฉดserverpod startํฐ๋ฏธ๋์์Rํค๋ก ์๋ฒ+์ฑ ๊ฐ์ ์ฌ์์. Staging ์ ํด๋ฐฑ(nightly/CI ๋๋ ๋ก์ปฌ ํ์คํ ๊ธฐ๋ ๋ถ๊ฐ ์)์ด๋ฉฐ ์ฑ์ ๊ธฐ์กด client/OpenApiService/flavor ํด๋ฐฑ ์ค์ ์ ๋ฐ๋ฅธ๋ค โ base-URL ํ๋์ฝ๋ฉ ๊ธ์ง. unit + widget ๋ ์ด์ด์ mock ์ ๊ทธ๋๋ก ์ ์ง (์ 2 / BDD widget_test ์ mock BLoC). ์ด ๋ ธํธ๋ Patrolยท integration ๋ ์ด์ด์๋ง ์ ์ฉ๋๋ค. ์ ์ ยทMCP ์ฐ๋์serverpod-local-fullstack,serverpod-mcp-guide์คํฌ ์ฐธ์กฐ. (3.x / legacy: ๋ก์ปฌ DB ๋docker compose up -d๋ก ๊ธฐ๋, ํตํฉํ ์คํธ๋withServerpod+dart test -t integrationโ API ๋์ผ.)
4. E2E ์๋๋ฆฌ์ค ๋ ์ด์ด (์ ํ์ )#
.feature ์ ์๋๋ฆฌ์ค ์ค cross-feature ๋๋ @patrol-only @journey ๋ก ํ๋จ๋๋ ์๋๋ฆฌ์ค:
app/{app}/integration_test/features/{name}.feature๋ก ์ด๊ดintegration_test/scenarios/{name}_{scenario_slug}_test.dart์์ฑtest_bundle.dart์ import ์ถ๊ฐrun_all_scenarios.sh๊ฐฑ์
ํ๋จ ํด๋ฆฌ์คํฑ:
@journeyํ๊ทธ- step ์์ 2๊ฐ ์ด์ feature ์ ํ์ด์ง ์ ์ด
-
์ค ๋ฐฑ์๋ ์ฐ๋ ํ์ (๊ธฐ๋ณธ = ๋ก์ปฌ ํ์คํ
serverpod start+ ๋ด์ฅ Postgres, Staging ํด๋ฐฑ โserverpod-local-fullstack)
5. ๊ฒ์ฆ (/pr:preflight ์์)#
์ค์บํด๋ ์์ฑ ์งํ cc-dev-cycle:pr:preflight --scope={feature-packages} ๋ฅผ ์๋ ํธ์ถ:
| ๋ ์ด์ด | /pr:preflight ์คํ ๋ช
๋ น | ์ฑ ์ |
|---|---|---|
| Unit | melos exec --scope=... -- flutter test --exclude-tags=integration |
/pr:preflight |
| Widget (BDD widget_test ํฌํจ) | ์ ๋ช ๋ น์ ํฌํจ | /pr:preflight |
| Integration @smoke (์กฐ๊ฑด๋ถ) | patrol test --target=@smoke (๋๋ฐ์ด์ค ์์ ๋) |
/pr:preflight |
๊ฐ ๋ ์ด์ด ์คํจ ์์ธ์ preflight ๋ฆฌํฌํธ์ ๊ทธ๋ฃนํํด ์ ์.
/pr:preflight ์ ์ ์ฑ
์ cc-dev-cycle/rules/pr-preflight-policy.md
์ฐธ์กฐ.
6. ๋ฆฌํฌํธ#
์ฌ์ฉ์์๊ฒ ์ถ๋ ฅ:
- ์์ฑ๋ ํ ์คํธ ํ์ผ ์ (๋ ์ด์ด๋ณ)
TODO์ฃผ์์ด ๋จ์ step ํ์ผ ๋ชฉ๋ก (์ฌ๋์ด ์ฑ์์ผ ํจ)-
๋ค์ ๋จ๊ณ ์ ์:
TODOํด๊ฒฐ โ/e2e:explore๋ก ์ค์ ์กฐ์ ํ์ธ โ/e2e:draftโ/e2e:lock- ๋๋ ๋น ๋ฅธ ๊ฒฝ๋ก:
cc-flutter-dev:test์์ด์ ํธ๋ก unit/widget ์์ฑ
์ถ๋ ฅ#
Unit ๋ ์ด์ด#
feature/{type}/{name}/test/src/domain/usecase/*_test.dartfeature/{type}/{name}/test/src/presentation/bloc/*_test.dartfeature/{type}/{name}/test/src/data/repository/*_test.dart
BDD Dual ๋ ์ด์ด#
feature/{type}/{name}/test/src/bdd/{name}.feature(์ ๊ท or ๊ธฐ์กด)feature/{type}/{name}/test/src/bdd/step/*.dartโ TestDriver step ํ ํ๋ฆฟfeature/{type}/{name}/test/src/bdd/{name}.widget_test.dartโ ์๋ ์์ฑfeature/{type}/{name}/test/src/bdd/{name}.patrol_test.dartโ ์๋ ์์ฑfeature/{type}/{name}/build.yamlโbdd_test_gen์ค์ ํ์ธ
E2E ์๋๋ฆฌ์ค ๋ ์ด์ด (์ ํ)#
app/{app}/integration_test/features/{name}.featureapp/{app}/integration_test/scenarios/{name}_{scenario}_test.darttest_bundle.dart,run_all_scenarios.sh์ ๋ฐ์ดํธ
์์ ๋งคํธ๋ฆญ์ค#
| ๋ ์ด์ด | ์์ ๋์ | ํธ์ถ ๋ฐฉ์ |
|---|---|---|
| Unit - UseCase | cc-uiux:unit-testing |
/test unit {UseCase} --feature {name} |
| Unit - BLoC | cc-uiux:unit-testing + cc-flutter-dev:bloc |
/test bloc {Bloc} --feature {name} |
| Unit - Repository | cc-uiux:unit-testing |
/test unit {Repository} --feature {name} |
| Widget ๊ฐ๋ณ | cc-uiux:widget-testing |
/test widget {Widget} --feature {name} |
| BDD Dual | cc-uiux:bdd-testing + cc-flutter-dev:bdd/generate |
๋ณธ ์คํฌ์ด bdd_test_gen ์คํ |
| Patrol step ๊ตฌํ | cc-e2e-authoring:step-implement + patrol_mcp |
ํ์ ๋จ๊ณ |
| ๊ณต์ step ์ถ์ถ | cc-flutter-dev:bdd-step-reuse-agent |
2+ feature ์์ ๋ฐ๋ณต๋๋ step |
| ๊ฒ์ฆ(Unit+Widget+Integration@smoke) | cc-dev-cycle:pr:preflight |
์ค์บํด๋ ์งํ ์๋ ํธ์ถ, ์คํจ ์ PR ์ฐจ๋จ |
๊ธ์ง ์ฌํญ#
- โ ๊ธฐ์กด ์กฐ์ง ์คํฌ(
cc-uiux:*,cc-flutter-dev:*) ์ ํ ํ๋ฆฟ์ ์ฌ์ ์ - โ
.widget_test.dart,.patrol_test.dart์๋ ์์ฑ๋ฌผ ์๋ ํธ์ง - โ
.feature๋ฅผ Korean-only ๋ก ์์ฑ (์์ด + ํ๊ธ ์ฃผ์ ํ์) - โ flutter-skill MCP ๋ฅผ ๋ณธ ์คํฌ ๋ด์์ ์ง์ ํธ์ถ โ
/e2e:explore์ปค๋งจ๋๋ฅผ ํตํด์๋ง - โ lock ๋ feature ์ ๊ธฐ์กด ์๋๋ฆฌ์ค ๋ฎ์ด์ฐ๊ธฐ โ draft ๋ก ๋ณ๋ ์์ฑ
์คํจ ์ผ์ด์ค#
| ์์ธ | ๋์ |
|---|---|
| feature ๋ชจ๋ ์์ | cc-bricks-feature / cc-flutter-dev:feature:* ์๋ด |
bdd_test_gen ๋น๋ ์คํจ | step ํ์ผ๋ช
/ํจ์๋ช
๋งคํ ์ฌ๊ฒํ , dart analyze |
| K ์์ ๋๋ฝ | key-hardening ์ฌ์คํ ์๋ด |
.feature skeleton ๋ง์ผ๋ก๋ ํ์ ๋ถ์กฑ | /e2e:explore ๋ก ์ค์ ์ฑ ํ์ ๊ถ์ |
| cross-feature ํ๋จ ์ ๋งค | ์ฌ์ฉ์ ์ ํ์ง (feature ๋ชจ๋ / app integration_test) |
์ฐธ๊ณ #
-
์กฐ์ง ํ
์คํธ ์์ :
cc-uiux/skills/unit-testing/SKILL.mdcc-uiux/skills/widget-testing/SKILL.mdcc-uiux/skills/bdd-testing/SKILL.mdcc-uiux/skills/integration-testing/SKILL.md
- ํจํด ๋ฃฐ:
cc-flutter-dev/rules/bdd-test-patterns.md - ๊ณต์ step:
cc-flutter-dev/agents/bdd-step-reuse-agent.md - ๋ก์ปฌ ํ์คํ ๋ฐฑ์๋(Patrol/E2E ๊ธฐ๋ณธ):
serverpod-local-fullstack,serverpod-mcp-guide -
๋ณธ ํ๋ฌ๊ทธ์ธ ์ฐ๊ณ:
skills/exploration,skills/scenario-draft,skills/key-hardening,skills/step-implement,skills/scenario-lock