Max Hamulyák
Developer since 2016 (focus on .NET)
Blog at https://kaylumah.nl/blog
Max Hamulyák max@kaylumah.nl
Max Hamulyák
Developer since 2016 (focus on .NET)
Blog at https://kaylumah.nl/blog
Behavior Driven Development (BBD)
Domain Specific Language (DSL)
Unambiguous executable specification
Automated testing using Cucumber
Document how the system actually behaves
Feature: Guess the word
# The first example has two steps
Scenario: Maker starts a game
When the Maker starts a game
Then the Maker waits for a Breaker to join
# The second example has three steps
Scenario: Breaker joins a game
Given the Maker has started a game with the word "silky"
When the Breaker joins the Maker's game
Then the Breaker must guess a word with 5 characters
Feature
Rule (as of Gherkin 6)
Example (or Scenario)
Given, When, Then, And, But for steps (or *)
Background
Scenario Outline (or Scenario Template)
Examples (or Scenarios)
# language: no
Funksjonalitet: Gjett et ord
Eksempel: Ordmaker starter et spill
Når Ordmaker starter et spill
Så må Ordmaker vente på at Gjetter blir med
Eksempel: Gjetter blir med
Gitt at Ordmaker har startet et spill med ordet "bløtt"
Når Gjetter blir med på Ordmakers spill
Så må Gjetter gjette et ord på 5 bokstaver
Feature: Calculator
Calculator for adding two numbers
Scenario: Add two numbers
Add two numbers with the calculator
Given I have entered 50 into the calculator
And I have entered 70 into the calculator
When I press add
Then the result should be 120 on the screen
Support for multi-line text |
Given a blog post named "Random" with Markdown body
"""
Some Title, Eh?
===============
Here is the first paragraph of my blog post. Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
"""
Support for complex structures |
Given the following users exist:
| name | email | twitter |
| Aslak | aslak@cucumber.io | @aslak_hellesoy |
| Julien | julien@cucumber.io | @jbpros |
| Matt | matt@cucumber.io | @mattwynne |
Do not do this |
Scenario: eat 5 out of 12
Given there are 12 cucumbers
When I eat 5 cucumbers
Then I should have 7 cucumbers
Scenario: eat 5 out of 20
Given there are 20 cucumbers
When I eat 5 cucumbers
Then I should have 15 cucumbers
Prefer this instead |
Scenario Outline: eating
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
Examples:
| start | eat | left |
| 12 | 5 | 7 |
| 20 | 5 | 15 |
Keywords are not used for uniqueness |
Given there is money in my account
Then there is money in my account
Given my account has a balance of £430
Then my account should have a balance of £430
BDD for .NET
Official version of Cucumber for .NET
Open source since 2009 (Gáspár Nagy, TechTalk)
Execute scenarios using automation code
The glue between Gherkin feature and test code is done via [Binding]
[Binding]
public class StepDefinitions
{
...
}
[When(@"^I perform a simple search on '(.*)'$")]
public void WhenIPerformASimpleSearchOn(string searchTerm)
{
var controller = new CatalogController();
actionResult = controller.Search(searchTerm);
}
[When("I perform a simple search on {string}")]
[When("I search for {string}")]
public void WhenIPerformASimpleSearchOn(string searchTerm)
{
...
}
Use the [Scope] attribute to define the scope:
[Scope(
Tag = "mytag",
Feature = "feature title",
Scenario = "scenario title"
)]
[Binding]
public class StepDefinitions
{
...
}
[When(@"I perform a simple search on '(.*)'",
Scope(Tag = "controller"))]
public void WhenIPerformASimpleSearchOn(string searchTerm)
{
var controller = new CatalogController();
actionResult = controller.Search(searchTerm);
}
[When(@"I perform a simple search on '(.*)'"),
Scope(Tag = "web")]
public void PerformSimpleSearch(string title)
{
selenium.GoToThePage("Home");
selenium.Type("searchTerm", title);
selenium.Click("searchButton");
}
[BeforeFeature]/[AfterFeature]
[BeforeScenario]/[AfterScenario]
[BeforeScenarioBlock]/[AfterScenarioBlock]
[BeforeStep]/[AfterStep]
Different [Binding] need to share data
Feature: ContextSharing
Simple calculator for adding two numbers
Scenario: Add two numbers
Given the first number is 50
And the second number is 70
When the two numbers are added
Then the result should be 120
public class CalculatorContext
{
public int? FirstNumber { get; set; }
public int? SecondNumber { get; set; }
public int? Result { get; set; }
}
[Binding]
public sealed class ContextSharing
{
private readonly CalculatorContext _calculatorContext;
public ContextSharing(CalculatorContext calculatorContext)
{
_calculatorContext = calculatorContext;
}
[Given("the first number is (.*)")]
public void GivenTheFirstNumberIs(int number)
{
_calculatorContext.FirstNumber = number;
}
}
[Binding]
public class WebDriverSupport
{
private readonly IObjectContainer objectContainer;
public WebDriverSupport(IObjectContainer objectContainer)
{
this.objectContainer = objectContainer;
}
[BeforeScenario]
public void InitializeWebDriver()
{
var webDriver = new FirefoxDriver();
objectContainer.RegisterInstanceAs<IWebDriver>(webDriver);
}
}
[Binding]
public class Binding
{
ScenarioContext _scenarioContext;
public Binding(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[BeforeScenario("web")]
public static void BeforeWebScenario()
{
if(_scenarioContext.ScenarioInfo.Tags.Contains("automated"))
StartSelenium();
}
}
Some [Binding] need complex types
More natural language |
Given we have '0' items in stock:
Given we have 'no' items in stock:
public class Conference
{
public string? Name { get; set; }
public string? Location { get; set; }
public DateTimeOffset? From { get; set; }
public DateTimeOffset? To { get; set; }
}
Automation
[Given]
public void GivenTheFollowingConferences1(Table table)
{
}
Horizontal Tables
Given the following conference1
| Name | Location | From | To |
| DevDays | Arnhem | 2023-05-12 | 2023-05-13 |
Vertical Tables
Given the following conference1
| Key | Value |
| Name | DevDays |
| Location | Arnhem |
[Given]
public void GivenTheFollowingConferences1(Table table)
{
foreach (var tableRow in table.Rows)
{
string name = tableRow["Name"];
string location = tableRow["Location"];
string from = tableRow["From"];
string to = tableRow["To"];
// ...
}
}
[Given]
public void GivenTheFollowingConferences2(Table table)
{
foreach (var tableRow in table.Rows)
{
string name = tableRow.GetString("Name");
string location = tableRow.GetString("Location");
DateTimeOffset from = tableRow.GetDateTime("From");
DateTimeOffset to = tableRow.GetDateTime("To");
// ...
}
}
[Given]
public void GivenTheFollowingConferences3(Table table)
{
var conferences = table.CreateSet<Conference>();
}
[Given]
public void GivenTheFollowingConference1(Table table)
{
var conference = table.CreateInstance<Conference>();
}
[Given]
public void GivenTheFollowingConferences(List<Conference> conferences)
What will be the value of location? |
Given the following conference1
| Name | Location | From | To |
| DevDays | | 2023-05-12 | 2023-05-13 |
Given the following conference1
| Name | From | To |
| DevDays | 2023-05-12 | 2023-05-13 |
Given the following conference1
| Name | Location | From | To |
| DevDays | <null> | 2023-05-12 | 2023-05-13 |
[Binding]
public class GlobalHooks
{
[BeforeTestRun]
public static void BeforeTestRun()
{
Service
.Instance
.ValueRetrievers
.Register(new NullValueRetriever(Constants.NullIndicator));
}
}
Scenario Outline: Technical Id
Given we have the following technical id '<Id>':
Scenarios:
| Id |
| 7e53b915-a454-4337-afae-0cf0e69814a2 |
[Given(@"we have the following technical id '(.*)':")]
public void GivenTheFollowingTechnicalId(Guid id)
_specFlowOutputHelper.WriteLine($"id: '{id}'");
Scenario Outline: Technical Id
Given we have the following technical id '<Id>':
Scenarios:
| Id |
| 7e53b915-a454-4337-afae-0cf0e69814a2 |
| 7e53b915-a454-4337-afae |
| 7e53b915-a454-4337 |
| 7e53b915-a454 |
| 7e53b915 |
| 7 |
Business Entities
Tags
Business language
Real data
Intention revealing
Essential
Focused
Imagine it’s 1922
Use * or and
Given an open account with 100 USD balance
When the account receives a 20 USD payment
Then the account balance will be 120 USD
Given a <status> account with <old balance> balance
When the account receives an <amount> payment
Then the account balance will be <new balance>
Examples:
| status | old balance | amount | new balance |
| open | 100 USD | 20 USD | 120 USD |
Given an "open" account with "100 USD" balance
When the account receives a payment of "20 USD"
Then the account balance will be "120 USD"
Consider target audience