BDD
BDD is an extension of TDD that aims to enable deeper collaboration between engineers, domain experts, and quality assurance engineers (if your company employs them). A diagram of how this works with TDD is shown here.

Figure 8.6 – BDD as an extension of TDD
The goal of BDD is to provide a higher level of abstraction from code through a domain-specific language (often referred to as a DSL) that can become executable tests. Two popular frameworks for writing BDD tests is the use of Gherkin (https://cucumber.io/docs/gherkin/reference/) and Cucumber (https://cucumber.io)
Gherkin defines a set of keywords and a language specification. Cucumber reads this text and validates that the software works as expected. For example, the following is a valid Cucumber test:
Feature: checkout Integration Scenario: Successfully Capture a payment Given I am a customer When I purchase a cookie for 50 cents. Then my card should be charged 50 cents and an e-mail receipt is sent.
Some teams work with their domain experts to ensure their acceptance criteria in their ticketing system are in this format. If it is, this criterion can simply become the test. This aligns nicely with DDD.
Now that we have a high-level understanding of BDD, let’s take a look at implementing a test in Go. We are going to use the go-bdd
framework, which you can find at https://github.com/go-bdd/gobdd.
Firstly, let’s install go-bdd in our project:
go get github.com/go-bdd/gobdd
Now, create a features
folder:

Figure 8.7 – Our features folder after creation
Inside the features
folder, let’s add a file called add.feature
with this inside it:
Feature: Adding numbers Scenario: add two numbers together When I add 3 and 6 Then the result should equal 9
Next, let’s add an add_test.go
file and the following:
package chapter8 import ( "testing" "github.com/go-bdd/gobdd" ) func add(t gobdd.StepTest, ctx gobdd.Context, first, second int) { res := first + second ctx.Set("result", res) } func check(t gobdd.StepTest, ctx gobdd.Context, sum int) { received, err := ctx.GetInt("result") if err != nil { t.Fatal(err) return } if sum != received { t.Fatalf("expected %d but got %d", sum, received) } } func TestScenarios(t *testing.T) { suite := gobdd.NewSuite(t) suite.AddStep(`I add (\d+) and (\d+)`, add) suite.AddStep(`the result should equal (\d+)`, check) suite.Run() }
In the preceding code, we add a bdd
step
function called add. This function name is important; the framework knows that when I add 3 and 6
gets mapped to this function. If you change the name of this function to “sum”, you’d need to update the feature file to say, when I sum 3 and 6 together
. We then perform our logic and store it in the context so that we can recall it later.
We then define a check function that is our actual test; it validates our assertions. Finally, we set up a test suite to run our code.
If you run the preceding test, it should pass.
This might be your first time seeing a BDD-style test, but I bet it’s not your first time seeing a unit test. Why is that?
As you can see, although BDD tests are closer to natural language, it pushes a lot of the complexity down into the tests. The preceding example we used is trivial, but if you want to express complex scenarios (such as the cookie example we used previously) there is a lot of scaffolding the developer needs to implement to make the tests work correctly. This can be worthwhile if you have lots of access to your domain experts and you are truly going to work side by side. However, if they are absent or not invested in the process, unit tests are much faster and more engaging for engineering teams to work with. Much like DDD, BDD is a multidisciplinary team investment, and it is worth ensuring you have buy-in from all stakeholders before investing too much time in it.