This project uses BDD (Behavior-Driven Development) style tests with TestDriver abstraction for dual test generation.
Test Hierarchy#
| Level | Tool | Location | Scope | CI |
|---|---|---|---|---|
| BDD Widget Test | bdd_test_gen / bdd_widget_test | feature/*/test/src/bdd/ |
Single feature, mock-based | Every push |
| Patrol E2E Test | patrol + bdd_test_gen | app/*/integration_test/ |
Cross-feature, real server | PR gate + Nightly |
| Golden Test | alchemist / golden_toolkit | feature/*/test/goldens/ |
Visual regression | PR changes |
TestDriver Abstraction (Recommended)#
Generate both widget tests and Patrol E2E tests from a single .feature file:
*.feature â bdd_test_gen Builder (build_runner)
âââ *.widget_test.dart (TestDriver + WidgetTestDriver)
âââ *.patrol_test.dart (TestDriver + PatrolTestDriver)
Step functions: TestDriver-based â reusable in both
K class: Unified widget Keys â must be assigned to actual widgets
Key Classes#
| Class | Purpose | Wraps |
|---|---|---|
TestDriver | Abstract interface | â |
WidgetTestDriver | Widget test driver | WidgetTester |
PatrolTestDriver |
E2E test driver | PatrolIntegrationTester |
K | Centralized widget Keys | â |
Directory Structure#
TestDriver-based (bdd_test_gen â recommended)#
feature/{type}/{name}/test/src/bdd/
âââ {name}.feature # Gherkin scenarios
âââ {name}.widget_test.dart # Auto-generated (DO NOT MODIFY)
âââ {name}.patrol_test.dart # Auto-generated (DO NOT MODIFY)
âââ step/ # Step functions (TestDriver-based)
âââ i_am_on_the_{page}.dart
âââ i_tap_the_{button}.dart
âââ the_{element}_should_be_displayed.dart
Legacy (bdd_widget_test)#
feature/{module}/test/src/bdd/
âââ {feature}_test.dart # Auto-generated
âââ hooks/
â âââ hooks.dart # Test setup/teardown hooks
âââ step/
âââ i_am_on_the_{page}.dart
âââ i_tap_the_{button}.dart
âââ the_{element}_should_be_displayed.dart
âââ ...
Step File Naming Rules#
Given (Preconditions)#
i_am_on_the_{page}.dart- On a specific pagethe_dashboard_has_loaded.dart- Dashboard has loadedthe_filter_data_is_available.dart- Filter data is available
When (Actions)#
i_tap_the_{button}.dart- Tap buttoni_select_{option}.dart- Select optioni_enter_{input}.dart- Enter input value
Then (Result Verification)#
the_{element}_should_be_displayed.dart- Element is displayedthe_{value}_should_be_{expected}.dart- Value matches expectedthe_{error}_should_be_displayed.dart- Error is displayed
build.yaml Configuration#
bdd_test_gen (TestDriver-based â recommended)#
targets:
$default:
builders:
bdd_test_gen|dual_test_gen:
enabled: true
generate_for:
- test/src/bdd/*.feature
options:
stepFolder: step
bdd_widget_test (legacy)#
targets:
$default:
builders:
bdd_widget_test|featureBuilder:
enabled: true
generate_for:
- test/src/bdd/*.feature
options:
stepFolders:
- test/src/bdd/step
Step Function Patterns#
TestDriver-based (Recommended)#
import 'package:test_driver/test_driver.dart';
/// Usage: I enter {string} in the email field # ėīëĐėž íëė ė
ë Ĩ
Future<void> iEnterInTheEmailField(TestDriver driver, String param1) async {
await driver.enterText(K.emailField, param1);
}
/// Usage: the book list should be displayed # ëė ëŠĐëĄ íė
Future<void> theBookListShouldBeDisplayed(TestDriver driver) async {
await driver.expectVisible(K.bookList);
}
WidgetTester-based (Legacy)#
import 'package:flutter_test/flutter_test.dart';
/// KPI card section should be displayed
void theKpiCardSectionShouldBeDisplayed(WidgetTester tester) {
expect(find.byType(SalesKpiSection), findsOneWidget);
}
Asynchronous Function (When Needed)#
import 'package:flutter_test/flutter_test.dart';
/// Tap button
Future<void> iTapTheSubmitButton(WidgetTester tester) async {
await tester.tap(find.text('Submit'));
await tester.pumpAndSettle();
}
When Parameter is Unused#
/// Dashboard has loaded (tester unused)
void theDashboardHasLoaded(WidgetTester _) {
// Only perform state setup
}
Test File Structure#
@Tags(['smoke', 'sales_analysis'])
import 'package:flutter_test/flutter_test.dart';
import './step/i_am_on_the_sales_analysis_page.dart';
import './step/the_dashboard_has_loaded.dart';
import './step/i_select_7_days_period.dart';
import './step/the_7_days_period_should_be_selected.dart';
void main() {
group('''Sales Analysis''', () {
Future<void> bddSetUp(WidgetTester tester) async {
await iAmOnTheSalesAnalysisPage(tester);
}
testWidgets('''Select 7 days period''', (tester) async {
await bddSetUp(tester);
theDashboardHasLoaded(tester);
await iSelect7DaysPeriod(tester);
the7DaysPeriodShouldBeSelected(tester);
}, tags: ['period_selection']);
});
}
Step File Creation Checklist#
- Create file:
test/src/bdd/step/{step_name}.dart - Add import: Add import to main test file
- Implement function: Implement appropriate verification/behavior logic
- Run tests:
flutter testormelos run test
Missing Step File Error#
error âĒ Target of URI doesn ' t exist: ' ./step/the_kpi_card_section_should_be_displayed.dart '
Solution:
- Create the missing step file
- Implement function following standard patterns
- Re-run analysis to verify
Scenario Target Tags (bdd_test_gen)#
@widget-only
Scenario: Mock-based loading state # Mock ęļ°ë° ëĄëĐ ėí
Given the server returns loading state
Then the loading indicator should be displayed
@patrol-only
Scenario: Native home button press # ëĪėīí°ëļ í ëēíž
When I press the home button
And I return to the app
Then the session should be restored
@both(default) â generates both widget + Patrol tests@widget-onlyâ widget test only (skipped in Patrol)@patrol-onlyâ Patrol E2E only (skipped in widget test)
Widget Test vs Patrol E2E Selection#
| Criteria | BDD Widget Test | Patrol E2E |
|---|---|---|
| Scope | Single feature/screen | Cross-feature flow |
| Backend | Mock | Real server (Staging) |
| Speed | Fast (milliseconds) | Slow (minutes) |
| Stability | High (deterministic) | Lower (network-dependent) |
| Native | Not available | Available (home, notifications) |
Precautions#
- File name = Function name: File and function names must match (snake_case to camelCase conversion)
-
Auto-generated code:
*.widget_test.dart,*.patrol_test.dart,*_test.dartâ DO NOT MODIFY - async/sync: TestDriver-based steps always use
Future<void>async -
pumpAndSettle caution: Use
pump()in WidgetTestDriver (avoids null check errors) -
Korean comment stripping:
#comments in step patterns are auto-removed by parser
Related Documents#
- Flutter test documentation
- See
bdd-testingskill in cc-uiux-testing for detailed TestDriver patterns