Unit Testing for BDD

Run a Test

Chris Wall

04/10/2018

There are a lot of "whatever" Driven Development methodologies out there to help give us a framework by which to craft better software. Most all of these stem from Test Driven Development and narrow TDD's focus to help solve more specific problems or make it easier to implement TDD in your own projects. One of the many methodologies to evolve from this is Behavior Driven Development or BDD.

Behavior Driven Development expands on the concepts of Test Driven Development to create a vocabulary for mapping your team's unit tests and user stories back to actual Business Functions and application behavior. This creates an ecosystem where all the involved parties, regardless of where they skew on the Technical/Functional/Business spectrum, can communicate more efficiently regarding the project at hand. There is also something wonderfully organic about envisioning your application primarily in terms of the functionality it provides to the end user.

So to that end, how does this affect the way we do TDD? What does BDD do to evolve the way we think of our unit tests?

Being an evolution of TDD, the usual concepts still apply - write your tests first then write code to make those tests pass. The only difference here is that within the scope of BDD, we want our tests to model Business Functions and not strictly technical functions. A test that says "someObject.removeChild() is working as expected", has less intrinsic value than a test that says "clicking the X button on an item of the children list of Some Obj removes that child".

Enter Given-When-Then! 

The Given-When-Then paradigm is the preferred way to structure your tests for a BDD implementation. To that end, let's talk about what Given-When-Then is and how to implement it in your own testing. It's actually pretty simple to write Given-When-Then tests. This paradigm is just saying that there should be 3 phases to a running test. Let's look at each one in the context of our previous example of "clicking the X button on an item of the children list of Some Obj removes that child". Each piece represents a segment of the workflow of setting expectations and verifying the results. 

For example: Given there are leaves in my yard When my wife asks me to rake the leaves Then the yard is clear of leaves! :)

AS-Test Framework

At Afterman Software, we created a simple Open Source framework that helps us to keep these sorts of tests uniform and gives us some base classes to remove the boilerplate that can start to sprawl any time you have a specific "convention" to your testing. All of the examples here will be written against this framework. In the AS-Test framework, there are 2 ways to configure a test: by inheriting from TestBase<T> or by using the SimpleTest static factory. When deriving from the base class, each single test is contained in a class derived from TestBase<T>. TestBase<T> manages the workflow of Given-When-Then and wires up the test through the Custom Attributes expected by the Visual Studio Test Runner. The SimpleTest configuration exposes the workflow through interfaces that can be handled in the context of a single test method. These are separated mostly to allow for personal preferences in file structure since most people are used to putting many tests inside a single class as test methods.

To get started, we need to create a new test first. We will use SimpleTest for these examples, but the same workflow could be implemented either way.


This will create the traditional Test Class and Test Method for the VS Test Runner to use. This is a very contrived example, but will allow us to go into more detail on the specific pieces of the test. Note that the argument provided to Test()'s generic type parameter should be the type you wish to test. In our case, we only care about what the Children property looks like and that is an array of strings.

Given

The Given is the phase when you are setting up your presuppositions before the test executes. For our specific test case we need to suppose that we have an instance of SomeObj and that it has specific children. This can be as simple as instantiating a new object or setting up a complex object tree.

When

Once we have our initial state expectation defined, we move on to When which is the action phase. When represents the functionality we with to actually test. Your When function should be relatively simple and should perform an action on the Given state and return a representation of the resulting state that you will later validate. If you find yourself putting a lot of code into the When function, you are probably either:

  1. Leaking Expectations You are setting up expectations in the When that should be provided for by Given. Anything that creates state assumptions belong in Given not When.
  2. Testing Too Low  You are testing code that is lower level than the actual root of the functionality. Call from the top and let your code flow the test to the lower portions organically.

Then

Finally we come to Then - the validation of the result of all our efforts. Depending on the focus of your test, the function we craft for validation can vary drastically in complexity. A large part of the AS-Test framework is devoted to exposing a builder pattern implementation for building validation objects to use for your Then validations.

Wrapping It Up

There are numerous benefits to a good Test Driven Development implementation in your project and Behavior Driven Development is a great addition to this testing regimen. Utilizing the Given-When-Then testing vocabulary not only helps to keep your tests universal and structured but also gives your whole team (functional, technical, and business) a more result-oriented way to talk about the project you're building together.

Download on Github