Saturday 27 October 2012

Starting off with unit testing and TDD? Here's my advice...

Thanks to Mark Mishaev for inspiration.

Sometimes, I hear that someone is trying to start off with unit testing or TDD and they ask me for some good advice on where to start - some general guidelines that would help them along the way. I thought I'd summarize my usual answer here for everyone to benefit from.

So, if you're starting off with unit testing or TDD, here's my advice:

  1. Read at least these blogs to grasp some good practices:
  2. Pick up some books on the subject. The topic of unit testing and TDD might seem straightforward (there are a lot of simplifications around, such as "unit test is just a small code that creates a class, invokes a method and performs assertions on its output" and "TDD means you write your test first, then write the code"), but it actually requires quite a bit of knowledge to get things right. Also, don't limit yourself to a single book - read at least three or four - many TDD gurus have more or less different styles of doing TDD and writing unit tests and your own technique will get more and more flexible as you manage to understand more of them.
  3. Try writing your tests first. For a list of benefits, take a look at:
    1. The importance of test failure
    2. Test First - why is it so important?
  4. For new code, try to follow Need Driven Development
  5. To drive your code and design, try exposing yourself to “diagnostic pain”. This means putting some limitations onto yourself and when you’re tempted to break them, it means that the code or design is at fault and you should refactor. To give you a quick example, I do not use Setup and TearDown kinds of methods in unit tests (not mentioning Action Attributes from NUnit), which means all the objects used in the unit test are created in the body of the test method itself. I sometimes use helper methods when they improve readability and understandability of the test (but not to hide redundancy - that's the catch). At first it might look awkward, but I have created a lot of unit tests without Setup and Teardown and I'm very happy with the result. So it’s not like I don’t know how to use a unit testing framework - I deliberately hold back from using many of its features
  6. Ideally, a maintainable unit test has to fulfill three conditions, as pointed by Scott Bain:
    1. It fails reliably when behavior specified by it is broken
    2. It fails only when behavior specified by it is broken (so breaking another behavior should not break this test)
    3. No other unit tests will fail when behavior described by this test is broken
    This is the ideal - try to keep as close to it as possible.
  7. Try using a technique called Constrained Non-Determinism. You can either use tools that help with this (like AutoFixture for C#) directly, or you can wrap it in your own class (if you're reading my blog, you probably know that I like to wrap creating anonymous values in a class called Any). Alternatively, just roll out your own mini library or a set of functions.
  8. Use continuous testing tools, (examples for .NET include Mighty Moose (free) or NCrunch (paid)) for running your unit tests. If no such solution exists for your programming language of choice, you can always write a script that performs continuous compilation and running unit tests and lets you know of the results. There are some tools like Watchr that can make writing such scripts easier (especially when integrated with your operating system's default user notification mechanism).
  9. Always specify class behaviors (not methods or classes) with unit tests – to help you with this, try using the following convention of naming your tests: ShouldXYZ() where XYZ is the description of the behavior the class should supply. The convention is taken from Dan North’s post and helps to avoid:
    1. Check-it-all unit tests – where one does a single setup and verifies multiple behaviors.
    2. Names that do not make sense. I saw one a unit test named: SendFrameMethodSendsFrame() which tells almost nothing about the behavior. The “Should” naming forces us to rethink the name and come up with something like: ShouldPassReceivedValidFrameThroughTheMessageQueue() which works better.
    3. Also, pick names that read well when converted from "ThisNamingConvention" (or "This_naming_convention" - whatever you choose to apply to your unit test methods) to "this naming convention" – it lets you use the unit test results report in Cruise Control or Jenkins as a living documentation of your code after you apply a little post-procesing to it.
    4. You can look at my blog post that discusses the repercussions of bad naming.
  10. When choosing a mocking framework for your project, put your money on ease of use and readability. For example, many mocking frameworks for C# use lambda expressions everywhere, but many programmers are still not used to thinking in terms of expressions and are a bit confused. When developers find using such framework awkward, they're more likely to reject the idea of unit testing or TDD. Thus, as a main mocking framework, I like to choose NSubstitute.
  11. Always keep handy a "fallback mock framework" - there are some features that your main mocking framework of choice does not support or that are difficult to achieve using this framework. For example, NSubstitute does not yet support partial mocks. That's why it's good to have a secondary mock framework of choice that can make up for the main one's weaknesses in some rare cases. For such a fallback mock framework, I like to use Moq, but that may as well be FakeItEasy or Rhino Mocks or something else - depending on what you need.
  12. Try to stay as close as possible to SOLID principles and adhere to the Law Of Demeter (which is a conclusion from those principles, specifically - from Open Closed principle) – all of this lets you write better, more focused and more maintainable unit tests. For a list of smells that show the problem is in the design rather than in tests, see:
    1. Test Reflexology - part 1
    2. Test Reflexology part 2
    3. TDD and Single Responsibility Principle
    4. Mocking method chains, part 1: when not to
    5. A kata challenge to try your TDD skills on - failure and what we can learn from it
  13. Watch out for unit tests execution times – unit tests that run for more than 2-3 seconds (to tell you the truth, many should even run in less than one second), almost always have a smell attached to them – either they touch external resources or are not unit tests. Staying away from external resources may be tricky. For one such example where this actually does get tricky and one may be tempted to break the isolation, see my example related to timers
  14. Avoid the "The code is so straightforward that there’s no need to test it" thinking. When the code is complex, the solution is refactoring, not unit-testing (of course, tests can be written, but they have to be used as coverings). Unit tests are about specification, not coverage, and short specifications are good.
  15. Perform a kata or two on your own. One such kata and its exemplary solution are posted on this blog
  16. There may be a temptation to write more coarse-grained tests/specifications that are tied to the code (they’re sometimes called white-box tests). This is possible, however, I recommend not to do it if it’s not very well thought. I’ve been in projects where coarse-grained, so called “white-box tests” led to situations where correcting and debugging the tests took twice as long as changing the code. Again, it’s possible to write good coarse-grained tests, it just that it’s hard – it requires reading some books and doing few dry runs to grasp many best practices that are different than in case of unit tests. On short discussion of how things may go wrong, be sure to look at my post: Perfect from the start.
  17. Also, there may be a temptation to "test private methods". This is a sign that things are going a little bit offroad.
  18. Attaining X% code coverage should not be a goal. High coverage is good when it’s a side effect.

I could go on writing, but I think these are the most important, non-obvious things I learned throughout my adventure with TDD. As always, feel free to add your advice in the comments.

See ya!

2 comments:

James Peckham said...

no setups or teardowns... interesting. If i did that i would have a lot of duplication and i like a fixture to revolve around similar setups, but admittingly i'm in some very legacy stuff usually so my classes usually have a lot of dependencies to inject. smelly :)

Good post! Thanks for doing it.

Grzegorz Gałęzowski said...

Hi, James!

Thanks for your comment!

I agree that in legacy code, you don't have to get everything perfect from the start - every change for better is good. Even when the tests are not perfect, they can serve as coverings for future refactoring.

On the other hand, with new code, the "no setup and teardown" rule is the first thing I advise teams to try out - people new to unit testing are very likely introduce a lot of smells and hide them in Setup and Teardown methods. This wastes a lot of valuable feedback on the design.

Another point - setup and teardown have their own problems and, as surprising as it can be, they can actually make tests harder to maintain! For instance, when you change something in your setup, sometimes you have to read through all the tests using it and make sure that this new setup logic does not make them "false positives" (i.e. tests that can never fail) etc. Also, when an instance of system under test needs to be configured differently for some tests and differently for others, there's a temptation to create two of them in setup (where actually you'd have to create two different classes, each of them having its own setup). Then, not only can the tests look weird (because we have to think of a name that makes sense for each such instance and naming them "instance1" and "instance2" hinders readability), but a person who needs to understand just a single test needs to read the part used in other tests as well and distinguish between the parts that matter for the test in question.

One more thing - there are styles of organizing unit tests where setup and teardown are used more heavily (such as "one behavior per test class"), but I find them harder to grasp for beginners and less intuitive than "one behavior per test method".

As you see, the topic is a long and interesting one :-). Also, it's actually not my idea - the "no setup and teardown" rule is well established in the TDD world (e.g. http://jamesnewkirk.typepad.com/posts/2007/09/why-you-should-.html
). Of course, there are opponents and proponents on each side of the fence - that's why it's always good to listen to what others say.

Thanks again and best regards!