Levelwise
English
Testing

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.

Not reviewedWritten with AI helpReading time: 11 minOnline shop free shipping exampleC# and Reqnroll 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:

  1. What did “above 500 thousand” mean? The programmer thought 500 thousand itself is not free. The expert thought it is.
  2. The amount before discount or after it? The programmer used the amount before discount. The expert wanted the amount after discount.
  3. 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”.

Sales expertProgrammerTesterOne short meetingReal examples499,000: paid shipping500,000: free.featureGiven When ThenIn business languageAutomated testReqnroll + xUnitOn the real codeThe most important part is the talk, not the tool
The examples from the meeting become the automated tests.

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:

  1. Given. The first state of the system. For example, the cart total.
  2. When. The action the user takes. For example, checking out the cart.
  3. 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.

Scenario in business languageTest code methodsGiven the cart total is 499000 tomanWhen the customer checks outThen the shipping cost is 50000 tomanGivenCartTotal(int total)WhenCustomerChecksOut()ThenShippingCostIs(int expected)Arrange, act, assert: the same three parts of a unit test
[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.

The tool is not the heart of BDD. You can do BDD even without Reqnroll. The main part is the conversation and the shared examples. You can write the same examples in an xUnit test with clear names. The tool is worth it only when people who are not programmers really read the feature files.

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 toman

It 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

  1. Conversation first, file second. If the programmer writes the feature file alone, they have only built a test that looks different.
  2. One behavior per scenario. Split a long scenario with ten When steps into several scenarios.
  3. Business language, not technical language. A scenario should have no table names, page addresses or HTML code.
  4. 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.
  5. 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

  1. Many bugs come from misunderstanding, not from wrong code.
  2. In BDD, three roles build exact examples together before the code.
  3. Examples are written with Given, When, Then so everyone can read them.
  4. In .NET, the Reqnroll tool connects each sentence to a C# method.
  5. A good scenario describes behavior, not clicks.
  6. The main part of BDD is the conversation. Without conversation, the tool is only extra cost.