TDD vs BDD
The main focus of this post is to describe the unit test class structures I use, which are a mixture between TDD (Test Driven Development), and BDD (Behaviour Driven Development) patterns. The difference between TDD and BDD is quite subtle, but once realised it is quite profound. Matt Wynne tackles the subject in a TDD vs BDD blog post describing TDD as very similar to BDD, except that BDD had a much more business, or customer centric focus, while TDD was more system oriented. In Matt's book - The Cucumber Book, he describes unit tests as focusing on "building the system right", whilst acceptance tests focus on "building the right system", and we can draw a similar comparison between TDD and BDD. Since we can mock our to dependencies to test complex business logic, we can write our BDD style acceptance tests as unit test classes.
Both TDD and BDD are about -
- defining your specification first, i.e. the test
- writing enough code to allow the test to fail meaningfully
- writing the functional code to deliver the working solution for the test
- refactoring the solution to improve the code, i.e. DRY it up (DRY - Don't Repeat Yourself)
My main focus on TDD and BDD is related to the patterns of the test code.
Arrange Act Assert
The pattern typically used in TDD unit tests is called the AAA pattern or "Arrange Act Assert", where basically you -
- Arrange the state of the test item
- Perform an action on the test item
- Assert the result of the action
I use the AAA pattern for performing simple unit tests on self contained classes, which I can easily supply inputs, execute a test, and then verify the results.
The structure of these tests is very simple, typically a test method for each test, and perform the Arrange, Act, Assert functionality within the single test method. See the example below related to the "happy hour" example from the first post I published on testing
The test example below is simply checking that the LookupPrice method returns the expected result for the "Little Creatures Pale Ale" beer.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestClass] | |
public class PriceListTest | |
{ | |
[TestMethod] | |
public TestLookupPrice() | |
{ | |
// Arrange | |
var priceList = new PriceList(); | |
// Act | |
var result = priceList.LookupPrice("Little Creatures Pale Ale"); | |
// Assert | |
Assert.AreEqual(7, result); | |
} | |
} |
Gherkin
Gherkin is a language for writing acceptance tests such that a non technical user can understand the specification, and also allows a developer to interpret what system tests will verify that the logic meets the test expectations.
Gherkin is structured as -
- Given some pre condition
- When a particular action is performed
- Then an expected result should be obtained
As you can see, the pattern of the test is very similar to AAA, but subtly, the test is written in relation to the expected behaviour of the system from a business logic perspective, rather than the technical execution of a single class which is being unit tested.
From this perspective, I typically use BDD styled tests for testing business logic defined as user stories in an agile project, but for simple unit test cases on internal classes, I write AAA styled tests.
In the examples shown below, I have a base class called SpecificationContext that executes the Given and When methods in the test initialise. Each test class executes the Then methods as each individual test.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestClass] | |
public abstract class SpecificationContext | |
{ | |
[TestInitialize] | |
public void Init() | |
{ | |
Given(); | |
When(); | |
} | |
public virtual void Given() | |
{ | |
} | |
public virtual void When() | |
{ | |
} | |
[TestCleanup] | |
public void TestCleanup() | |
{ | |
Cleanup(); | |
} | |
protected virtual void Cleanup() | |
{ | |
} | |
} |
I derive from the SpecificationContext class to provide a DrinkOrderContext class which creates a context that I can perform a number of different BDD styled tests on.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DrinkOrderContext : SpecificationContext | |
{ | |
protected Mock<IPriceList> drinksMenu; | |
protected Mock<IClock> clock; | |
protected List<Order> orders; | |
protected decimal halfPrice = 17.05m; | |
protected decimal fullPrice = 34.10m; | |
public override void Given() | |
{ | |
base.Given(); | |
// create mock price list | |
this.drinksMenu = new Mock<IPriceList>(); | |
// setup lookup method for three drinks | |
drinksMenu.Setup(dm => dm.LookupPrice("Little Creatures Pale Ale")).Returns(7); | |
drinksMenu.Setup(dm => dm.LookupPrice("Pigs Fly Pale Ale")).Returns(8); | |
drinksMenu.Setup(dm => dm.LookupPrice("Hitachino White Ale")).Returns(9); | |
// create mock clock | |
this.clock = new Mock<IClock>(); | |
// create the list of orders | |
this.orders = new List<Order>(); | |
orders.Add(new Order() { Drink = "Little Creatures Pale Ale", Quantity = 2 }); | |
orders.Add(new Order() { Drink = "Pigs Fly Pale Ale", Quantity = 1 }); | |
orders.Add(new Order() { Drink = "Hitachino White Ale", Quantity = 1 }); | |
} | |
} |
My test classes are named according to the Given and When aspect of the test, and the test methods represent the Then component of the test, such that the results of running the tests show the class name in one result column, and the test in the next column. If my tests had multiple expectations i.e. Then blah And blah, then each expectation would be another test Then. Each test is then run independently, so there are no tests dependent on another test having been executed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestClass] | |
public class given_a_drink_order_when_the_customer_orders_at_sunday_626pm : DrinkOrderContext | |
{ | |
private decimal result; | |
public override void When() | |
{ | |
base.When(); | |
// setup clock to be 6:26pm on Sunday 30th of the June 2013 | |
this.clock.Setup(c => c.GetDateTimeNow()).Returns(new DateTime(2013, 06, 30, 18, 26, 00)); | |
var checkout = new Checkout(this.drinksMenu.Object, this.clock.Object); | |
this.result = checkout.CalculateCost(this.orders); | |
} | |
[TestMethod] | |
public void then_the_cost_is_full_price() | |
{ | |
Assert.AreEqual(this.fullPrice, this.result); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestClass] | |
public class given_a_drink_order_when_the_customer_orders_at_friday_513 : DrinkOrderContext | |
{ | |
private decimal result; | |
public override void When() | |
{ | |
base.When(); | |
// setup clock to be 5:13pm on Friday 28th of the June 2013 | |
this.clock.Setup(c => c.GetDateTimeNow()).Returns(new DateTime(2013, 06, 28, 17, 13, 00)); | |
var checkout = new Checkout(this.drinksMenu.Object, this.clock.Object); | |
this.result = checkout.CalculateCost(this.orders); | |
} | |
[TestMethod] | |
public void then_the_cost_is_half_price() | |
{ | |
Assert.AreEqual(this.halfPrice, this.result); | |
} | |
} |
Example results are shown below.
Conclusion
This was just a brief introduction to the class structure of my tests so that you understand the pattern on the tests that I will be discussing in future posts.
Hopefully the discussion about TDD/BDD patterns gave you something to think about in terms of patterns for test cases. From a practical perspective, I use the Gherkin styled pattern for defining acceptance tests so that I can easily verify that I have captured a customer's expected system behaviours within my test suites, which gives me confidence that I am delivering the expected solution, as well as "safe" environment to refactor the solution to improve code reuse, or optimise for performance down the track.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.