0 purchases
bdd framework
BDD Framework #
This package is brought to you by Zaher
Hammoud and me, Marcelo Glasberg.
Note there is also a npm version of this package for
JavaScript, TypeScript, React, React Native, Node etc.
This package supports and simplifies Behavior
Driven Development (BDD) for Dart/Flutter.
BDD is a fantastic development technique that empowers you to build comprehensive, well-documented,
well-tested, and easily adjustable code. It enhances the collaboration between developers and
"business stakeholders", which include Project and Product managers, Product owners, Business analysts, QA
engineers, testers, and sometimes even clients.
Despite its numerous benefits, not all companies adopt BDD. One reason behind this is that
traditional BDD frameworks tend to complicate the process, discouraging developers from writing
BDDs. And let's be honest, if it's challenging to write BDDs, they simply won't get written.
In the past, I championed writing BDDs manually, bypassing the need for a framework. This approach
has its limitations though, particularly for larger teams. It becomes difficult to establish a
standard for writing BDDs, and potentially leaves other staff members in the dark. A suitable
framework can solve this, as long as it eliminates unnecessary complexity and puts the developers'
needs at the forefront.
That's where this package comes in. It's designed to make writing BDDs an absolute breeze,
encouraging developers to adopt it. And in spite of its "developer-centric" approach, I believe
that business stakeholders will also appreciate and prefer the streamlined process it introduces.
Here's what makes this framework stand out:
You can create BDD tests entirely in code, no need for .feature files.
It is developer-centric, catering primarily to the needs of developers, rather than business
stakeholders.
But to keep the business stakeholders in the loop, you have the option to export to
Gherkin/Cucumber .feature files, automatically, each time the tests are run.
There's no need for "glue files" or glue code to extract information from .feature files.
It doesn't use generated code. You can skip including the build_runner package.
To aid code development, and also to
support TDD in case you need
it, it provides clear, colorful and easily understandable error messages in the console when
assertions fail.
It's user-friendly for both individual developers and teams.
It has an IntelliJ plugin to support
it (see here).
It encourages writing more BDDs, by greatly simplifying the process.
It uses the Gherkin Syntax. Note: Other syntaxes are possible, with
some adaptation.
I launched this package in 2022, but I didn't get around to documenting it extensively. Now, in
August 2023, I'm about to rectify that by providing this thorough documentation and a comprehensive BDD
tutorial, complete with a real example. Whether you need to learn BDD, or you are experienced and looking to
refine your technique, I've got you covered. Get ready to master creating BDDs in practice!
1. Understanding how other BDD frameworks operate #
Before we dive deeper, let's take a moment to understand how typical BDD frameworks function, and
why they might not be my cup of tea.
Typically, the process follows these steps:
Some business stakeholders pen down BDD specs in structured text files called "feature files". These files detail
specific features that your software must provide.
Developers then create "glue files" to pair with these feature files. They essentially glue
the human-readable specifications to the executable code, by reading the specification text and
converting it to typed values in real code variables.
Next, developers code and execute the BDDs, where both the input values and expected outcomes
were created by the glue code from the feature files.
If any issues or missing details surface in the feature files, developers must alert the business
stakeholders, wait for necessary amendments, then revisit the glue files and implement fixes in
the code.
Similarly, when developers identify absent BDDs, they must request the business stakeholders to
generate more feature files.
Let's illustrate this with an example. Say, a business analyst drafted the following feature file:
Scenario: Selling stocks
Given user Alice has 10 shares of IBM.
When Alice sells 8 shares to Bill for $2 each.
Then Alice gets $16.
copied to clipboard
Your role as a developer would be first to construct a glue file, extracting all the data
points: Alice, $10, IBM, Bill, $2, and $20. Remember, as a feature file is textual,
these values are text strings.
However, for the test code, you'd require Alice and Bill as User objects, IBM as
a Stock object, and the monetary figures as Money objects. So, you'll need to code a mechanism
to transform these string data into typed objects.
The catch is, any modification in these values by the business analyst might break the test. For
instance, if Alice sells 12 shares instead of 8, that's a problem because she only owns 10. Such a
change would necessitate code adjustments to ensure the test passes.
Ideally, the coded solution should cover all possible scenarios. To maximize the benefits of BDD,
the business analyst should craft the feature file to encompass all relevant data variations.
Here's an updated version:
Scenario: Selling stocks
Given user Alice has <number> shares of <stock>.
When Alice sells <number sold> shares to Bill for <price> each.
Then Alice gets <balance>.
Example:
number | stock | number sold | price | balance
10 | IBM | 8 | 2 | 16
10 | IBM | 12 | 2 | error
10 | IBM | 8 | 0 | error
copied to clipboard
This feature file is more complex. Coming up with it may have required significant back-and-forth
between the developer and the business analysts.
The business analysts were forced into this complexity, even if they might
have preferred separate BDDs for error scenarios, rather than lumping them into a single BDD.
A potential alternative could be for the developer to skip the glue file, and hard-code the values
as actual objects straight into the test code, instead of reading them from the feature file. This
approach, however, carries the risk of the feature file becoming disjointed from the code. If the
business analyst amends the feature file, the developer might overlook updating the code. Besides,
if the feature file naturally includes an example table like the one above, not reading directly
from the table values would necessitate creating a large amount of hard-code data in the BDD.
Another hiccup is that, while coding, the developer often thinks of numerous additional BDDs to
implement. But rather than just putting them into effect, they have to run through the entire
process of asking the business analysts to add them, then recreating the associated glue files,
and so on.
Finally, it can be challenging for the business analysts to consider all the minutiae of a BDD when
composing it. They frequently end up crafting incomplete BDDs, which then have to be remedied
by the developer. The devil truly is in the details, and it's considerably easier for the
developer, not business analysts, to uncover the necessary specifics when writing the code.
Here's an example:
Scenario: Buying stocks
Given user Alice has 10 shares of IBM.
When Alice buys 8 shares from Bill for $2 each.
Then Alice spends $16 and ends up with 18 shares.
copied to clipboard
The BDD above is incomplete. It should be written in the "given" section that Alice has sufficient
funds to buy the shares. If she doesn't, the purchase should be unsuccessful. Here's the corrected
BDD:
Scenario: Buying stocks
Given user Alice has 10 shares of IBM and $20 in balance. # here!
When Alice buys 8 shares from Bill for $2 each.
Then Alice ends up with 18 shares and $4.
copied to clipboard
However, the developer is in a better position to spot the mistake, as the code will fail as soon
as the developer runs it, when Alice lacks enough money. When the developer has the control,
the BDD assists in guiding its own creation.
This entire procedure can be quite laborious for both developers and business stakeholders, often
resulting in a lesser number of BDDs being composed. It's essential to keep the process as
streamlined as possible to maximize efficiency and ensure all potential scenarios are adequately
covered.
2. How this BDD Framework package is different #
The present package fundamentally rethinks the way you interact with BDDs. It's designed
with simplicity and efficiency in mind. Here's how it works:
Business stakeholders begin by jotting down BDD drafts. These don't have to be perfectly
structured feature files just yet.
Developers read these drafts and bring the BDDs to life directly through code, including the
wording of the BDDs.
Developers directly instantiate the BDD values as properly typed objects. No more fumbling
with strings!
If developers spot any errors or missing details in the feature files, they've got the green light
to fix them right on the spot, directly within the code.
When developers notice more BDDs are needed, they can instantly add them to the code. If some
BDDs are too difficult to code, they can be broken into smaller BDDs right away.
After coding, developers can run the BDD tests and effortlessly observe both the BDD text and the
results neatly well formatted in the console.
The cherry on top is that simply running the BDD tests also auto-generates the feature files,
and saves them into some suitable directory close to the code.
These feature files will get committed to the version control repository, when the code itself
gets committed. Here, the business stakeholders can go through them to ensure everything's on
track. This also provides a historic trail of feature file alterations in the repo, and even
allows for version comparisons.
Optionally, tools like Jira are smart enough to pull the feature files directly from your Git
repo, and present them in an easy-to-digest
format (see here).
This makes the business stakeholders' review process a breeze.
This new process greatly reduces the tedious back-and-forth between business stakeholders and
developers for fixing feature files. Say goodbye to glue code or the hassle of converting strings
into appropriately typed objects.
The BDD Framework also takes the pressure off business stakeholders, as they don't have to craft
perfect BDDs right from the get-go. They can rely on developers to enrich the details when they're
translating the BDDs into code – that's when the finer points truly come to light. All the while,
business stakeholders stay in the loop by reviewing the feature files stored in the repo.
From my experience, this revised process makes it writing BDDs as easy as writing unit tests!
3. BDD Recap #
Let's do a quick BDD recap, and then create some BDDs for a real app that's included in the
example directory of this package.
Behavior-Driven Development (BDD) fosters an environment where teams employ conversation and
specific examples to create a mutual understanding of the intended features of an application.
This approach aligns closely with the principles of Specification by Example and Acceptance
Tests, all of which can be implemented with this package.
BDD serves multiple purposes: It can be used as both acceptance tests and integration tests,
ensuring that the developed software meets the customer's requirements.
It provides explicit guidance for developers, detailing what needs to be done.
Similarly, it instructs testers on precisely what to test, and offers business stakeholders a
clear expectation of the end results.
A key aspect of BDD is writing test cases in a natural language that non-programmers and domain
experts can read.
The "Gherkin language" is not obligatory, but is frequently used for writing BDD test cases.
Gherkin is a domain-specific language that allows you to write your tests in a way that can be
easily read and understood.
A typical BDD written in Gherkin has the following structure:
Feature: A high-level business or product requirement
Scenario: A business rule that the system should fulfill
Given: The initial context at the beginning of the scenario
When: An event that occurs during the scenario
Then: The expected outcome
For instance:
Feature: User Authentication
Users should be able to log in and log out
Scenario: Successful Login
Given I am on the Login Screen.
When I enter a valid username and password.
Then I should be taken to the Home Screen.
Scenario: Failed Login
Given I am on the Login Screen.
When I enter an invalid username and password.
Then I should see an error message.
copied to clipboard
This format is highly readable, and business stakeholders will be able to understand
what's going on. This is a fundamental benefit of BDD, since it allows everyone involved to have a
clear understanding of the system's behavior and expectations.
It's very important the fact that BDDs are executable. In other words, they have some associated
Dart code that actually runs the test and marks them green
() or red ().
Because of that:
BDDs serve as a documentation for the system's behavior that's highly unlikely to become
outdated, unlike traditional documentation. This means the need for other documentation of your
software is greatly reduced.
BDDs prevent regression, making sure the program is doing what's necessary, and will keep like
that in the future.
BDDs allow for test-driven development (TDD), where you write the BDDs first, and then write
the code to make them pass (although it's not obligatory that you develop with TDD).
BDDs help you refactor the code, as you can be sure that the system is still working as expected
after you make changes.
Writing good BDDs involves clear and concise communication. Test scenarios should be understandable,
unambiguous, and provide a clear distinction between the Given, When, and Then steps.
In addition, good BDDs should be independent, meaning they should not rely on the state from other
BDDs or require them to run in a specific order. They should also be written at a level of detail
that provides sufficient information without becoming overly complex.
Remember, the main idea of BDD is to clarify what should be done before you start coding.
This way, you ensure that you're building the right thing, and you can use your BDD
scenarios as a guide and validation tool throughout the development process.
4. Practical advice #
The BDD tests should reflect what the system does, not how it's done. In special, you should
avoid specifying behavior through UI interactions. For example, the following is bad, as it talks about textfields and
buttons:
Given I am on the Login Screen.
When I click the first textfield and type a valid username and password,
And I click the blue login button.
Then The Home Screen swipes in from the right to the left of the screen.
copied to clipboard
UI changes all the time, and demands widget tests, which are harder to implement. Also, UI is
defined by the designer, not the business stakeholders or the developer. So, it's better to avoid UI
in BDDs.
The code itself should only use business classes, not widgets or other UI classes. This should be
easy to do as long as your code is properly structured, with clear separation between business and
UI code. If you're using a proper architectural pattern, you should be fine. If one day you are
implementing some BDD and you notice you need to touch the UI, that's a code smell, and means
you should refactor your code to separate code that shouldn't be there. Which means the BDDs will
also make sure your code is properly structured, one more advantage of using BDDs.
5. BDD Keywords #
BDDs are divided into "sections" by the keywords Given, When and Then. Each section has
its own purpose:
The Given section is for setting up the initial state of the system, and it's not
part of what's being tested. You can add expect calls in the Given section if you want,
but in this case they function like assertions, just to make sure the initial state is really
being respected. Surprisingly, the Given section is often the most complex part of the test,
as it's not always easy to put your system into a particular state. Pro tip: Creating helper classes
and standardized fixtures can help with that.
The When section is what you are actually testing. It describes a specific action or event
that causes a change in the system's state.
Lastly, the Then section outlines the expected result. It presents what you predict will
happen after the action specified in the When section has been performed. Here, you must include
one or more expect calls. These calls are there to verify that the action from the When
section has indeed caused the system's state to change as anticipated in the Then section.
These sections may optionally be broken into smaller phrases with the And keyword. For example:
Given user Alice has 10 shares of IBM and $20 in cash-balance.
copied to clipboard
May be written as:
Given user Alice has 10 shares of IBM,
And she has $20 in cash-balance.
copied to clipboard
You may also add tables to the sections, with any number of rows and columns, if you think it
improves readability. For example:
Feature: Buying and Selling Stocks
Scenario: Selling stocks.
Given The user has 120 dollars in cash-balance.
And The current stock prices are as such:
| Ticker | Price |
| AAPL | 50.25 |
| IBM | 30.0 |
| GOOG | 60.75 |
When ...
Then ...
copied to clipboard
Finally, you may want some of your BDDs to run more than once, each time with different data.
To that end you may add an Examples section, with any number of rows and columns. For each
row, the BDD should run again, with the values from the row being passed to the code.
For example, this BDD should be run twice:
Feature: Buying and Selling Stocks
Scenario Outline: Buying and Selling stocks changes the average price.
Given The user has <Quantity> shares of <Ticker> at <At> dollars each.
When The user <BuyOrSell> <How many> of these stock at <Price> for each share.
Then The number of shares becomes <Quantity> plus/minus <How many>.
And The average price for the stock becomes <Average Price>.
Examples:
| Ticker | Quantity | At | BuyOrSell | How many | Price | Average Price |
| IBM | 10 | 100.0 | buy | 2 | 50.0 | 91.67 |
| IBM | 8 | 200.0 | sell | 3 | 30.0 | 302.0 |
copied to clipboard
6. Example App #
The example directory of the BDD Framework repo in GitHub contains an
example Flutter mobile
app that I'm going to use to demonstrate how to write BDD tests. The app is a simple stock
portfolio app, where the user can buy and sell stocks:
As you can see in the screenshot above, the app has a list of available stocks, and a list of stocks
the user owns, called the "Portfolio". The user can buy and sell stocks, and the app will update the
Portfolio accordingly. The app also shows the user's Cash Balance, and the total value of the stocks
the user owns. You can transfer money in and out of the app, US$ 100 at a time, by pressing the
circle buttons at the top of the screen. When you have enough money to buy a particular stock, its
buy button will be enabled.
When you have enough stocks to sell, its sell button will be enabled.
The stock prices continuously change, and the app will update the portfolio value accordingly.
The Portfolio shows the average price of the stocks the user owns, and the total number of stocks
the user owns.
You may run the app in your own machine by cloning the repo and running the
main.dart file.
To make things easy for you to find, I've added all business classes into a single file called
business_classes.dart.
You shouldn't have all your business classes in a single file in your real app, obviously.
I created two BBD test files inside the test directory. One is called
bdd_buy_and_sell.dart and the other
bdd_average_price.dart.
In practice, you will have many separate BDD files, one per feature file.
These BDD test files have a total of 4 BDDs, that illustrate different aspects of the present BDD
Framework. Note one of them has an Examples section with 2 rows, so it will run twice, with
different data.
This is not nearly enough to make sure the whole app works as intended. After reading through the
example BDDs I've created, you may want to try it out yourself by creating more BDDs for the
example app.
7. Hello World #
Suppose you want to implement the following BDD:
Feature: Buying and Selling Stocks
Scenario: Buying stocks.
Given The user has 120 dollars in cash-balance.
And IBM price is 30 dollars.
And The user has no IBM stocks.
When The user buys 1 IBM.
Then The user now has 1 IBM.
And The cash-balance is now 90 dollars.
copied to clipboard
In the bdd_buy_and_sell.dart file you may translate that description into code, by writing the
following:
void main() {
var feature = BddFeature('Buying and Selling Stocks');
Bdd(feature)
.scenario('Buying stocks.')
.given('The user has 120 dollars in cash-balance.')
.and('IBM price is 30 dollars.')
.and('The user has no IBM stocks.')
.when('The user buys 1 IBM.')
.then('The user now has 1 IBM.')
.and('The cash-balance is now 90 dollars.')
.run((ctx) async {});
}
copied to clipboard
As you can see above, we create a BddFeature object, and then we create a Bdd object, passing
that feature to it. Then we chain a bunch of given, when, then and and calls, and finally
call run to run the BDD. The run method takes a callback that will be called when the BDD
starts running.
How to implement that callback is up to you, and depends completely on your business logic.
To actually test what's being described in the BDD, use expect calls.
You should strive to make the test code very clean and easy to read and understand. That way, it will serve as
documentation for your app. Why? Because it demonstrates how to execute the feature described in the BDD. If you have
complete BDDs that cover the whole app functionality, this means you also now have complete isolated, simplified and
runnable code that demonstrates how to do everything that the app does. It's the perfect documentation!
The following is my code that actually runs the BDD test above.
state = AppState.initialState();
// Given:
state.portfolio.cashBalance.set(120.00);
var ibm = state.availableStocks.findBySymbol('IBM');
ibm.setCurrentPrice(30.00);
state.portfolio.clearStock('IBM');
// When:
state.portfolio.buy(ibm);
// Then:
expect(state.portfolio.howManyStocks('IBM'), 1);
expect(state.portfolio.cashBalance, CashBalance(90.00));
copied to clipboard
As you can see above, I separated the code for the Given/When/Then sections with comments like
// Given: etc. That's optional, but recommended. The code is very readable and does exactly
what the BDD says it should do.
Note again that the test code callback itself has nothing to do with the BDD Framework.
It's just plain Dart code that depends on your business logic. The complete code is this:
void main() {
var feature = BddFeature('Buying and Selling Stocks');
Bdd(feature)
.scenario('Buying stocks.')
.given('The user has 120 dollars in cash-balance.')
.and('IBM price is 30 dollars.')
.and('The user has no IBM stocks.')
.when('The user buys 1 IBM.')
.then('The user now has 1 IBM.')
.and('The cash-balance is now 90 dollars.')
.run((ctx) async {
state = AppState.initialState();
// Given:
state.portfolio.cashBalance.set(120.00);
var ibm = state.availableStocks.findBySymbol('IBM');
ibm.setCurrentPrice(30.00);
state.portfolio.clearStock('IBM');
// When:
state.portfolio.buy(ibm);
// Then:
expect(state.portfolio.howManyStocks('IBM'), 1);
expect(state.portfolio.cashBalance, CashBalance(90.00));
});
}
copied to clipboard
When you run this BDD, you'll see the following output in the console:
TEST 1 ══════════════════════════════════════════════════
Feature: Buying and Selling Stocks
Scenario: Buying stocks.
Given The user has 120 dollars in cash-balance.
And IBM price is 30 dollars.
And The user has no IBM stocks.
When The user buys 1 IBM.
Then The user now has 1 IBM.
And The cash-balance is now 90 dollars.
✔ TEST 1 PASSED!
copied to clipboard
8. BDD Tables #
The Gherkin language allows for tables in the BDD description. Tables let you present
information in a tabular manner, and are sometimes easier to read than plain text.
Suppose we have the following:
Feature: Buying and Selling Stocks
Scenario: Selling stocks.
Given The user has 120 dollars in cash-balance.
And Apple (AAPL) costs $50.25, IBM costs $30.0, and Google (GOOG) costs $60.75.
And The user Portfolio contains 5 AAPL, 3 IBM and 12 GOOG.
When The user sells 1 IBM.
Then The user now has 2 IBM.
And AAPL is still 5, and GOOG is still 12.
And The cash-balance is now 150 dollars.
copied to clipboard
To make the above BDD description easier to understand, we could have created two tables:
Feature: Buying and Selling Stocks
Scenario: Selling stocks.
Given The user has 120 dollars in cash-balance.
And The current stock prices are as such:
| Ticker | Price |
| AAPL | 50.25 |
| IBM | 30.0 |
| GOOG | 60.75 |
And The user Portfolio contains:
| Ticker | Quantity |
| AAPL | 5 |
| IBM | 3 |
| GOOG | 12 |
When The user sells 1 IBM.
Then The user now has 2 IBM.
And AAPL is still 5, and GOOG is still 12.
And The cash-balance is now 150 dollars.
copied to clipboard
These tables may be implemented in code by adding row objects into a .table method call.
Values are then added as val objects into the rows:
Bdd(feature)
.scenario('Selling stocks.')
.given('The user has 120 dollars in cash-balance.')
.and('The current stock prices are as such:')
.table(
'Available Stocks',
row(val('Ticker', 'AAPL'), val('Price', 50.25)),
row(val('Ticker', 'IBM'), val('Price', 30.00)),
row(val('Ticker', 'GOOG'), val('Price', 60.75)),
)
.and('The user Portfolio contains:')
.table(
'Portfolio',
row(val('Ticker', 'AAPL'), val('Quantity', 5)),
row(val('Ticker', 'IBM'), val('Quantity', 3)),
row(val('Ticker', 'GOOG'), val('Quantity', 12)),
)
.when( ...
.then( ...
...
copied to clipboard
One way of implementing the above BDD is to hard-code the table values, as shown below:
state = AppState.initialState();
// Given:
state.portfolio.cashBalance.set(120.00);
var appl = state.availableStocks.findBySymbol('AAPL');
var ibm = state.availableStocks.findBySymbol('IBM');
var goog = state.availableStocks.findBySymbol('GOOG');
appl.setCurrentPrice(50.25);
ibm.setCurrentPrice(30.00);
goog.setCurrentPrice(60.75);
state.portfolio.set('AAPL', quantity: 5, averagePrice: 100);
state.portfolio.set('IBM', quantity: 3, averagePrice: 100);
state.portfolio.set('GOOG', quantity: 12, averagePrice: 100);
// When:
state.portfolio.sell(ibm);
// Then:
expect(state.portfolio.howManyStocks('IBM'), 2);
expect(state.portfolio.howManyStocks('AAPL'), 5);
expect(state.portfolio.howManyStocks('GOOG'), 12);
expect(state.portfolio.cashBalance, CashBalance(150.00));
copied to clipboard
Another alternative is to read the values directly from the table. Notice the run method
gets a context object of type BddContext, usually named ctx:
.run((ctx) async { ...
copied to clipboard
You can get a table by its name, and from there get the rows and the values:
var table = ctx.table('Available Stocks');
var rows = table.rows;
var value = rows[0].val('Ticker');
copied to clipboard
The above BDD without hard-code values could then be written as shown below:
state = AppState.initialState();
// Given:
state.portfolio.cashBalance.set(120.00);
// We read and create the info from the "Available Stocks" table:
var availableStocksTable = ctx.table('Available Stocks').rows;
for (var row in availableStocksTable) {
String ticker = row.val('Ticker');
double price = row.val('Price');
var stock = state.availableStocks.findBySymbol(ticker);
stock.setCurrentPrice(price);
}
// We read and create the info from the "Portfolio" table:
var portfolioTable = ctx.table('Portfolio').rows;
for (var row in portfolioTable) {
String ticker = row.val('Ticker');
int quantity = row.val('Quantity');
state.portfolio.set(ticker, quantity: quantity, averagePrice: 100);
}
// When:
var ibm = state.availableStocks.findBySymbol('IBM');
state.portfolio.sell(ibm);
// Then:
expect(state.portfolio.howManyStocks('IBM'), 2);
expect(state.portfolio.howManyStocks('AAPL'), 5);
expect(state.portfolio.howManyStocks('GOOG'), 12);
expect(state.portfolio.cashBalance, CashBalance(150.00));
copied to clipboard
This code is more complex. It's probably better to hard-code the values, unless in case of large
tables. Use good judgement.
9. BDD Examples #
Besides tables, the Gherkin language also allows for examples in the BDD description.
While tables are optionally used only to make to BDD description easier to understand, examples are
something else entirely. They are used to generate multiple BDDs from a single BDD description.
If you add more than one .example to a BDD description, the BDD will be run once for each one.
These runs are independent, which means some of them may pass and others may fail.
Consider the following BDD description:
Feature: Buying and Selling Stocks
Scenario Outline: Buying and Selling stocks changes the average price.
Given The user has <Quantity> shares of <Ticker> at <At> dollars each.
When The user <BuyOrSell> <How many> of these stock at <Price> for each share.
Then The number of shares is becomes <Quantity> plus/minus <How many>.
And The average price for the stock becomes <Average Price>.
Examples:
| Ticker | Quantity | At | BuyOrSell | How many | Price | Average Price |
| IBM | 10 | 100.0 | buy | 2 | 50.0 | 91.67 |
| IBM | 8 | 200.0 | sell | 3 | 30.0 | 302.0 |
copied to clipboard
The examples may be implemented in code by adding val objects into .example method calls:
Bdd(feature)
.scenario( ...
.given( ...
.when( ...
.then( ...
.and( ...
.example(
val('Ticker', 'IBM'),
val('Quantity', 10),
val('At', 100.00),
val('BuyOrSell', BuyOrSell.buy),
val('How many', 2),
val('Price', 50.00),
val('Average Price', 91.67),
)
.example(
val('Ticker', 'IBM'),
val('Quantity', 8),
val('At', 200.00),
val('BuyOrSell', BuyOrSell.sell),
val('How many', 3),
val('Price', 30.00),
val('Average Price', 302.00),
)
.run((ctx) async { ...
copied to clipboard
Note the example values are not limited to strings. They can be of any type, including enums, numbers,
custom classes, etc.
The code that implements the BDD test must read those typed values from the Examples section,
using the ctx.example object:
String ticker = ctx.example.val('Ticker');
int quantity = ctx.example.val('Quantity');
double at = ctx.example.val('At');
BuyOrSell buyOrSell = ctx.example.val('BuyOrSell');
int how = ctx.example.val('How many');
double price = ctx.example.val('Price');
double averagePrice = ctx.example.val('Average Price');
copied to clipboard
You can also write it like this, if you want:
var val = ctx.example.val;
String ticker = val('Ticker');
int quantity = val('Quantity');
double at = val('At');
BuyOrSell buyOrSell = val('BuyOrSell');
int how = val('How many');
double price = val('Price');
double averagePrice = val('Average Price');
copied to clipboard
As you can see, the values are read by name, and the type is inferred from the variable type.
Notice you don't need to refer to the number of the example you are reading, because the BDD Framework will
automatically run the BDD as many times as needed, one for each set of example values.
In this example it will be run twice.
Here is the complete code:
Bdd(feature)
.scenario('Buying and Selling stocks changes the average price.')
.given('The user has <Quantity> shares of <Ticker> at <At> dollars each.')
.when('The user <BuyOrSell> <How many> of these stock at <Price> for each share.')
.then('The number of shares is becomes <Quantity> plus/minus <How many>.')
.and('The average price for the stock becomes <Average Price>.')
.example(
val('Ticker', 'IBM'),
val('Quantity', 10),
val('At', 100.00),
val('BuyOrSell', BuyOrSell.buy),
val('How many', 2),
val('Price', 50.00),
val('Average Price', 91.67),
)
.example(
val('Ticker', 'IBM'),
val('Quantity', 8),
val('At', 200.00),
val('BuyOrSell', BuyOrSell.sell),
val('How many', 3),
val('Price', 30.00),
val('Average Price', 302.00),
)
.run((ctx) async {
String ticker = ctx.example.val('Ticker');
int quantity = ctx.example.val('Quantity');
double at = ctx.example.val('At');
BuyOrSell buyOrSell = ctx.example.val('BuyOrSell');
int how = ctx.example.val('How many');
double price = ctx.example.val('Price');
double averagePrice = ctx.example.val('Average Price');
// Sets up everything and just make sure we have money to buy whatever we need.
state = AppState.initialState();
state.portfolio.cashBalance.set(100000.00);
// Given:
var availableStock = state.availableStocks.findBySymbol(ticker);
availableStock.setCurrentPrice(at);
state.portfolio.set(ticker, quantity: quantity, averagePrice: at);
// When:
availableStock.setCurrentPrice(price);
state.portfolio.buyOrSell(availableStock, buyOrSell, howMany: how);
// Then:
expect(state.portfolio.howManyStocks(ticker), quantity + (buyOrSell.isBuy ? how : -how));
expect(state.portfolio.getStock(ticker)!.averagePrice, averagePrice);
});
copied to clipboard
10. Running all BDDs at once #
You can run all BDDs in a single Dart file by clicking the run button to the left of the main function in the
editor.
Or you can a single BDD by clicking the run button to the left of that particular BDD, in the editor.
However, you will usually create many separate Dart files containing many BDDs each, and you will want to
run all of them at once. To that end, simply create a file called run_all.dart in your test directory,
and list all your BDD files.
Here, for example, I am listing 2 BDD files: bdd_buy_and_sell.dart and bdd_average_price.dart:
import 'package:bdd_framework/bdd_framework.dart';
import 'package:flutter_test/flutter_test.dart';
import 'bdd_average_price.dart' as bdd_average_price;
import 'bdd_buy_and_sell.dart' as bdd_buy_and_sell;
void main() async {
// This will print the result to the console.
BddReporter.set(
ConsoleReporter(),
FeatureFileReporter(clearAllOutputBeforeRun: true),
);
group('bdd_buy_and_sell.dart', bdd_buy_and_sell.main);
group('bdd_average_price.dart', bdd_average_price.main);
await BddReporter.reportAll();
}
copied to clipboard
The BddReporter.set() method lets you add one or more "reporters" to decide where you want to see
the test results. You can create any reporters want by extending the BddReporter class. For
example, you could create an HtmlReporter() to output your tests as HTML and then add them to your
internal company's website.
The BDD Framework package comes out of the box with 2 reporters:
ConsoleReporter() prints the results to the console. Under the IntelliJ IDE for Windows
the output will have color. Note colors don't work under the IntelliJ for Mac
(issue).
FeatureFileReporter() writes the results into .feature files that contain your BDDs in
Gherkin language.
If the parameter clearAllOutputBeforeRun is true, all previous feature files will be
deleted and recreated each time you run the tests.
You can set the variable FeatureFileReporter.dir to choose the directory where your
feature files will be created. The default is to save them into ./gen_features/. By saving
them into your Flutter project, you can then simply commit your code to add those files to
your version control repo. This will serve as documentation for your current project version.
If you use Jira, you can
install
Cucumber for Jira to show your feature files inside of Jira. Each time you run the tests
and create the feature files, just commit them to your repo. Cucumber for Jira will read
those files and present them well formatted for you.
11. Console output #
When you run your BDDs in the IntelliJ IDE, your tests will be listed in the left panel. The tests
will be numbered as 1, 2 etc.
Since BDDs with an Examples section run more than once, each run is going to
be numbered with an index. For example, if the fourth BDD has two examples, we'll get 4₁ and 4₂
(see in the image below).
The output of each BDD will be printed to the console.
In Windows, the output will be colored. That doesn't work in the Mac, as Dart's console for
the Mac still doesn't support colors (see
issue).
For tests that pass you'll get a ✔ TEST 1 PASSED message.
For tests that fail you'll get the error stacktrace, a ⚠ TEST 1 FAILED message, and
the failing description.
At the end, you'll see the test summary, under the title RESULTS:
12. Plugin support #
If you use the IntelliJ IDE, you may install the
Marcelo's
Flutter Dart Essentials plugin to support the BDD Framework package.
It enhances code readability, making it easier to distinguish the many BDD parts.
Once installed, the plugin draws horizontal line separators above Bdd() calls, and also between
the BDD keywords: .given(...), .when(...), .then(...) and .example(...).
Also, in the BDD body you may want to separate the implementation of the given/then/when parts.
To that end, it will also add separators above any comments that start with // Given:,
// When:, // Then:, // Given/When: or //When/Then:.
All this only works for files with names that start with bdd_ or end with _test.dart.
Note: The plugin has other features which are not related to the BDD Framework, such as drawing horizontal line
separators above class, enum and extension definitions, as well as above test() and group()
method calls:
Copyright #
This package is protected under copyright and kindly provided by
Parkside Technologies.
As a company dedicated to simplifying global access to U.S. stocks, Parkside was
acquired by Nubank in 2022. The publication of this package was authorized by Parkside.
For more information, please refer to the license page.
The Flutter packages I've authored:
async_redux
provider_for_redux
i18n_extension
align_positioned
network_to_file_image
image_pixels
matrix4_transform
back_button_interceptor
indexed_list_view
animated_size_and_fade
assorted_layout_widgets
weak_map
themed
bdd_framework
My Medium Articles:
Async Redux: Flutter’s non-boilerplate version of Redux
(versions:
Português)
i18n_extension
(versions:
Português)
Flutter: The Advanced Layout Rule Even Beginners Must Know
(versions: русский)
The New Way to create Themes in your Flutter App
My article in the official Flutter documentation:
Understanding constraints
Marcelo Glasberg:
glasberg.dev
github.com/marcglasberg
linkedin.com/in/marcglasberg/
twitter.com/glasbergmarcelo
stackoverflow.com/users/3411681/marcg
medium.com/@marcglasberg
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.