Unit Tests Test Your Requirements, Not Your Code
When people are first getting started with writing unit tests, the pattern typically looks like this:
- Write classes and methods
- Write unit tests that test those classes and methods
Let’s say I was building an e-commerce application, and I had a ShoppingCart class with an AddProduct(IProduct product) method. If I’m new to unit testing, I’m probably cutting my teeth by creating a ShoppingCartTest class that has an AddProductTest() method in it. Lots of people start learning like this. It is such a common way to do unit tests that certain testing frameworks will even take your code and automatically generate unit tests for you (MSTest, I’m looking in your direction).
Now, we all have to start somewhere, and this is also how I started writing tests, but the weakness of learning this way is, when you decide to make the shift into TDD by writing your unit tests first, most people are totally clueless as to how to do this. How do you write unit tests before your code if the unit tests are strictly based around the code you’ve written? These poor souls sweat railroad ties desperately trying to essentially write classes in their head, then write their tests, then write the classes. This is TDD to them, and it is a painful and unhelpful thing.
The remedy to this, and the quickest path to becoming a TDD developer, is to learn to write unit tests that test your requirements. This bears repeating with more whitespace around it.
Write tests that test your requirements.
There are all kinds of helpful testing frameworks to make this more intuitive (SpecFlow, MSpec, and many other excellent tools), but for the moment, I just want to focus on how to do this regardless of a specific tool.
To return to the e-commerce example above, let’s say I have the following requirement / user story / common sense insight that reads thus:
As a shopper, I want to add products to a shopping cart so that I can purchase them all at once.
Instead of sitting down and slamming out code for this or circling the nouns to get my class names, I’m going to write a test for this requirement. Maybe it looks like this:
public class When_adding_a_product_to_the_shopping_cart { public void The_number_of_items_in_the_cart_should_increase_by_one() { } }
I would continue to add test methods for each thing that was supposed to be the case when someone added a product to the shopping cart. For example, I might have a The_item_added_should_be_a_product method and so on. Notice that the tests are being formed to test the requirement, not any specific code that might get the job done. Notice that the class and method names read in a pretty straightforward manner. This is how I know I’m covering my bases. Even a business user could walk through this (with me, of course) and let me know if there were any aspects of adding a product that I was missing or misunderstanding.
So, now I start thinking about how to test that requirement. Maybe it looks like this:
public class When_adding_a_product_to_the_shopping_cart { public void The_number_of_items_in_the_cart_should_increase_by_one() { var cart = new ShoppingCart(); var product = new Product(); cart.AddProduct(product); Assert.IsTrue(cart.Products.Count == 1); } }
Ok, that’s not actually the code I’d write, to be honest. The product should be a mock. I’d set up the cart and possibly execute the add in a precondition to this test and just check the product count in the actual test method, but I don’t want to muddy the waters right now.
This test code will break, of course, mostly because I made reference to a ton of things that don’t exist in my code. The test will be red. It now becomes my responsibility to write the smallest amount of code I can that will make this test compile and run successfully.
There are two, huge benefits (among others) from writing your tests this way (which is more of a BDD way):
One, writing your tests first becomes a lot easier. You write classes around the basic pieces of required functionality and methods around the stuff that should happen with that functionality. Your methods should also include what happens when things go wrong, etc. Basically the kind of stuff you need to know to build your application. Once I have that broken down, it’s a lot easier to formulate the kind of code I’d need to get that done.
Two, and this cannot be stated enough, successful tests mean that I’m delivering what the application should do, not that my code does what I expect. We’ve all seen it – a nice suite of unit tests that run green, but the code doesn’t deliver on the expected requirements. It works, it does what you told it to do, it just doesn’t do what anybody wanted or needed. In this methodology, if all my tests pass, it means my code is fulfilling the functionality we needed, not simply that the code is working as intended.
You can probably see where this is going. In Part 2, we’ll look at how this process helps you write actual code.




