While we would like everything to be perfect – nothing ever is.
While we might like to do Test Driven Development – sometimes we take the shortcut. Why? Because of time, application, lack of practice or knowledge. Sometimes it’s because we’re just scared it will send the wrong signal to those we work with. There may be concern that either arguing for or against something as impactful as TDD can cause friction.
Sometimes even mentioning trigger phrases like “Test Driven Development” or “Trunk Based Development” or “Agile” or even “Waterfall” can provoke reactions which we don’t expect from people around us.
Why? Because not everyone thinks that things need to change in the current process. Many people are not concerned with the way of working and perhaps think “if it ain’t broke, don’t fix it”.
Test-Driven Development is undoubtedly a big step, and it can seem insurmountable. It takes time to learn it and a lifetime to master it, and speaking as someone who has come to it later on, while I find it remarkable, I still find it hard to put it into practice. I recognise that there are things that we can do which don’t necessarily mean we need to write a test first.
I like to call this approach more about being aligned with testing. This is software development which doesn’t force us to consider our class or function design too carefully in the first instance but allows us to retrofit our tests easily to validate our design later.
Having said that, if you want to write your tests first, that is also great. It will also bring you all the benefits of test-driven development immediately, not to mention excellent code coverage, better intrinsic design and more thoughtful code architecture. TDD first if you can. If not then Test-Aligned Development might be a good alternative. So how does it work?
I suggest we think about it a bit like this. Test-Aligned Development means you think about how your code could be testable in the near future.
Low Code Coverage is a Smell
For example, I recently worked on a project where large areas of code were deemed untestable. Why? Because lots of essential business logic was hidden in private functions. This had not been called out sufficiently by those working with the team. While standards were in place for code coverage, they were not enforced. So I started to poke around, trying to understand why the code coverage was so low and how we could potentially fix it.
When I discovered large private methods, I realised we needed to refactor to make these methods testable. Refactoring immediately brought public methods in subclasses and therefore tests could be written easily.
However, extracting many private methods to new (or expanded existing) classes is difficult and slow work. It would have been better to prevent this situation than fix or retrofit it. If someone had seen the lack of coverage as a smell then this could have been avoided.
Therefore I propose we think about how you will test your code.
If you can’t quickly write a test for the existing code (and your coverage is low < 60%), refactor your existing code for testing instead of writing more new code. If you can’t do either of those right now – then make sure you flag this up as an area of concern.
Large areas of untested business logic are a serious concern. If you really can’t spend the time writing tests now, think about how to test this code in the future.
Deployment Complexity Should Be Investigated
If you application hard to build and deploy? Can you do it automatically? How often does this process break?
Look for dependencies towards libraries, modules, other functions, globals, files and database connections, APIs and network or cloud resources. Ask yourself, could you test this code quickly? How much of your business logic is buried in the code you create?
Many dependencies, coupled with core business logic, are sure signs that the design you’ve created is too interdependent and probably needs refactoring. New elements may need to be brought in. Naming must be considered carefully, and attention must be paid to testing.
Ideally, any complex subsystem similar should be split out into its own domain. Consider how this may be handled by the team or teams that need to look after it.
Any business logic must be guarded well by tests which exercise that business logic. Think carefully about the implications of not testing these vital elements.
Testing Complexity is a Sign
If you cannot easily automate your testing, then this can also be a sign that your architecture is insufficiently robust. Code or library dependencies, subtleties in configuration, excessive amounts of environmental setup and toil – all of these work against automation (or at least make it hard). If you cannot easily automate your testing using either off the shelf CI/CD tools and testing frameworks, then ask yourself why this is such a special case? Most likely the code you’re writing is not that special and well catered for by existing frameworks. If you find yourself writing a lot of bespoke scripting for testing automation – should you be?
Test-Aligned Development as an Alternative
If you can’t commit to TDD, then perhaps you can try TAD.
Start to think about what you are doing every day when it comes:
- Adding more code (think: how can we test this? how is our coverage?)
- Deployment complexity (think: how can we simplify this?)
- Automating testing (think: how can we save time?)
By paying attention to the complexity, testability and deployability of our software, we can avoid a big crunch refactoring effort and improve the overall quality without necessarily having to go all out on a TDD effort.
TDD may not be achievable and anyway in itself won’t fix problems you might see with deployabliity. By choosing a proactive approach to quality you can make small everyday steps towards higher software quality.