Test Driven Development is nothing new, by far, and I’m not a beginner any more either. Nevertheless, it took me about 15 years to try it out. Whaddaya think, you may wonder? I shall tell you.
To put this into a bit of context, let me quickly summarize my career as a developer with focus on the testing aspect.
The company I work at has no culture when it comes to automated testing. For the longest time, software was validated and approved manually by a person. Some of that still runs today and it’s stable, too. When I mention that it’s written in the old and complicated language of C++, some may wonder how.
Although we didn’t do any automated testing, we were, and to some extend still are, unit testing our code by stepping through it in the debugger. While the result in software quality may be equally good, no matter the technique, it certainly isn’t reproducible.
It took a long time for unit testing to become a thing, mostly triggered by growing experience and the tools to easily support it. It wasn’t until I was tasked to architect and design a new application that I really started writing tests. At that time, the Grails framework was the tool of choice and it promoted unit and integration testing with its own easy integration into the tooling. I’m somewhat proud that a very large percentage of that application tests itself automatically. Grails basically mandates that you verify every corner of your code or at some point, when the application has become huge and complex, you don’t dare changing simple things like variable names or removing a class’ field because of how Grails compiles the code – or doesn’t, due to the dynamic nature of the Groovy language. Runtime errors are the result of spotty test coverage.
(Stay away from Grails, seriously)
I tried to apply those experiences to the older C++ products, at least for the new parts that were created, but I had to learn it the hard way that the software just doesn’t support testing easily. If not designed properly, testing becomes a chore or even impossible.
Fast forward another few years, a bit of personnel shuffling and I’m teamed up with someone that is very insistent on everything being tested. Suddenly, I had another proponent of what I have come to require for myself. This new project we worked on is tested as completely as it can be given the time constraints and knowledge of the platform and language. We both had to adapt to a new platform, the Mac (although I have some history) and a new language, Swift.
I’m coming to the end of this (origin) story, just one more paragraph, bear with me.
Now, the big hot thing in town is Spring Boot paired with Kotlin. The advantage we have here is that the Java world has very good and mature testing tools and libraries. And that’s where it finally clicked. Things I’ve learned indirectly over the past few years using Grails now have been refined and make perfect sense. Where Grails seems to be rather sloppy, at least that’s my perception of how I learned to test using this framework, Spring Boot feels much cleaner.
And that’s when I started doing things backward, so to speak. First write the test against an unimplemented interface and then add the actual implementation. This does provide some benefits. It’s nothing somebody else hasn’t likely already written down somewhere. However, I was a sceptic at first, not being sure what it will do for me writing tests for something that doesn’t yet exist. So, here’s my personal thought process.
First, it makes me think about the interface more. What’s the input, what’s the output? What kind of data do I have available and what kind of data am I likely to require in the class/method? But not only that. I also tend do think more about the API documentation. What is my expectation when I call this method in general? What is my expectation when I call it with some edge case input? Does it modify anything? What are the preconditions that are checked before actual work is being done? And how are errors reported? I feel like looking from the outside first makes me take more time to get the interface right and the documentation on point. There’s nothing worse than looking at some Javadoc (or whatever) and not have your questions answered.
Second, starting with the tests helps me design my classes and dependencies in a way that makes them testable. That’s the biggest problem of the old C++ codebase. Because many dependencies are hardwired, it’s almost impossible to test something in isolation. Even Grails, with its explicit support for unit and integration testing, doesn’t really enforce you to think about this. Yes, there’s some basic software design that is encouraged. But by providing a fully configured and functional Spring context in integration test mode with all bells and whistles, there is no need to think about testability. You have all your services with database access ready to go. The whole chain, no matter how deep.
Spring Boot is stricter and separates the non-database beans from the database support. I won’t go into detail here because I have another article planned on exactly this topic. To put it short: by not having everything work as easily as is the case with Grails, I began to understand the importance of designing software components so that they are testable and how to do it. Now, I only test exactly one component. I have finally grasped why to mock classes and interfaces. It’s so trivial and yet so powerful and helpful. Why would I need a whole database layer when I can just fake the access and return static data? Why would I need to call another web service when I can just return static data and the HTTP status code of my choice? This is much easier than trying to force error conditions on remote web services. Imagine testing the controller of a REST application. Such a thing can require a service which requires a database and maybe another service that itself is calling out to a web service. That’s quite the chain. But it’s not necessary if everything is designed properly.
It was eye-opening, and this is what I meant by “it finally clicked”.
Lastly, and that may sound stupid, but by writing the tests at the beginning I don’t have to do that after implementing the class/method. I mean, as much as I find automated tests useful, I don’t like writing them. When I’m done authoring my code I want to see it running, immediately. I don’t want to write tests. Thus, tests first make me happier at the end.
Some last words on the topic.
Although I am raving about unit testing and the advantages of mocking objects, there’s still the need for integration tests, especially in communication scenarios with external services through an API. An application’s internals may be completely unit tested, but you must make sure, e.g. when writing an SDK for your own fancy-schmancy web service, that communication between the two is working correctly. The internals of the SDK can be unit tested for basic and corner cases. That’s not enough though, as we have learned the hard way . Software like that also needs to be tested against a live system.
I know that Grails is based on Spring and starting with Grails 3 the basis is Spring Boot. I don’t know if testing has changed much from v2 to v3, but I never really saw the need for mocking as I do now. And that’s because, as mentioned earlier, the combination of non-database and database functionality in integration tests. It makes things so convenient, too convenient one might say, that it is very tempting to not think too much about proper design.