Posted on

BDD in Golang

Behaviour-Driven Development (BDD) is, in my opinion, one of the best development practices to tackle projects with complex business logic. BDD is meant to help communication between technical and non-technical members of the team, by creating a common (natural) language for specifications and development. It is especially useful when trying to implement a Domain-Driven methodology, as it will help the developers share a common understanding of the business logic with the clients.

As a Go developer, I was happy to find that BDD testing was very easy to implement thanks to the godog package, and I will try to show you how it can be integrated into your tests.

A bank application

We will test a very simple bank application that allows the user to deposit and withdraw money. The business logic is already written in this simple struct:

// account.go
package bank

type account struct {
  balance int
}

func (a *account) withdraw(amount int) {
  a.balance = a.balance - amount
}

func (a *account) deposit(amount int) {
  a.balance = a.balance + amount
}

Writing the specification

This file will match a specification file stored in the features folder. The specifications are written in Gherkin. Gherkin is a specifications language based on natural languages. It uses keywords that developers will be able to match against their code such as: Given, When, Then…

Note: Gherkin is available in many natural languages, make sure to always use one all the members of your team speaks fluently

In this file we will describe two scenarios, one for deposits and one for withdrawals:

#file: features/account.feature
Feature: bank account
  A user's bank account must be able to withdraw and deposit cash

  Scenario: Deposit
    Given I have a bank account with 10$
    When I deposit 10$
    Then it should have a balance of 20$

  Scenario: Withdrawal
    Given I have a bank account with 20$
    When I withdraw 10$
    Then it should have a balance of 10$

Writing the test

godog logo
The godog library allows us to run BDD tests

The sentences in the account.feature file will then need to be linked to runnable test code. Running the godog command can automatically suggest the structure for your test file. This command can be installed with go get github.com/DATA-DOG/godog/cmd/godog.

This test file will contain one function for each of the steps defined in the scenario, as well as a FeatureContext command that will link the Go functions to natural languages sentences using regex, and define the setup/cleanup operations:

package bank

import (
  "fmt"
  "github.com/DATA-DOG/godog"
)

var testAccount *account

func iHaveABankAccountWith(balance int) error {
  testAccount = &account{balance:balance}
  return nil
}

func iDeposit(amount int) error {
  testAccount.deposit(amount)
  return nil
}

func iWithdraw(amount int) error {
  testAccount.withdraw(amount)
  return nil
}

func itShouldHaveABalanceOf(balance int) error {
  if testAccount.balance == balance {
    return nil
  }
  return fmt.Errorf("Incorrect account balance")
}

func FeatureContext(s *godog.Suite) {
  s.Step(`^I have a bank account with (\d+)\$$`, iHaveABankAccountWith)
  s.Step(`^I deposit (\d+)\$$`, iDeposit)
  s.Step(`^I withdraw (\d+)\$$`, iWithdraw)
  s.Step(`^it should have a balance of (\d+)\$$`, itShouldHaveABalanceOf)

  s.BeforeScenario(func(interface{}) {
    testAccount = nil
  })
}

Launching the godog command will result in the test scenarios being run (and normally, everything should be green 😉). You can also launch all the tests using go test by modifying your TestMain.

Using scenario outlines

Just like table driven tests is a common way to write tests in Go, scenario outlines will allow you to run the same steps on a larger dataset. This will require transforming each scenario in our feature file to be transformed into a feature file and providing test data in Examples sections:

Feature: bank account
  A user's bank account must be able to withdraw and deposit cash

  Scenario Outline: Deposit
    Given I have a bank account with <start>$
    When I deposit <deposit>$
    Then it should have a balance of <end>$
    
    Examples:
      | start | deposit | end |
      | 10    | 0       | 10  |
      | 10    | 10      | 20  |
      | 100   | 50      | 150 |

  Scenario Outline: Withdrawal
    Given I have a bank account with <start>$
    When I withdraw <withdrawal>$
    Then it should have a balance of <end>$

    Examples:
      | start | withdrawal | end |
      | 10    | 0          | 10  |
      | 20    | 10         | 10  |
      | 100   | 50         | 50  |

This time running godog will execute 6 scenarios and 18 steps.

godog result

Conclusion

Obviously, BDD won’t be useful for every kind of application. But I know it can help many teams easily solve some complex business problem. As usual, mastering the tools like Gherkin is not enough to take all the benefits from BDD, and to do that you should also learn about practices such as Test-Driven Development (its precursor) and Domain-Driven Design.

You can find the full code for this article on GitHub gists.