Behavior-Driven Development (BDD)
In BDD, the programmer, the tester and the business expert build a few real examples together before writing code. These examples are written as Given, When, Then and become automated tests. The result is documentation that always stays in sync with the code.
Author: bezzad
The problem: right code, but the wrong thing
The sales expert says: “Orders above 500 thousand toman get free shipping.”
The programmer writes the code and also writes tests. All tests are green. But after the release, the sales expert is not happy:
- What did “above 500 thousand” mean? The programmer thought 500 thousand itself is not free. The expert thought it is.
- The amount before discount or after it? The programmer used the amount before discount. The expert wanted the amount after discount.
- Why did the tests not catch the error? Because the tests checked the programmer’s understanding, not what the business wanted.
The problem was not in the code. The problem was in shared understanding.
The BDD idea: a conversation with real examples
Behavior-Driven Development starts from this exact problem. Before writing code, three roles have a short meeting together: someone who knows the business, someone who writes the code and someone who tests. This meeting is sometimes called the “Three Amigos”.
In this meeting, instead of a general rule, we build exact examples:
- A cart of 499 thousand toman, after discount: shipping cost is 50 thousand toman.
- A cart of 500 thousand toman, after discount: free shipping.
- A cart of 600 thousand toman with a 20 percent discount, that is 480 thousand: shipping cost is 50 thousand toman.
These three examples find both misunderstandings above, before even one line of code is written.
The Given, When, Then language
We write the examples in a fixed shape. We call this shape Gherkin:
- Given. The first state of the system. For example, the cart total.
- When. The action the user takes. For example, checking out the cart.
- Then. The result we expect. For example, the shipping cost.
These are the same three parts of a unit test: arrange, act and assert. The difference is that it is written in business language, and the sales expert can read it too.
Feature: Free shipping
Orders of 500000 toman or more ship for free.
The rule uses the total after discount.
Scenario: Order just below the limit pays shipping
Given the cart total is 499000 toman
When the customer checks out
Then the shipping cost is 50000 toman
Scenario Outline: Shipping cost by cart total
Given the cart total is <total> toman
When the customer checks out
Then the shipping cost is <shipping> toman
Examples:
| total | shipping |
| 200000 | 50000 |
| 500000 | 0 |
| 480000 | 50000 |
With a Scenario Outline we run one scenario with several rows of data. Each row of the table is one example.
Connecting the scenario to code
A feature file alone is only text. A tool must connect each line of it to a C# method. In .NET, the common tool today is Reqnroll. The older tool, SpecFlow, was retired at the end of 2024. Reqnroll is built from the code of that same project, and moving to it is simple.
[Binding]
public sealed class ShippingSteps
{
private decimal _cartTotal;
private decimal _shipping;
[Given("the cart total is {int} toman")]
public void GivenCartTotal(int total) => _cartTotal = total;
[When("the customer checks out")]
public void WhenCustomerChecksOut() =>
_shipping = ShippingCost.For(_cartTotal, isVip: false);
[Then("the shipping cost is {int} toman")]
public void ThenShippingCostIs(int expected) => Assert.Equal(expected, _shipping);
}
The word int inside braces finds the number in the sentence and gives it to the method parameter. So one method is enough for all scenarios that have the same sentence with a different number.
A good scenario, a bad scenario
A scenario should describe behavior, not the clicking steps.
BadStep-by-step and tied to the page
Given I open "/cart"
And I click "#add-item-7"
And I type "2" into "#qty"
When I click "#checkout"
Then I see "50,000" in ".shipping"If the page design changes, the scenario breaks. The sales expert also understands nothing from it.
GoodDescriptive and in business language
Given the cart total is 499000 toman
When the customer checks out
Then the shipping cost is 50000 tomanIt only states the rule. How it runs is the job of the test code methods.
Comparing BDD and TDD
| TDD | BDD | |
|---|---|---|
| Main question | Does the code work correctly? | Are we building the right thing? |
| Who writes it? | The programmer | Three roles together |
| Test language | C# code | Given, When, Then sentences |
| Size | Usually one class or one small rule | Usually one full feature |
These two are not rivals. You can write a BDD scenario for a feature and build each class inside it with TDD.
Important rules
- Conversation first, file second. If the programmer writes the feature file alone, they have only built a test that looks different.
- One behavior per scenario. Split a long scenario with ten When steps into several scenarios.
- Business language, not technical language. A scenario should have no table names, page addresses or HTML code.
- Not all scenarios should run through the page. Test code methods can call the domain code or the API directly. This is much faster and more stable.
- Keep the sentences the same. If we write one sentence in ten different ways, we need ten duplicate methods.
Common mistakes
| Mistake | Result | The right way |
|---|---|---|
| Writing features without the business in the room | Misunderstandings are still not found. | A short meeting with examples before the code. |
| Click-based scenarios tied to the page | Tests break with every change in the look. | Descriptive scenarios, run through the API or the domain. |
| Using Reqnroll for all tests | An extra layer with no benefit. Nobody except programmers reads the features. | Only for rules the business must see. |
| General sentences like “the system works correctly” | The scenario checks nothing exactly. | An exact number and result in each example. |
When to use BDD?
Good fit
- Complex business rules where a misunderstanding is expensive.
- A team that has a business expert available, who really reads the scenarios.
- Areas like insurance, tax, pricing and payment.
Bad fit
- Technical code with no business rules, like a cache library.
- A team that only installs the tool, but has no conversation.
- Simple rules where one good unit test is enough.
Summary in six lines
- Many bugs come from misunderstanding, not from wrong code.
- In BDD, three roles build exact examples together before the code.
- Examples are written with Given, When, Then so everyone can read them.
- In .NET, the Reqnroll tool connects each sentence to a C# method.
- A good scenario describes behavior, not clicks.
- The main part of BDD is the conversation. Without conversation, the tool is only extra cost.