The problem with Unit Tests
But what about UI projects? Virtually every project I’ve worked on uses core third party libraries to handle rendering, templates, AJAX, routing, rich editing, DOM manipulation, modules etc. Unit tests for much of these make sense, and those projects more than likely have their own tests (as well as being battle-tested by users).
So what do we do? We pick an arbitrary code coverage target like 95%, and go and write 100s of tests for simple UI methods. Your boss approves because they think it’s making their product better.
Most app code is doing something in the event > update state > update UI cycle and it’s repetitive, simple code. Usually the complexity occurs as the app experiences growing pains and the mutation of state and signalling of events between modules becomes tangled — an issue which unit testing doesn’t concern itself with since it tests units of code in isolation.
when(‘onDialogCancelClick is called’)
then(‘I expect the closeDialog method to be called’)when('hideElement is called with true')
then('I expect the hidden class to be added to the element')
when('hideElement is called with false')
then('I expect the hidden class to be removed from the element')
Before long your UI will be coupled with these kind of tests which add no value, and your ever-evolving UI requires more and more refactoring of those brittle tests, slowing the velocity of your project.
Contrary to the belief of the product owner or manager, most unit tests do not:
- Make your code more stable
- Reveal bugs
- Help with refactoring (actually they actively hinder it)
By their nature unit tests thrive at testing pure functions, but they are bad at testing the behaviour of your UI.
In my years of experience working on unit tested projects I’ve observed that developers write the code first, then the tests after. TDD is just an unnatural fit for most UI development, where you’re not sure of the shape of your view until you start building the UI.
BDD to the rescue?
BDD (Behaviour Driven Development) is a reaction to this problem. It aims to change superficial tests like this:
when(‘onDialogCancelClick is called’)
then(‘I expect the closeDialog method to be called’)
when(‘I click the cancel button’)
then(‘I expect the dialog to be closed’)
It puts the focus on testing the desired behaviour of the code. This means that the tests are less rigidly coupled with the implementation details, and can provide more value to the product.
For a non-UI project BDD can be a little harder to grasp, but it’s pretty clear here, and there’s a fundamental problem..
Shouldn’t we be testing behaviour with functional tests?
I think we should. A DOM which exists only in memory is not suitable for testing UI behaviour — it’s inaccurate and limited, and your unit tests have probably loaded up a particular module and aren’t suitable for checking the behaviour of that module in situ. It’s like testing each single segment of a rollercoaster ride detached from the rest and then saying that it’s good to go.
Unit tests are good. How we use them isn’t.
We need to be more considered as to when we use unit tests, forget code coverage and instead ask yourself what benefit every test is giving. Write fewer superficial tests like When I trigger this event, I expect this callback to be called, and more When I create a tree of views and remove one in the middle, cleanup should be executed in the correct order and there should be no memory leaks.
But let’s just remember..
No matter whether you have 100% unit coverage and functional tests for every AC, in a large project the things that will bite you are environment, integration, performance, and edge case bugs that nothing picked up except manual testing or at worst red-faced customers. So always weigh up the cost and value of automated tests and don’t run away with the hype. Keep your tests fast and resilient, balance it with your development velocity, and make sure you can release fast when shit hits the fan.
Of course this is just my opinion, if you think unit tests provide more value than I’m giving them credit for, let me know!