The art of unit testing and Test-driven Development (TDD) is considered obscure to some but is incredibly useful to the practie of software development. We all know that testing of some kind is important to producing quality software, yet our actions do not reflect this knowledge.
Are tests necessary?
Some of us test the features we’re working on manually before sending the code along for acceptance testing. While this type of manual testing is important when implementing a new feature, the lack of a corresponding automated test is problematic for future maintenance because it is easier for regressions to occur. It can be argued there are some cases where lack of automated tests is okay:
- There are lots of very experienced and skilled developers
- The code is already written in a very maintainable and clean way
- The code is throwaway code
- We want to frontload development at the cost of future development, as in the case of a simple MVP.
The main caveat to all of the above scenarios is they focus on the now rather than the future. While we don’t want to go yak-shaving by attempting to future proof everything, there is a concern that if we’re always focusing on the now, then we may never focus on the future.
Why don’t we write tests?
For those who consider tests important, why is it that we do not write tests? There are a number of specific reasons for every scenario, but they can be boiled down to a few categories:
If testing is not part of the company culture, developers are rarely explicitly given time to write tests. It’s also common for developers to feel bogged down by the amount of work they have to do–adding writing automated tests would just be additional work. Especially when automated testing is something new for a developer, work can take several times longer to complete.
Writing automated tests and writing testable code is a discipline unto itself. It’s no wonder that sometimes we don’t write tests because we don’t know how to. We are ignorant of how to do it. Even more difficult is adding tests to legacy code.
Sometimes management disincentivizes testing. Imagine that you were rewarded for every bug you fix. This would create an incentive for you to avoid writing automated tests (this might allow more regression-related bugs through). Praising someone for fighting a fire in production, when it was preventable through automated testing is another kid of disincentive. Finally, ignoring the tests that people write and not creating a testing culture can be a disincentive. Those that are externally motivated may not feel rewarded for something perceived as “extra” work.
How do we start writing more tests?
Although I don’t believe in 100% coverage, at the minimum, I try to write tests for important bits of functionality, for things I’d be spending a lot of time manually testing, or code that I expect to be regression-sensitive. I don’t always want to write tests for the reasons stated above, and I have some thoughts that might help people that feel the same way.
I’ve always gotten good performance reviews. While this does not mean I’m a good developer, it should mean that I get my work done on time and as expected. Still, when I compare my productivity and ability now compared to when I first started in my career, it is worlds apart. I know that the code I wrote as a junior was less secure, less performant, had more issues, and was less maintainable. The one constant between then and now is that I’ve always felt like I had a lot of work to do. My experience has informed several opinions I hold.
No matter how much work I have, there is a limited amount I can do. I remember always being busy, no matter how much work I was actually finishing. This tells me I could’ve finished fewer tasks (especially when I’ve worked long hours), or put more quality time into fewer tasks without much change in my perceived performance.
The scope of my code has increased over time, but the time to implement things hasn’t drastically changed. As I’ve improved over my career, the time to implement a feature such as a web form hasn’t changed much. What’s changed is the quality of the feature I deliver. For example, things like validation, santization, security, performance, and, yes, testing are things I include in features by default.
It’s uncommon for management to explicitly give time to test. I’ve rarely if ever been alotted time for testing. Rather than being because management does not care about quality, I think this is more because management expects me to take care of quality in the ways I see fit. It would be like management giving me more time to make something secure. I think it’s expected that I make things secure by default unless otherwise directed to totally disregard security, and I consider testing to be a lot like that.
There are two things I’ve done to address my own ignorance with writing
tests. Firstly, while generally I don’t use TDD in
my daily work, I think it is incredibly useful to have experience with it,
and know how to do it, in order to gain a better understanding of how to
write testable code. Where I tend to use TDD is where I am very clear about
how I want the interface for a feature to look when it is done. For example,
I often use TDD with code that does string parsing. There is usually a
parse which returns a string, and the inputs and outputs are
well defined. I also prefer to use TDD for programming challenges like
Advent of Code.
Secondly, I always recommend
Working Effectively with Legacy Code
as two must-read books. They’ve both helped me to learn how to add
tests to existing code.
It’s hard to deal with systemic disincentives to testing. At best, the disincentives are incidental. At worst, they may be designed to make middle-management look good, but I haven’t ever encountered these dark management patterns. Most of what I have seen has been positive attitudes towards fire-fighting and a lack of a testing culture. Although systemic incentivization may be something difficult for an individaul to address, I try to adopt some internal incentives for writing tests. One such incentive is that if I break something, I will probably get blamed for it or have to fix it. An existing test makes this easier. Even if I am not the one who broke it, sometimes the original developer will be asked to fix an issue, even if someone else broke it. Another incentive is that a test serves as documentation about how something should work and makes returning to a project easier.
I’ve never been in an organization that placed a heavy emphasis on testing. Despite this, I have become a proponent of automated testing. I think that 100% code coverage is impractical, but there must exist some idyllic waypoint between TDD and 100% coverage and having zero tests. There are only a few main reasons why people don’t test, and there are clear ways to address those reasons. Although it is hard to enact systemic change as a front-line developer, there are a number of things that an individual can do to start writing automated tests. Whether we start writing more tests is predicated on whether we even agree that automated tests are important, but I think we can all agree that testing is an important part of creating quality software.