Tuesday, September 30, 2014

Thoughts on unit testing

Last week, I blogged about Chill. It's a way of testing I've been playing with over the last couple of years.

A unit test should be named cleanly

As with anything, naming of unit tests is very important. When you execute the tests, the first thing you see is the name. If a test fails, you quickly want to know:

  • What’s the test subject? Is it a simple unit test or more a scenario?
  • What is being tested? Which method is being called or which scenario is being executed
  • What is being asserted? 

There are many different naming styles. I find myself to prefer a ‘given when then’ naming style.

The example above describes this. In my test runner, I see

To me, this is very clear, expressive and organized. It also offers me plenty of way to make certain bits reusable.

A unit test be concise, simple to read and understand

Unit tests are there to help you make and detect mistakes. When you make a change in a larger project, it’s likely that you’ll break some unit tests that you haven’t written yourself. Imagine opening a breaking unit test that consists of 3 pages of complicated code. Good luck in finding out what’s wrong.

Unit tests should be concise. A couple of lines of setup code MAX. A single line for the WHEN and ideally a single assert.

A unit test should not require a lot of code

When something requires a lot of code to test, it’s typically a smell that the thing you’re trying to test is too complex.

However, your style of testing also impacts the amount of code you have to write. When writing unit tests, you often have to:

  • Create mock objects for your dependencies. 
  • Setup your dependencies
  • Create test data and store this in member variables. 

A lot of this work is repetitive and just clutters up your test code. I’ve put quite a bit of effort into minimizing this amount of work.

For example, Creating an object, even if that object has 35 properties,  should be a single line. (NO, putting all 35 properties on a single line does not count!). You can see how I’m using a Mother class combined with the .With() extension method to minimize this work.

In my current project, I’m seeing a lot of unit tests, but each requires a lot of code. Spec files of more than 1000 lines are not an exception here. To me, this immediately flags an issue both the subject under test (maybe the subject has too many responsibilities) but also with the style of testing.
Call me lazy, but code that I don’t have to write is also code I don’t have to maintain.

Unit tests should show relevant information

If you have many test cases for a single class, it’s tempting to put a lot of testing logic into separate reusable functions. For example, creating objects, setting up mocks, etc. While this has the (huge) benefit of not having to repeat yourself and thus making your tests more maintainable, it has the downside of hiding information.

I believe it’s important NOT to hide relevant information in your tests.

In the TestStack example, I’m creating a customer with a specific customerID. The customerid is important, because that’s also the customerid that I’ve configured my mock object with.
If you would have read:
Var c = CustomerMother.CreateMother();

You wouldn’t know where the magic number 12 came from. You now have an implicit dependency between the way the mother creates the customer and the mock object. This immediately reduces the understandability of the tests, but also increases the brittleness of the tests.

That’s why I love the .With() extension method. It provides me a very light weight way to keep ONLY the relevant information within my tests.

Unit tests should not repeat information

Yes, the DRY principle. Often misused, but when applied correctly very useful.

One of the techniques I use to avoid repeating myself is AutoMocking. With automocking, my subject gets created by a container and automatically gets mock objects injected. I don’t need to explicitly call the constructor anymore. If you have 10 tests all creating a subject with a couple of dependencies and then you have to change your subject to add a dependency, then you’d need to change all 10 constructors calls, just to get it to compile again.

Also automocking helps me so that I don’t have to explicitly have to create and store mock objects in a variable. This is all ceremony that doesn’t add anything to the readability of my tests.
The second technique I use a lot is the ObjectMother pattern. Often you need objects containing data in your tests. I’ve often seen that creating and populating these objects is done explicitly in your tests. However, not only does this repeat a lot of code, this code is also unneeded in your tests. It also makes your tests more brittle, because the definition of what a ‘valid’ object is you can use for testing with can change over time.

Again, the With() extension method allows me to customize the objects created by a mother in a very lightweight fashion.

1 comment: