| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /bdd:generate |
| Aliases | /bdd, /test:bdd:generate |
| Category | petmedi-workflow |
| Complexity | moderate |
| MCP Servers | sequential, context7, serena |
/bdd:generate#
Context Framework Note: This command generates BDD Feature files and Step Definitions.
Project Language Rules Summary#
| Category | Language | Note |
|---|---|---|
| Feature title/description | Korean | Write freely |
| Scenario title/description | Korean | Write freely |
| Step pattern | English | Korean Usage comment required at top |
| Step parameter (UI text) | {'Korean value'} |
Passed as Dart string |
| Custom Step function | English camelCase | English/Korean annotation in Usage comment |
Triggers#
- When generating BDD tests for a Feature
/figma:analyzePhase 6- When adding BDD tests to existing Features
Context Trigger Pattern#
/bdd:generate {feature_name} [--options]
Parameters#
| Parameter | Required | Description | Example |
|---|---|---|---|
feature_name |
โ | Feature๋ช (snake_case) | community |
--entity-name |
โ | Entity๋ช (Auto-inferred) | Post |
--location | โ | Feature Location | application |
--screens |
โ | Screen Type | "list,detail,form" |
--from-claudedocs |
โ | Copy from claudedocs | true |
--only-steps |
โ | Generate Step Definitions only | true |
--run-build | โ | Auto Execute build | true |
Project Structure#
feature/{location}/{feature_name}/
โโโ lib/
โ โโโ ...
โ โโโ test_steps/ # ๋๋ฉ์ธ ํนํ ์คํ
๋ผ์ด๋ธ๋ฌ๋ฆฌ
โ โโโ common_steps.dart # Feature ๊ณต์ฉ ์คํ
โ โโโ {feature}_steps.dart # Feature ์ ์ฉ ์คํ
โโโ test/
โ โโโ features/ # .feature ํ์ผ
โ โ โโโ {feature}_list.feature
โ โ โโโ {feature}_detail.feature
โ โ โโโ {feature}_form.feature
โ โโโ steps/ # ์๋ ์์ฑ ์คํ
(build_runner)
โ โโโ ...
โโโ build.yaml
โโโ pubspec.yaml
build.yaml Configuration#
Recommended structure: Common steps in package/core, each feature imports via
externalSteps
targets:
$default:
sources:
include:
- " pubspec.yaml "
- $package$
- lib/$lib$
- lib/**.dart
- test/**
builders:
# BDD Widget Test - Feature ํ์ผ ๊ธฐ๋ฐ ํ
์คํธ ์์ฑ
bdd_widget_test|featureBuilder:
enabled: true
options:
# ์๋ ์์ฑ๋๋ ์คํ
์ ์ฅ ์์น
stepFolderName: test/steps
# ์ธ๋ถ ๊ณต์ฉ ์คํ
๋ผ์ด๋ธ๋ฌ๋ฆฌ
externalSteps:
# Core ๊ณตํต ์คํ
(์ฑ ์ ์ฒด ๊ณต์ฉ)
- package:core/src/test/bdd/common_steps.dart
- package:core/src/test/bdd/navigation_steps.dart
- package:core/src/test/bdd/widget_steps.dart
# Feature ์ ์ฉ ์คํ
(์ ํ์ฌํญ)
# - package:{feature}/test_steps/{feature}_steps.dart
Step Definition Writing Rules#
Core Rules#
- Function name: English camelCase (e.g.,
iSeeClassListScreen) - Usage comment: English Step Pattern + Korean purpose description
- Parameters: Korean UI text is passed in
{'Korean Value'}format
Step Definition Template#
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
// =============================================================================
// Given Steps
// =============================================================================
/// Usage: Given the app is running
/// ์ฉ๋: ์ฑ์ ์คํํจ
Future<void> theAppIsRunning(WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
}
/// Usage: And user is logged in as {email}
/// ์ฉ๋: {email} ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ๋ ์ํ๋ฅผ ์ค์
Future<void> userIsLoggedInAs(WidgetTester tester, String email) async {
// ํ
์คํธ์ฉ ๋ก๊ทธ์ธ ์ํ ์ฃผ์
}
// =============================================================================
// When Steps
// =============================================================================
/// Usage: When I tap {text} text
/// ์ฉ๋: {text} ํ
์คํธ๋ฅผ ํญํจ
Future<void> iTapText(WidgetTester tester, String text) async {
await tester.tap(find.text(text));
await tester.pumpAndSettle();
}
/// Usage: And I tap enroll button
/// ์ฉ๋: ๋ฑ๋ก ๋ฒํผ์ ํญํจ
Future<void> iTapEnrollButton(WidgetTester tester) async {
await tester.tap(find.widgetWithText(ElevatedButton, '๋ฑ๋ก'));
await tester.pumpAndSettle();
}
// =============================================================================
// Then Steps
// =============================================================================
/// Usage: Then I see {text} text
/// ์ฉ๋: {text} ํ
์คํธ๊ฐ ํ์๋๋์ง ํ์ธ
Future<void> iSeeText(WidgetTester tester, String text) async {
expect(find.text(text), findsOneWidget);
}
/// Usage: And I see class list screen
/// ์ฉ๋: ์์
๋ชฉ๋ก ํ๋ฉด์ด ํ์๋๋์ง ํ์ธ
Future<void> iSeeClassListScreen(WidgetTester tester) async {
expect(find.byType(ClassListView), findsOneWidget);
}
/// Usage: And I see at least {count} class items
/// ์ฉ๋: ์ต์ {count}๊ฐ์ ์์
ํญ๋ชฉ์ด ํ์๋๋์ง ํ์ธ
Future<void> iSeeAtLeastClassItems(WidgetTester tester, int count) async {
final items = find.byType(ClassListTile);
expect(items, findsAtLeast(count));
}
Feature File Writing Guide#
Core Rules#
- Feature/Scenario title: Korean
- Step Pattern: Write in English (the Pattern the Package matches), and must attach Korean comments.
- Parameters:
{'Korean Value'}Format
Examples: List Screen#
Feature: ์์
๋ฑ๋ก
ํ์์ด ์ํ๋ ์์
์ ๊ฒ์ํ๊ณ ๋ฑ๋กํ ์ ์๋ ๊ธฐ๋ฅ์ ํ
์คํธํ๋ค.
๋ฑ๋ก ์๋ฃ ํ ๋ง์ดํ์ด์ง์์ ๋ฑ๋ก๋ ์์
์ ํ์ธํ ์ ์์ด์ผ ํ๋ค.
Background:
# ์ฑ ์คํ ๋ฐ ๋ก๊ทธ์ธ ์ํ ์ค์
Given the app is running # ์ฑ์ด ์คํ๋จ
And user is logged in as { ' student@school.com ' } # ์ฃผ์ด์ง ์ด๋ฉ์ผ๋ก ๋ก๊ทธ์ธ ์ํ์
@smoke
Scenario: ์์
๋ชฉ๋ก ์กฐํ
์ฌ์ฉ์๊ฐ ์์
ํญ์ ์ ํํ๋ฉด ํ์ฌ ๋ฑ๋ก ๊ฐ๋ฅํ ์์
๋ชฉ๋ก์ด ํ์๋๋ค.
# ์์
ํญ์ผ๋ก ์ด๋
When I tap { ' ์์
' } text # ' ์์
' ํ
์คํธ๋ฅผ ํญํจ
# ์์
๋ชฉ๋ก ํ๋ฉด ํ์ธ
Then I see class list screen # ์์
๋ชฉ๋ก ํ๋ฉด์ด ํ์๋๋์ง ํ์ธ
And I see at least {3} class items # ์ต์ 3๊ฐ์ ์์
ํญ๋ชฉ์ด ํ์๋๋์ง ํ์ธ
@enrollment @payment
Scenario: ์ ๋ฃ ์์
๋ฑ๋ก
์ฌ์ฉ์๊ฐ ์ ๋ฃ ์์
์ ์ ํํ๊ณ ๊ฒฐ์ ๋ฅผ ์๋ฃํ๋ฉด ๋ฑ๋ก์ด ์๋ฃ๋๋ค.
# ์์
์ ํ
Given I am on class list screen # ์์
๋ชฉ๋ก ํ๋ฉด์ ์์
When I tap { ' ๊ณ ๊ธ ์ํ ' } text # ' ๊ณ ๊ธ ์ํ ' ํ
์คํธ๋ฅผ ํญํจ
# ๋ฑ๋ก ๋ฒํผ ํด๋ฆญ
And I tap enroll button # ๋ฑ๋ก ๋ฒํผ์ ํญํจ
# ๊ฒฐ์ ์งํ
And I complete payment with { ' ์นด๋ ' } # ' ์นด๋ ' ๊ฒฐ์ ์งํ
# ๋ฑ๋ก ์๋ฃ ํ์ธ
Then I see { ' ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค ' } text # ' ๋ฑ๋ก์ด ์๋ฃ๋์์ต๋๋ค ' ํ
์คํธ๊ฐ ํ์๋จ
And class { ' ๊ณ ๊ธ ์ํ ' } appears in my enrolled list # ๋ด ๋ฑ๋ก ๋ด์ญ์์ ' ๊ณ ๊ธ ์ํ ' ์ด ๋ํ๋จ
Examples: Form Screen#
Feature: ์์ ์ ์ถ
ํ์์ด ์์ ๋ฅผ ํ์ธํ๊ณ ์ ์ถํ ์ ์๋ ๊ธฐ๋ฅ์ ํ
์คํธํ๋ค.
Background:
Given the app is running # ์ฑ์ด ์คํ๋จ
And user is logged in as { ' student@school.com ' } # ์ฃผ์ด์ง ์ด๋ฉ์ผ๋ก ๋ก๊ทธ์ธ ์ํ์
@homework @submit
Scenario: ์์ ํ์ผ ์ ์ถ
ํ์์ด ์์ ์ ํ์ผ์ ์ฒจ๋ถํ๊ณ ์ ์ถํ๋ฉด ์ ์ถ ์๋ฃ ์ํ๊ฐ ๋๋ค.
Given I am on class list screen # ์์
๋ชฉ๋ก ํ๋ฉด์ ์์
When I tap { ' ์์ ' } text # ' ์์ ' ํ
์คํธ๋ฅผ ํญํจ
And I tap first homework item # ์ฒซ ๋ฒ์งธ ์์ ํญ๋ชฉ์ ํญํจ
And I attach file { ' answer.pdf ' } # ' answer.pdf ' ํ์ผ์ ์ฒจ๋ถํจ
And I tap submit button # ์ ์ถ ๋ฒํผ์ ํญํจ
Then I see { ' ์ ์ถ ์๋ฃ ' } text # ' ์ ์ถ ์๋ฃ ' ํ
์คํธ๊ฐ ํ์๋จ
And homework status is { ' ์ ์ถ๋จ ' } # ์์ ์ํ๊ฐ ' ์ ์ถ๋จ ' ์
Using Predefined Steps#
bdd_widget_test Package default provided steps:
# ์ด๋ฏธ ๊ตฌํ๋์ด ์์ - ์ปค์คํ
์คํ
๋ถํ์
Then I see { ' ํ์ํฉ๋๋ค ' } text # ' ํ์ํฉ๋๋ค ' ํ
์คํธ๊ฐ ํ์๋๋์ง ํ์ธ
And I tap { ' ํ์ธ ' } text # ' ํ์ธ ' ํ
์คํธ๋ฅผ ํญํจ
And I don ' t see { ' ๋ก๋ฉin progress ' } text # ' ๋ก๋ฉin progress ' ํ
์คํธ๊ฐ ํ์๋์ง ์๋์ง ํ์ธ
And I see {Icons.check} icon # ์ฒดํฌ ์์ด์ฝ์ด ํ์๋๋์ง ํ์ธ
And I enter { ' test@email.com ' } into { ' ์ด๋ฉ์ผ ' } input field # ' ์ด๋ฉ์ผ ' ์
๋ ฅ ์นธ์ ' test@email.com ' ์ ์
๋ ฅํจ
Multi-Package Environment Setup#
To share steps across multiple packages, use bdd_options.yaml:
bdd_options.yaml (Project root)
include: package:core/bdd_options.yaml
externalSteps:
- package:core/src/test/bdd/bdd.dart
- package:{feature}/test_steps/common_steps.dart
hooks.dart Configuration#
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
// Mock imports
import '../../mocks/mocks.dart';
/// BDD ํ
์คํธ Setup Hook
Future<void> setUp(WidgetTester tester) async {
// Fallback values ๋ฑ๋ก
registerFallbackValues();
}
/// BDD ํ
์คํธ TearDown Hook
Future<void> tearDown(WidgetTester tester) async {
// Pure DI: getIt.reset() ๋ถํ์
}
/// Fallback values ๋ฑ๋ก
void registerFallbackValues() {
// ํ์ํ fallback values ๋ฑ๋ก
// registerFallbackValue(Fake{Entity}());
}
Domain Step Library Advantages#
| Advantage | Description |
|---|---|
| Reusability | Share same steps across multiple feature files |
| Maintenance | Changes to step logic in one place reflect everywhere |
| Consistency | Unified domain terminology and test patterns |
| Multi-package | Can share across packages via externalSteps |
| Auto-complete | IDE provides hints based on Usage comments |
MCP Integration#
| Task | MCP Server | Purpose |
|---|---|---|
| Scenario structuring | Sequential | Systematic scenario design |
| Pattern reference | Context7 | bdd_widget_test Document |
| Code generation | Serena | Step Definition Generation |
Examples#
Basic Usage#
/bdd:generate community
Copy from claudedocs + Build#
/bdd:generate community \
--from-claudedocs true \
--run-build true
Generate Step Definitions Only#
/bdd:generate community \
--only-steps true
Generate Specific Screens Only#
/bdd:generate community \
--screens " list,detail "
Test Execution#
# Code generation
dart run build_runner build --delete-conflicting-outputs
# watch mode (recommended during development)
dart run build_runner watch --delete-conflicting-outputs
# Tests ์คํ
flutter test
# Tag filtering
flutter test --tags enrollment
flutter test --exclude-tags payment
# Run specific feature files only
flutter test test/features/class/
melos Commands#
# Generate BDD test code
melos run test:bdd:generate --scope={feature_name}
# Run BDD tests
melos run test:bdd --scope={feature_name}
# ์ ์ฒด ์คํ (์์ฑ + ํ
์คํธ)
melos run test:bdd:full --scope={feature_name}
Core Rules Summary#
-
Step patterns in English: Given/When/Then steps in
.featurefiles must be in English -
Function names in English camelCase:
iSeeClassListScreen,iTapEnrollButton -
Usage comment required:
/// Usage: And I tap enroll button+/// Purpose: Tap the enroll button - Korean parameters: Pass in
{'Korean value'}format - Reuse shared steps: Utilize
package:core/src/test/bdd/bdd.dart - Utilize externalSteps: Specify external step libraries in build.yaml
- Use tags: Classification tags like
@smoke,@navigation - Step ์ฌ์ฌ์ฉ ์ฐ์ : ์ step ์์ฑ ์ ๋ฐ๋์ ๊ธฐ์กด step ์ธ๋ฒคํ ๋ฆฌ ๊ฒ์ (์๋ ์ฐธ์กฐ)
Step ์ฌ์ฌ์ฉ ํ์ ์ ์ฐจ (Pre-Generation Check)#
์ .feature ํ์ผ ์์ฑ ์ ๋ฐ๋์ ์คํ:
# ๊ธฐ์กด step ์ธ๋ฒคํ ๋ฆฌ์์ ์ ์ฌ step ๊ฒ์
find . -path " */test/src/bdd/step/*.dart " -exec basename {} \; | sort -u | grep -i " {keyword} "
๋ฒ์ฉ Key ๊ธฐ๋ฐ ๊ณต์ Step (/bdd:refactor-steps ์ฐธ์กฐ):
| Step ํจํด | ๊ณต์ step ํจ์ | ๋์ฒด ๋์ |
|---|---|---|
I tap the {'key'} widget |
iTapTheWidget |
|
I tap the {'text'} text | iTapTheText | ํ ์คํธ ๊ธฐ๋ฐ ํญ |
I enter {'val'} in the {'key'} widget |
iEnterInTheWidget |
|
the {'key'} widget should be displayed |
theWidgetShouldBeDisplayed |
|
the {'text'} text should be displayed |
theTextShouldBeDisplayed |
ํ ์คํธ ๊ฒ์ฆ |
I wait for {'N'} seconds | iWaitForSeconds | ๋๊ธฐ step |
{'key'} = Widget Key ๋ฌธ์์ด (K ํด๋์ค ์์์ ์ผ์น):
๊ฒฐ๊ณผ: ๊ธฐ์กด step๊ณผ ๋งค์นญ๋๋ฉด ์ step ํ์ผ ์์ฑ ์คํต, ๊ธฐ์กด ํ์ผ ์ฌ์ฌ์ฉ.
Reference Agents#
- Detailed implementation rules:
~/.claude/commands/agents/bdd-scenario-agent.md - Step ์ฌ์ฌ์ฉ ๋ถ์:
~/.claude/agents/bdd-step-reuse-agent.md - Step ๋ฆฌํฉํ ๋ง:
/bdd:refactor-steps