Test-Driven Development (TDD) is funny. Most developers hesitantly acknowledge they should do it more often, however, I see a lot of developers failing at it. It seems to me, most of us still don’t understand, why we should even bother. So some of them only use it when they are working on the most simplistic stuff. In fact, they should do the exact opposite. Let me explain.
I know that Dave Farley and Martin Fowler have been preaching about my epiphany since the beginning of Agile. And I’ve heard what they are saying. But I have never felt it.
What Most People Don’t Understand about TDD
TDD is not about harassing less experienced developers. It’s not about making the coding process more time-consuming and overly complex. It’s certainly not busy work. We also don’t do it for the infamous 75% code coverage. It is not even about the tests themselves.
TDD is a design technique.
All the other benefits are just side effects.
Why is TDD a Design Technique?
The moment I started writing tests, I had to re-think my design “top-down”. I was forced to think about the logic I wanted to encapsulate in the component. I was forced to think about the API of the component that will control its behavior. Every time I wanted to add logic to the component, at the same time I had to think about how to test it. To put it in other words: TDD makes you look at your code from another perspective.
My component had way too many responsibilities. It was wired to four apex methods, loading data and dynamically rendering contents based on the data received. Mocking those apex methods is not only a lot of boilerplate and makes your tests very hard to read. It literally made me feel uncomfortable.
As a result, writing those tests uncovered plenty of design flaws in my code.
It helped me to improve encapsulation and reduce coupling. It made it easier to design a concise input and output API and encouraged me to design for reusability. Conclusively, I extracted two stand-alone subcomponents from my big monster component.
Is TDD your silver bullet to achieve the best designs? Of course not. But if you use it properly, it makes your life easier.
How To Use TDD Effectively
TDD helps to achieve better designs, more quicker. It uncovers some of the flaws of bad architecture very quickly and brutally. This gives you actual feedback on how your code “feels” to use, which is invaluable.
If you are a very experienced developer, chances are you don’t need this technique to write good code. When I write Apex, I don’t struggle with encapsulation, single responsibility, and good testability. This all comes naturally. But since I am still inexperienced in LWC, TDD helped me a lot to improve my design.
To apply TDD effectively, I found it very helpful to listen to your feelings while writing tests. When writing my tests, I tried to identify recurring feelings that guided me toward a better design. I found it incredibly useful to use the uneasiness they create to take action.
“I Cannot Even Describe This Test” – Feeling
Tests should be named after the functionality under test, the relevant input, and the expected output. If you can’t come up with a concise name, there’s something wrong.
- If you have problems describing the functionality, chances are there are too many things going on in your component.
- When it’s hard to describe the relevant input, your component depends on too many things.
- If you can’t precisely name the expected output, the component probably does too many things with your inputs.
Use this awkward feeling. Think about how you can reduce the complexity of your component. Extract logic and responsibilities, until testing becomes easy again.
“How Should I Even Test This?” – Feeling
A good component or class has no side effects and shouldn’t depend on the internal state of an imported dependency. Something goes in. Something comes out. And if you’re lucky, everything is deterministic. Yet we all write components that
@wire five apex methods and dynamically load more forms based on data of one of the loaded records.
Every time you have to awkwardly mock the results of multiple apex methods to achieve specific behavior, this is where you should pay attention.
Try to extract the logic into a dedicated component that receives the relevant data via an
@api property. This will allow you to write unit tests without mocking
@wire adapters. Wrap a dedicated component around it that handles all the apex calls. Testing and understanding this component will already be much easier: You only have to focus on the contract between both components, not the internal behavior.
You will be surprised how much easier it will be to test your original parent component.
“There Are Too Many Tests” – Feeling
If you are like most developers, your code grows organically. Or, what I find to be much more fitting: It spreads like cancer.
You don’t pay attention for 5 seconds and just like that, you produced yet another God object. If you find yourself blindly copy-pasting new tests at the bottom of your god object’s test class, this is where you should stop.
If you don’t even bother reading the existing tests, it’s very likely that the current object you are editing is not the right place to add whatever you wanted to put there. This is made visible by TDD. Try to watch out for those situations where you have to manipulate a complex fixture as a proxy to cause minor variations in the behavior of your object. Be self-aware and pay attention to your gut feeling, the next time you do this.
Instead, extract the logic to a dedicated component or class. Unit test this class directly. Now you only have to verify in your parent’s tests, that this component is called correctly.
A good rule of thumb is 2-3 variations of a desired behavior and class files that are no longer than 200-300 lines. When a class reached 400 lines of code, you should get very nervous. When I find myself writing more than 3 versions of tests for the same functionality, I know it’s time to extract something.
TDD is one of the practices we all know are useful, but many of us have problems really understanding the practical benefits. TDD makes it so much easier to write good code that will cause fewer headaches in the future.
For a long time, I have struggled to appreciate, why so many of my colleagues have problems adapting it. Maybe, these thoughts will convince more developers to try it. After all, it’s supposed to make your life easier.