LogoSkills

bdd:generate

BDD Feature file and Step Definition generation

ํ•ญ๋ชฉ๋‚ด์šฉ
Invoke/bdd:generate
Aliases/bdd, /test:bdd:generate
Categorypetmedi-workflow
Complexitymoderate
MCP Serverssequential, context7, serena

/bdd:generate#

Context Framework Note: This command generates BDD Feature files and Step Definitions.

Project Language Rules Summary#

CategoryLanguageNote
Feature title/descriptionKoreanWrite freely
Scenario title/descriptionKoreanWrite freely
Step patternEnglishKorean 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:analyze Phase 6
  • When adding BDD tests to existing Features

Context Trigger Pattern#

/bdd:generate {feature_name} [--options]

Parameters#

ParameterRequiredDescriptionExample
feature_name โœ… Feature๋ช… (snake_case) community
--entity-name โŒ Entity๋ช… (Auto-inferred) Post
--locationโŒFeature Locationapplication
--screens โŒ Screen Type "list,detail,form"
--from-claudedocs โŒ Copy from claudedocs true
--only-steps โŒ Generate Step Definitions only true
--run-buildโŒAuto Execute buildtrue

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#

  1. Function name: English camelCase (e.g., iSeeClassListScreen)
  2. Usage comment: English Step Pattern + Korean purpose description
  3. 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#

  1. Feature/Scenario title: Korean
  2. Step Pattern: Write in English (the Pattern the Package matches), and must attach Korean comments.
  3. 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#

AdvantageDescription
ReusabilityShare same steps across multiple feature files
MaintenanceChanges to step logic in one place reflect everywhere
ConsistencyUnified domain terminology and test patterns
Multi-packageCan share across packages via externalSteps
Auto-completeIDE provides hints based on Usage comments

MCP Integration#

TaskMCP ServerPurpose
Scenario structuringSequentialSystematic scenario design
Pattern referenceContext7bdd_widget_test Document
Code generationSerenaStep 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#

  1. Step patterns in English: Given/When/Then steps in .feature files must be in English
  2. Function names in English camelCase: iSeeClassListScreen, iTapEnrollButton
  3. Usage comment required: /// Usage: And I tap enroll button + /// Purpose: Tap the enroll button
  4. Korean parameters: Pass in {'Korean value'} format
  5. Reuse shared steps: Utilize package:core/src/test/bdd/bdd.dart
  6. Utilize externalSteps: Specify external step libraries in build.yaml
  7. Use tags: Classification tags like @smoke, @navigation
  8. 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 X button 118๊ฐœ
I tap the {'text'} textiTapTheTextํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ํƒญ
I enter {'val'} in the {'key'} widget iEnterInTheWidget I enter/select 67๊ฐœ
the {'key'} widget should be displayed theWidgetShouldBeDisplayed the X should be displayed 304๊ฐœ
the {'text'} text should be displayed theTextShouldBeDisplayed ํ…์ŠคํŠธ ๊ฒ€์ฆ
I wait for {'N'} secondsiWaitForSeconds๋Œ€๊ธฐ 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