A Quick Rant about Unit Testing (or the lack thereof)

I wrote 10,000 lines of test code for a project once. Was it worth it?

A Quick Rant about Unit Testing (or the lack thereof)
Photo by Joyce McCown / Unsplash

First, let's get the terminology out of the way.

Unit testing is when we write code specifically for testing small “units” of code, like functions. These tests are written in a way that they can test several different aspects or behaviors of a single function quickly and automatically, without any input from the user.

Test Driven Development

There is a concept in software development called “test-driven development”, where rather than writing your entire program first, and then writing tests for it, you actually write the tests as you go; often writing the tests before you write the code. This sounds like it would go slower but in the long run, it is an extremely efficient way to program.

Now, I don't particularly follow the principles of TDD religiously for every line of code that I write. That would just be a waste of time in my book. If I'm working on something that is just going to be a proof of concept and possibly thrown out? No point in wasting time writing tests for it. But, as soon as a proof of concept turns into a concrete plan, you can bet there will be tests written for it.

Unit Testing Forces You To Write Testable Code

As an added bonus unit testing, by its nature, encourages good program design by forcing you to write your code in a way that it is easily testable. This is particularly important when you start writing larger programs.

I wrote a compiler for a school project, and I had more lines of test code than I did actual program code. Not by just a few either - there were ten thousand lines of test code. Waste of time? Absolutely not. Anytime something went wrong with a test, I knew exactly what and where the problem was, and I was able to test every aspect of the compiler in seconds anytime at the push of a button.

I recently tried to introduce a testing framework to an application at work to help us prepare for some refactoring efforts. After two days, I had a single unit test ready to go, but there was so much configuration and specific tuning for that single test that it made the entire project impractical. Any tests written that way would be impossible to maintain.

So what makes a good test?

What Makes a Good Unit Test?

I'm a big fan of the book Clean Code by Robert C. Martin. The book has a whole chapter about testing, particularly about writing clean tests.

Martin states that clean (and I'd say just plain 'good' tests) follow five rules:

Fast - Tests should be fast, and they should run quickly. When tests run slow, you won't want to run them frequently, and then you won't find problems early enough to fix them easily.

Independent - Tests should not depend on each other. No test should rely on conditions or data that was produced by a previous test. This can lead to false positives/failures, and just in general makes them harder to maintain.

Repeatable - Tests should be repeatable in any environment. I should be able to run the tests the same way on my computer that you do on yours. I should be able to run them in a production environment, a QA environment, or perhaps most importantly, any computer I copy the code to.

Self Validating - The tests should have some kind of boolean output. Either they pass, or they fail. You should not have to read through log messages or files to tell whether your tests passed or not. If the tests aren't self-validating, then you risk having different interpretations of what a 'pass' or 'fail' is.

Timely - The tests need to be written in a timely fashion. The sooner you write your tests, the better. Writing tests right after, before, or during the time you are writing the actual application code allows you to write your tests "offensively", with all of the contexts of what you are doing. If you wait too long to write the tests, then you will be writing the tests "defensively", where you assume that the way the code works right now is the 'right' way, and your tests are just verifying the existing behavior.

Time Spent Testing Is Time Saved Later

Referring back to my compiler project I mentioned. That project took twelve weeks to complete. I was updating and adjusting tests the entire time. Sometimes, I was making adjustments to the code that I had written in my first week because there was a bug or a new feature that needed to be accounted for. When a test broke, I could immediately address it, instead of hoping my changes didn't break anything.

I have no idea how much time it saved me (tons I'm sure) but the unspoken benefit of having all of those tests was peace of mind. I could run all of the tests at the press of a button, they all finished within 3-4 seconds, and I knew that my compiler was working exactly as I expected it to.

If you are not in the habit of writing unit tests now, give it a try. It may take some discipline (writing new things is often more fun than writing tests) but I guarantee that the time you save and the confidence you gain about your code will be well worth the effort.