I don’t thing anybody has missed the fact that unit tests increase the quality of the code. But then someone starts talking about negative unit tests. What the heck is that?
A unit test verifies that the functionality in the code works as expected, but don’t forget the other side of the coin. Errors. And negative tests verify that your code doesn’t get stumped when an error occurs. Here are a few things that helps you write them.
Stable code doesn’t break because of errors
Code, like life, never works out the way you intended. There is always a lot of stuff happening outside of our control. For software, we use a lot of code that we don’t write ourselves. The operating system for instance. Or what about all those third party packages, like NuGets in the .Net world? Someone else will mess up, and you will take the hit.
So, you have to prepare. Try to anticipate as much as possible and make sure that your code will handle the problems as well as it can. It’s like you’re driving, and all of a sudden someone cuts you off. I you didn’t have any error handling you wouldn’t be able to break. You would probably slam into the car in front of you with a huge accident as a result. But, if you instead do have error handling, you would hit the brakes, avoid the problem, and continue along as if nothing happened.
This is your task as a developer. To make sure that the user gets to the destination safe and sound. You know it yourself, when an app or web page blows up in your face and you can’t complete that purchase, or all the data you just registered vanished into thin air, never to be seen again. This is something that never should happen. And the only person that can fix that in your code, is you! So you have better start thinking defence.
Negative tests are just as important as the positive ones
As you see, code that handles errors are just as important as the rest of the code. So why shouldn’t it get the same treatment when writing tests? Of course it should! We have to start writing tests that verify the error handling. If you think of it, error handling is functionality, just it’s not declared in a user story. Without it, the code won’t work properly. And functionality can contain bugs. And we use unit tests to protect us from that.
There will be a slight shift in your mind when you start thinking of error handling. When you code the happy path, you solve problems, you create data structures, you move things forward towards the end goal. Error handling is a bit the other way around. It is defensive, it is protective, it is a bit like taking care of your drunk friend, when he wobbles about and you stop him from walking right out into traffic or stops him from making a total fool of himself in front of a crowd.
Writing negative tests will reveal what you are missing
Something happens when you start thinking of ways that the code can fail. You start seeing the code in another light. Before, when you were on the happy path, you were creative, you were pushing forward, and that is a good feeling. But now, you take a step back and star challenging yourself. You start seeing ways that the code can fail.
Simply put, you start finding bugs!
What should I think of when I write negative tests?
Stop being a developer and start being a tester. The mind shift is that you are not constructing code, not creating. Instead, you are trying to break it. You are trying to find out how it can fail. The very fact that you are trying to walk paths in the code that you didn’t before will reveal a lot about it. Things that you just didn’t consider before becomes visible.
You look at possible return values, other than the expected one. Can it return an integer and you only expect it to return positive values? Then you have to make sure that the code can handle negative ones.
You analyse if:s, are there are any possibilities that you missed before?
And what about null? Do you handle nulls properly or do you just let the code explode in an exception?
Can the function throw an exception? Should you catch it?
There are a lot of things that you want to look at when you are in this mode. And of course, you write the tests to make sure that the code never does these things. And this opens a new chapter in the code. Error handling. When an error occurs, how should you handle that? You can’t ignore it, you have to do something. Interrupt the operation? Roll-back? Error messages? Data integrity? Did you save anything before? Logging?
It is wise to think of this before you start writing your app. You need a strategy for error handling, so you don’t have to put too much time into figuring it out every time you do error checking.
You also might want to read about Contract Testing, the tests you write to make sure that two functions can work well together.