SPOILER ALERT: Yes.
Code without tests is such an old idea that in his book “Working Effectively with Legacy Code” (written in 2004!) Michael Feather defines “Legacy Code” as code that is not accompanied by unit tests.
This should be common knowledge by now in the software community, but I still am running into developers that do not see the incredible importance of unit testing.
Like many folks, I have long admired parts of “Uncle Bob” Martin’s work. While I disagree with him, sometimes strongly, on some of his views, there is no question that a meme he has long pushed is absolutely spot on: Checking in code without accompanying tests is unacceptable.
I’m not here advocating Martin’s larger solution (test-driven development), but rather acknowledging the rectitude of his fundamental position. To be fair, Martin is not the only person, nor even the most prominent, to advocate the value of checking in code and tests at the same time. Kent Beck, Michael Feathers, and many of the exponents behind continuous integration and DevOps have long articulated this position. But Martin has tirelessly championed it and, in large part because of his efforts, most diligent developers today reflexively understand the importance of writing tests that immediately exercise their new code.
J. Timothy King has a nice piece on the twelve benefits of writing unit tests first. Unfortunately, he seriously undermines his message by ending with this:
However, if you are one of the [coders who won’t give up code-first], one of those curmudgeon coders who would rather be right than to design good software, well, you truly have my pity.
Extending your pity to anyone who doesn’t agree with you isn’t exactly the most effective way to get your message across.
Consider Mr. T. He’s been pitying fools since the early 80’s, and the world is still awash in foolishness.
It’s too bad, because the message is an important one. The general adoption of unit testing is one of the most fundamental advances in software development in the last 5 to 7 years.
For anyone new out there or someone not familiar with Unit Tests, let’s first get a formal definition so that we are all on the same page. Kapeesh?
What is Unit Testing?
Essentially, a unit test is a method that instantiates a small portion of our application and verifies its behavior independently from other parts. A typical unit test contains 3 phases: First, it initializes a small piece of an application it wants to test (also known as the system under test, or SUT), then it applies some stimulus to the system under test (usually by calling a method on it), and finally, it observes the resulting behavior. If the observed behavior is consistent with the expectations, the unit test passes, otherwise, it fails, indicating that there is a problem somewhere in the system under test. These three unit test phases are also known as Arrange, Act and Assert, or simply AAA.
A unit test can verify different behavioral aspects of the system under test, but most likely it will fall into one of the following two categories: state-based or interaction-based. Verifying that the system under test produces correct results, or that its resulting state is correct, is called state-based unit testing, while verifying that it properly invokes certain methods is called interaction-based unit testing.
King presents a list of 12 specific ways adopting a test-first mentality has helped him write better code:
- Unit tests prove that your code actually works
- You get a low-level regression-test suite
- You can improve the design without breaking it
- It’s more fun to code with them than without
- They demonstrate concrete progress
- Unit tests are a form of sample code
- It forces you to plan before you code
- It reduces the cost of bugs
- It’s even better than code inspections
- It virtually eliminates coder’s block
- Unit tests make better designs
- It’s faster than writing code without tests
Even if you only agree with a quarter of the items on that list– and I’d say at least half of them are true in my experience– that is a huge step forward for software developers. You’ll get no argument from me on the overall importance of unit tests. I’ve increasingly come to believe that unit tests are so important that they should be a first-class language construct.
Obviously, writing testable code requires some discipline, concentration, and extra effort. But software development is a complex mental activity anyway, and we should always be careful, and avoid recklessly throwing together new code from the top of our heads.
As a reward, we’ll end up with clean, easy-to-maintain, loosely coupled, and reusable APIs, that won’t damage developers’ brains when they try to understand it. After all, the ultimate advantage of testable code is not only the testability itself, but the ability to easily understand, maintain and extend that code as well.
I encourage developers to see the value of unit testing; I urge them to get into the habit of writing structured tests alongside their code. That small change in mindset could eventually lead to bigger shifts like test-first development — but you have to crawl before you can sprint.
If you liked this post, please share it with others! That is the biggest compliment I could receive. Please subscribe if you are a technology enthusiast!
I have never been convinced. Yes, I have worked at sites where it was de-rigueur.
I think that, in large (>3) teams, and large web-based systems, it is probably required.
In my case, for the last 5 years I have been building a desktop based .Net/SQL Server system.
It is more than 250,000 LOC atm. It runs on only 3 sites, 24/7/365. Typically there are 5 – 10 users on at any one time on each site.
After 2+ years live the DB on the largest site is around 19GB (120+ tables).
Nearly all the code was written personally by me. I use my own lightweight ORM (loosely modelled on the Stack Overflow one). All ORM code is generated.
There are NO unit tests in the system. I do have bugs, but fairly few and far between now. The “bugs” I do get are almost never those that can be picked up by unit tests. Why? Because, the code is almost always doing what I wanted it to do. It is just that what I wanted it to do was incorrect.
Before go-live I had several contractors cutting code (to meet the tight schedule). I knew their code was crap, but I figured to get it running and cope with it. Which we did. I have since let go all but one of those developers (he is a telephony guru and indispensable).
And I have replaced nearly all the code they wrote. With a huge reduction in the overall LOC.
The remaining developer, Ken, I have been expounding my code philosophy, which is simple to say, but hard to practice:
– Code is NOT there for compilers or run-time engines or computers to understand.
– Code exists solely for people to understand (otherwise we can just do it in machine code)
– ALL code you write should instantly readable by an experienced developer. Ie, you.
– Instantly. If you scan a block of code, you should be able to see, instantly, exactly what that code is doing.
– If you look at a block of code you wrote yesterday, and you cant immediately see what is doing – you done it wrong.
Of course, I have a huge advantage here:
– nearly all the code was written by me
– a substantial portion is generated by a generator I wrote myself
– I have full control of design changes (in that I speak directly with users who want them)
– My code is very, very clear and readable.