Isn’t Testing Obvious?

I am sometimes surprised by the rants people feel the need to go on on the internet. Then I am reminded that they are fuelled by people, and people are often idiots (citation: I am people). So, let’s look at a topic which comes up in job interviews, and LinkedIn rants. No, not generative AI. Far more fundamental than that. Testing practises.

When it comes to writing good code, we get very caught up in the fun of the algorithms and integrations. We have in the back of our minds that maybe we need to prove this thing works, but we write good code. Of course it will work. First time even.

I don’t even know how many times that attitude has found me refactoring.

It is great if you are prototyping, because then the tests are just “run it and see”. As soon as you add any complexity to your user inputs, and expected outputs, you are going to struggle though. It gets even worse if you are refactoring to add a feature, or deal with technical debt. If you have touched half the file, then how do you prove that you haven’t added any new bugs? How do you know that your well intentioned changes aren’t going to cause a memory leak for that one customer who hasn’t changed their integration in ten years, and expects not just backwards compatibility, but bugwards compatibility? Of course that never happened to me. I’m awesome, and I write perfect code covering all the use cases perfectly. Yeah… anyone worth their salt knows full well how easy it is to get this stuff wrong.

So, why is it so hard for us to build the proof system first? Why is it that outside of the TDD interview we forget to write the tests until we have something to test? Personally I have two sides to this answer. On the one hand, it is far more exciting to write the feature. The tests are usually pretty straightforward, yes they’re in the back of my mind, but they have an input and an output, and then they’re done. On the other hand, unless you did a really in depth software engineering degree, or learnt to code using TDD, you have not wired your brain to think test-first. You have spent years with “hacky” solutions. They were even good enough, and did really well on the sample input you were given. Unfortunately, your code is a mess, and every time you get told (or remember) to add tests you end up refactoring in ways you never anticipated.

The benefits of testing on the go are so many, you would think we would have TDD ingrained in us. We would consider it the most obvious way of writing code. Even the only way of writing code. I can promise you, there is a lot of code which you exercise every day which has never been systematically tested. So apparently, despite the best practice being clear, testing isn’t obvious. This is held up time and again when people submit conference talks about testing. When people rant on LinkedIn, BlueSky, X, or who knows where else. Why do we have to push for testing? Why are we so enamoured with shift-left thinking? Why is it novel to work in an agile environment where testing is shifted left, and developers own software quality? My personal favourite question, why is it so difficult to set up and use frontend testing frameworks?

I have written just enough Angular and React to know about Jest and Jasmine. I also fought them enough that at one point I figured I could write a novel just with the bad puns I would make to cope with having to use these frameworks. I’ll have to complete that at some point, just for the laughs. I’m sure in the five years (or more) since I tried to use them, they have improved, but I continue to be baffled by how hard we make our lives as developers by not having a test-first mindset. I did once bow out of an interview process after being given an assignment in TypeScript and when I asked about the preferred testing framework getting a rather incredulous response. So, clearly the people doing the ranting are on to something. I just wish I knew where to find the source of the issue.

My favourite benefit of test-first thinking is the impact it has on the code people write. Testable code is also readable code. Inputs and outputs are clearly defined, because they are used. There are fewer single character variable names, because if you have to know what it is doing in the test, you are going to want it to be well named. Classes and objects have proper constructors and initialisers – and IoC frameworks don’t get in the way of that. Spring and SpringBoot systems are very easy to write in such a way that they can be tested. The frameworks even support testing natively. Code written to be testable is also a lot leaner. You are aware of exactly the problem you are solving, you have a test for it, and that is the functionality you add. No extra methods which have some interesting side effects.

If you fully immerse yourself in test driven development you even get really good at refactoring. You don’t start adding functionality until you have tests covering all the existing functionality. This means that when you make a change, and you accidentally change something already existing, you have a safety net of failing tests to point out where you messed up. You refactor more carefully, you use stronger typing, and you are fully aware of the consequences of a YOLO push.

Sadly, we do not live in a world where developers like writing tests. More of them need to join me in the accidental QA role, and break things on a regular basis. So, how do we start making people think about it? Coverage reports are a good start. They don’t solve the worlds problems, because the report isn’t going to actually tell you if the tests are meaningful, but they can trigger a developer to remember adding the tests. The number of times I have pointed out that something is not tested at all, and then had the second revision of the code include comments like “fixes a bug caught in testing” has always driven my principle that I will neither ship nor review untested code. I use the coverage report to make that a quick process, and if the tests exist, then I don’t mind working through them to verify that they are meaningful. With the caveat that not all code needs to be covered, Kotlin data classes shouldn’t include business logic, and so may not be meaningful to test. They should also be defined as the business logic requires them, rather than before, and so be transitively covered.

So, yes. I may not always quite get the test-first thinking right myself. At least I know it is useful, and can enumerate why. If you don’t think you should have to write tests, I don’t want to work with you. If you don’t know that you should write tests, but are willing to give it a go after some education, then there is hope for you as a developer. Testing should not be something we rant about this often. We should be talking about the nuances of mutation testing, integration testing, the testing pyramid, and other such fascinating topics. Maybe we will get there.