Build Flutter with BDD

Teresa Wu
Teresa Wu
Dec 3, 2020 · 7 min read
Photo by Peter Ivey-Hansen on Unsplash

In this article I am going to show you how to implement Behaviour Driven Development (BDD) in a Flutter project.

What is BDD

Behaviour Driven Development (BDD) is an Agile process that encourages collaboration between tech and non-tech people. It engages people to work closely together to create a common shared language (pure English),which enables building a solid contract between all parties involved in a project, engineers, product owners, QA etc.

I have to admit that I was one of those who misjudged the power of BDD. Luckily my company has decided to explore it in the beginning of this year (2020), beginning with a three days workshop with a BDD advocate. Back in February, the entire dev team was so excited about it, then few weeks later we went into lockdown. Remote working has added extra constraints but we managed to practice BDD without understanding everything perfectly, and we slowly evolved our process to adapt to it as best practice.

The BDD process

https://johnfergusonsmart.com/329-2/

Unlike TDD (Test Driven Development) which you can start in your own pace, BDD requires buy-in from everyone in your team. It is not just writing Gherkin and turning it into automation testing, by shifting left in testing, product owners are involved in the development cycle and testing has input at a much earlier stage in the product cycle.

If you are working in a company with cross functional teams, introducing BDD is much simpler, you can pick 1 or 2 small teams to spearhead the first BDD project, then gradually roll it out everywhere. This is what we did at Tide, and luckily, because I am working on a project with a new tech stack:

Given the mission is to get things done right in the beginning

And mitigate creating tech debt for the future self

When working with the new project

Then we used BDD

Okay, that was a bad BDD example because it cannot be converted into tests 😅

🔍 Discovery: because BDD is a process, discovery sits at the core and you aren’t doing proper BDD without it. During discovery the team and PO discuss together the story being refined, and reveal problems that would otherwise only have come up during development or in production. If during this process, you have noticed your refinement session has been extended from two to four hours, it might be because you are doing BDD right 😺

       +-------------------------+----------------------------+
| Typical project | BDD project |
+-------------------------+----------------------------+
| PO creates user stories | PO creates user stories |
| Explain it to engineers | Discovery and refinement |
| QA estimates test cycle | QA and dev write scenarios |
+-------------------------+----------------------------+

🗒️ Backlog: when developers are working in Sprint N, product owners and business analysts are creating Sprint N+1 stories in backlog, at this point, the stories are partially filled with scope of the feature and user acceptance criteria(UAC). They will be converted into BDD scenarios in discovery workshops. Below is an example of a ticket description in backlog.

+--------+---------------------------------------------------------+
| Scope | as a user |
| | I want to enter personal details
| | so I can create a profile |
+--------+---------------------------------------------------------+
| UAC | When a user login, then the user is shown profile screen|
| | When a user clicks edit, the user is shown edit screen |
| | When a user enters email, the app validates its format |
+--------+---------------------------------------------------------+

🌎 Example mapping: during a Sprint, the three Amigos (product owner/dev/tester) meet for a discovery meeting (refinement/backlog grooming/event storming) refine stories for future sprints. The goal is to reform above user acceptance criteria into clean and thorough Gherkin scenarios. Product owner decides what goes in the scope, while testers bring in edge cases and developers spice it up with details. This conversation can be carried through a structured Example Mapping, and thanks to the invention of post-it notes, the meeting is immensely colourful.

  • A story has multiple rules but having too many indicates the story is too big, and it should be sliced into multiple stories.
  • A rule summarises a bunch of examples and an endless list of examples indicates the rule is over complicated.
  • Questions lead us into deeper conversations and showing too many red cards implies the story is not ready.

🥒 Gherkin: followed by example mapping, developers and testers can meet asynchronously to fill up the Gherkin scenarios from examples.

Scenario the business rule from example mapping

Given some context as set up and it is not included in example mapping

When some action is performed, i.e. invoke _sut

Then assert the expected behaviour

        +----------+---------------------------------------+
| Scenario | create user profile with valid inputs |
+----------+---------------------------------------+
| Given | user supplies email postcode and name |
+----------+---------------------------------------+
| When | all fields are valid |
+----------+---------------------------------------+
| Then | assert the expected behaviour |
+----------+---------------------------------------+

Coding, finally 😄

If you have followed proper BDD steps, producing it in Flutter is just a piece of 🍰 … joking…my colleague Denis actually spent a good amount of time discovering tooling and methodology. Provided that you have set up the dependencies, you only need two files for each feature.

  1. Prerequisite: yaml, folder and main()

In this code example, we are using ogurets lib, so add it in your project yaml:

dev_dependencies:
flutter_test:
sdk: flutter
ogurets: ^3.2.0

Create a folder in your project naming it acceptance_test or anything you like, the file structure should look like below:

my_cool_project 
...
acceptance_test
acceptance_test.dart
features
create_profile.feature
steps
create_profile_steps.dart
...
lib
pubspec.yaml

In the acceptance_test.dart file, create functions to invoke your tests

import 'package:ogurets/ogurets.dart';Future main() async {
final def = OguretsOpts()
..feature("acceptance_test/features")
..steps("acceptance_test/steps")
..debug()
..failOnMissingSteps(true);
await def.run();
}

2. Gherkin in create_profile.feature

@profile
Feature: [Profile] Create User Profile

Scenario: Create user profile with valid inputs
Given User supplies email postcode and name
When All fields are valid
Then The user profile is created

Scenario: ...

3. Tests in create_profile_steps.dart

import 'package:ogurets/ogurets.dart';

class CreateProfileSteps {
static final _mockApi = MockApi();
final CreateProfileUseCase _sut;
Result<Iterable<Profile>, String> _getProfile;

static final _provider = ProfileDependenciesProvider(
_mockApi, () => _userId,
);

CreateProfileSteps() : _sut = _provider.makeCreateProfileUseCase {
...
}

@Given("User supplies email postcode and name")
Future givenProfile() async {
_mockApi.getResult = success(_userProfileResponse);
}


@When("All fields are valid")
Future whenFieldsAreValid() async {
_mockApi = await _sut();
}

@Then("The user profile is created")
Future then1() async {
assert(_getProfile is Success<Iterable<Profile>, String>);
}
}

4. TDD 🎁

When we talk about BDD, we just cannot avoid mentioning Test Driven Development (TDD), they associate with each other so often, that sometimes people mistakenly think BDD is another testing tool. Actually it did start as a way to help explain how to do TDD right, by writing tests that are more focused on the visible behaviour, and less coupled to implementation. But over time it has morphed more to the view focused on communication with stakeholders (further reading)

By this point, your project should be full of warnings suggesting things don’t exist 🎉 This is where BDD and TDD connects, while TDD is related to a test-first concept, fundamentally it is a technical tool, whereas BDD focus on the process, ensuring we are clear about what we are building, why we are building it and how we test.

Now we just need to create use cases, repositories and data mapping. If you aren’t sure how, I have this article explaining the following in Flutter. In the end, once you have taken out all warnings, run the following command to execute the tests:

dart — enable-asserts acceptance_test/acceptance_tests.dart

And if you are using Jira, rather than hiding your feature file in Git repo, you can sync it up with Living Documentation plugin, and share it with your team.

A quick recap

  • User story delivers value to end user and it changes behaviour of a domain i.e. create user profile, modify playlist, upload invoices
  • Domain expert (Product owner or Business Analyst) creates stories in backlog with User Acceptance Criteria
  • Each User Acceptance Criteria becomes a business rule in example mapping, then each rule will be converted as one acceptance scenario
  • Most importantly, BDD is not Unit test or UI test
+-----------------------+------------------------+-----------------+
| UI test | Acceptance Test | Unit Test |
+-----------------------+------------------------+-----------------+
| The button is enabled | Able to create playlist| Date formatting |
| Margin is 10px | Able to make payment | Add number |
+-----------------------+------------------------+-----------------+

Tide Engineering Team

Behind the scenes of the Tide Engineering team in London…