Writing tests at the API boundary isn’t as clever as you think
Friday, June 7th, 2019 | Programming
Back in the day, unit tests and code coverage were the in thing. You wrote a class, you wrote a unit test for that class. Everything was tested at an individual level and you built integration tests on top of that.
Then we realised that maintaining all of these unit tests was a massive pain in the neck, made it tedious to refactor the code and didn’t provide much value when the real business value was only concerned with everything working together so that the user being able to successfully complete a journey. So, we started writing tests to the API boundary.
Fine. Except it wasn’t fine. The problem is that per-class unit tests are really useful when it comes to understanding other people’s code.
Let’s say we have a component and it is composed of many different internal functions and classes. We write a test to the API boundary so that people can use the component and we know that it works when people use it. That is fine if you are the only person maintaining the component and you have a good memory.
But what if multiple people work on the component and somebody else needs to refactor it? You could try looking in the docs. There are unlikely to be any, though, because it is an internal function. You could try reading the code. This approach is probably the one we rely on most of the time and often works. But if often isn’t too obvious, especially in loosely typed languages with lots of callbacks (*ahem* javascript).
How do you know what goes in and comes out of a function without docs and without types? You don’t. But having a unit test demonstrating stuff being passed in and out is really useful for making an educated guess.
Back in the day, unit tests and code coverage were the in thing. You wrote a class, you wrote a unit test for that class. Everything was tested at an individual level and you built integration tests on top of that.
Then we realised that maintaining all of these unit tests was a massive pain in the neck, made it tedious to refactor the code and didn’t provide much value when the real business value was only concerned with everything working together so that the user being able to successfully complete a journey. So, we started writing tests to the API boundary.
Fine. Except it wasn’t fine. The problem is that per-class unit tests are really useful when it comes to understanding other people’s code.
Let’s say we have a component and it is composed of many different internal functions and classes. We write a test to the API boundary so that people can use the component and we know that it works when people use it. That is fine if you are the only person maintaining the component and you have a good memory.
But what if multiple people work on the component and somebody else needs to refactor it? You could try looking in the docs. There are unlikely to be any, though, because it is an internal function. You could try reading the code. This approach is probably the one we rely on most of the time and often works. But if often isn’t too obvious, especially in loosely typed languages with lots of callbacks (*ahem* javascript).
How do you know what goes in and comes out of a function without docs and without types? You don’t. But having a unit test demonstrating stuff being passed in and out is really useful for making an educated guess.