People often ask me to review their unit tests, or answer questions on how to write different aspects of tests. I frequently find myself making the same suggestions over and over again. So, if you were thinking of asking one of these questions, maybe these common BDD refactorings will help.
I have 10 different instances of this class or enum. Does that mean 10 tests?
Tests should make things easy to change. What you’re doing is pinning down your code so that it’s hard for people to break it. I know that when your code is changed and the test breaks, people will look at the tests and work out what went wrong; that’s because your tests are brittle. Instead, I’d rather people came and looked at the tests because they’re clear examples of how to use the code and why it’s valuable.
QAs use equivalence partitioning. They say, “If I do this, it’s equivalent to doing these 5 other cases. So I don’t need to do those.” They also look at boundary conditions, where a shift in context starts to produce different behaviour. If we can do this too, we can cut down the number of different examples we need to produce for one aspect of behaviour.
So, for your ten cases, I might have just a few examples. This makes it easy for people to add more examples, and encourages them to understand the boundaries and partitions. We want it to be easy for people to change the code.
How do I do <this complicated thing> with my mocking framework?
Tests should make things easy to change. If you’re doing something complex with mocks, this is what it looks like:
- Given <this context>
- Expect <this outcome>
- Also <this really complex stuff that you have to read carefully>
- When <this event happens>
- Then <go back to the expectations and read them, because this is where that gets checked>
If the mocking you’re doing is making it harder to change the code, or you don’t have access to something like Mockito, try rolling out your own. This will make it easier to do the things you want in a legible way, and any assertions can happen at the end of the test, along with the rest of the outcomes.
I’ve called my test
should. Is that right?
Tests should make things easier to change. Why is returning true valuable? Because it triggers some business process? All right, then let’s call it
should. This will encourage people to read your tests. While you’re at it, you might want to rename the method to the same thing.
Yes, you’ll get duplication between the test name and the code name. That’s because your method is doing what it says on the tin, and you’re providing an example of how to do that.
Now my test is called
should. Do I need to write
Tests should make things easier to change. If I have to look in two different places for the examples of behaviour I want to change, then it’s not so easy. So, maybe not.
There are three ways of doing this. I’m guessing that triggering the process is the “happy path”. If this is the only valuable thing that happens, and it always happens, that’s fine – that’s the first way. But we know you were returning true, so there’s something else we need to consider. Is the happy path valuable without the sad path? For instance, a list tells you when it’s empty. That’s not valuable unless it also tells you accurately when it’s not empty. True for empty, false for not – they’re the same piece of behaviour. Neither is valuable without the other.
If you have two tests – one for each – you might want to put them in the same test method. Now you’re describing the behaviour holistically. I know Dave Astels said “one assertion per test”. We’re only looking at one aspect of behaviour. That’s the second way.
Of course, you might have a third way too. If you have several exceptional cases, you can list these as exceptions. So you’ll have one
shouldNot for each context in which the process is not triggered. Validation is a good example of when this happens; you would normally have one description of the happy path, and one description for each of the exceptions.
I can’t write these automated tests. It’s very difficult to describe how to use my class.
Really? Probably it’s quite a difficult class to use correctly, then. How did you think of that API? Did you think of the class first, then think about how to use it?
Instead, try thinking as if you were the consuming class. Think, “I need something that does this for me. This is how it should look. This is how I want to use it.” Then write some code that does exactly what you want.
Because you’ve thought about how your class is going to be used, it will be easier to describe how to use it in the examples. It will also be easier to understand. So when you do successfully write your tests, you’ll know that your code is easier to change.