Changing how I structure unit tests
When I write unit tests, I use the BDD-style GIven/When/Then format so that the tests are descriptive and explain the business functionality that is being implemented. But I’ve recently changed the way that I do it.
Let’s say that I’m implementing bank account functionality and I am writing code to implement the following:
Given a bank account
When I deposit money into the account
Then the balance should increase by the amount of the deposit
The old way
I used to write them like this:
[TestFixture] public class When_I_deposit_money_into_the_account : Specification { private Account _account; public void Establish_context() { _account = Given_a_bank_account(); } public void Because_of() { _account.Deposit(10m); } [Test] public void Then_the_balance_should_increase_by_the_amount_of_the_deposit() { _account.Balance.ShouldEqual(10m); } private Account Given_a_bank_account() { return new Account(); } }
This style is used by RSpec and many people in the .NET community. I’ve had a lot of success doing it this way, but I’ve always had some complaints.
- The Given/When/Then phrases are spread out and aren’t in order.
- We have the Specification base class, which isn’t bad but I think it might confuse new people who know NUnit but don’t know my special base class.
- If you have a lot of “given” statements, you’re tempted to use inheritance or nested contexts, each with their own level of setup, virtual properties and methods, etc. This is very unreadable and gets unwieldy very quickly.
The new way
This isn’t really a new way, but it’s new to me. I’ve seen it before over the years but I didn’t really start doing it until I joined my current team where they did it this way and I’ve come to like it a lot better than the old way.
[TestFixture] public class Bank_account_tests { [Test] public void Deposit() { Given_a_bank_account(); When_I_deposit_money_into_the_account(); Then_the_balance_should_increase_by_the_amount_of_the_deposit(); } #region Helper methods private void Given_a_bank_account() { _account = new Account(); } private void When_I_deposit_money_into_the_account() { _account.Deposit(10m); } private void Then_the_balance_should_increase_by_the_amount_of_the_deposit() { _account.Balance.ShouldEqual(10m); } #endregion }
The best thing about doing it this way is that the business functionality is clearly specified at the top of the class and the Given/When/Then statements aren’t spread out all over the place. There are no crazy inheritance hierarchies, base classes, or big setup methods. When I write tests, I just write out the Given/When/Then scenarios in plain text and then use Specs2Tests to generate the test code for me. Then all I have to do is fill in the private helper methods. This is really easy, like filling in the blanks.
Also, I typically hate regions but in this case I find that they work quite well because they hide the helper methods that I typically don’t want to see when I open the class file.
It depends
Obviously there are situations where this is all overkill and you can just write simple tests without Given/When/Then methods all over the place. Just do whatever makes sense. I’ve found that this new-to-me way leads to very readable and easy to maintain tests.
I’ve never seen tests structured like that before, and it’s interesting. I’m wondering if you could set up a fluent interface paradigm where all of the helpers return an instance of the class under test. (I haven’t really thought through the details, but the possibility of having Assert(Given().When().Then()) seems interesting)
Thanks for posting this Jon. I have noticed a lot of discussion on StackOverflow about how to structure unit tests in a BDD style, and have seen tools such as NBehave and MSpec promoted. The problem is, it requires a developer to really think about what the test code is doing, not what the semantics of the test actually are.
That being said, I have favored using Gherkin to drive the unit testing layer and have used SpecFlow to implement my Specs. However, I am going to have to try the approach you are sharing here. It is elegantly simplistic, and quickly helps developers to know exactly how a unit of code should behave and potentially allows us to share this more easily with non-technical people.
At the end of the day, I truly think that unit tests, behavior tests, and acceptance tests should be written so anyone (technical or not) could pick up a test and understand what is being tested.
Thanks again!
Interesting. A question…
Large set-up of dependencies always make my “before_each” (your “establish_context” above) type methods pretty large, so I’m guessing that happens in your Given method. Is that the case in practice? Or do you build the context elsewhere to keep the Given clean?
Tim,
The thing is that now that before_each is split up into multiple Given methods, so it’s not as messy. Sometimes those Given methods are still a little messy, and sometimes I still need a “before_each” style setup method that sets up mocks and stubs that apply to all of the tests. But at least it’s readable now.