TDD is the Unagile, Manual way to test your code

MetaDreamy
5 min readApr 11, 2018

--

This article is for computer programmers who already know what TDD is, what types are, and what static type checking is. It is intended for people who already know what the supposed pros and cons are of using TDD and of using static type checking. Since I expect you already know these things, I will write in a more philosophical approach, that is intended to be food for thought. The views expressed in this article will be presented as absolutes. This helps the food for thought to be more engaging and memorable as such. I don’t actually consider these viewpoints to be absolutes as I tend to change my mind about these topics over time. I am writing about these things because they interest me, and exploring particular viewpoints helps me clarify my own thoughts long after the words are written. Of course whether or not any particular programmer should use TDD, how much of the time they should use it, and whether or not they should use static type checking should probably be decided on the practicalities of their particular circumstance, which will often override what would be the logical choice if there were no such particular circumstances.

There are many fans of static type checking, and many fans of TDD. While they are by no means mutually exclusive, people who strongly promote TDD tend to think that types are less necessary and helpful. While people who strongly promote static type checking tend to think TDD is less necessary and helpful.

Really though, TDD is a way of telling the computer what behavior “to expect from your code”, before you even write the code. You are placing constraints on what your code is allowed to do. But conceptually, that is also what static type checking does. Types tell the compiler/checker what “to expect from your code”. They place constraints on what is considered valid. People often point out that tests should test behavior not types. But ultimately they are both different ways of using code to express limits on what is considered valid code in a given context, and both are used by the computer to automatically check to see if those constraints are honored.

When we write a program, we are actually writing our own language. The classes, functions, and variables we create form the rules or constraints of this language and how it can be used. We then use that language to express desired program behavior. Checked types and tests also add to the rules and constraints of this language, but types do it more elegantly, and in a truer spirit to being a programmer.

The most important principle of good design is something many people know as the DRY principle (don’t repeat yourself). This is a well known principle, and while there are valid debates about to what extent your code should be DRY, the principal does get to the heart of what a program is. A program is a series of instructions being run by an automation machine. To the extent your code is not DRY, you are not taking full advantage of the fact that you have a computer to do the repetitive stuff for you, and that is what is wrong with TDD.

When you always write tests to test your code, you have to keep writing more of them every time you add to your code. If you think of the tests as a way of building a language by expressing constraints on what your code can do, it becomes clear this is not a good way to make an elegant, well designed language. It is mostly not reusable. Instead of reusing your language, you just keep inventing new parts of it to express every new thing you want to say. It’s like if you were trying to have a conversation with a friend, but every time you wanted to express something new, you had to first invent new aspects of your language just to say it!

TDD is not automated testing, it is manual testing. You have to manually create and maintain new tests for every little thing you add to or change in the code. Statically checked type constraints are true automated testing because writing them is just a natural part of writing the code. They are part of the code itself, instead of a tacked on constraint placed off to the side in a separate file. When you use static type constraints, you are actually programming the compiler, you are a true programmer.

By placing the constraints in the code, you are keeping all the related information together, which helps you spot and fix places where type constraints don’t make sense. When you write tests, you artificially separate constraints from the code, making it harder to spot flaws, and making the overall code more convoluted and complex, thereby inviting more bugs to creep in. Using type constraints is a better fit for writing all your code in a very straightforward way, where you just directly express the features or desired functionality in the code. This is more lean. By keeping your design cohesive and lightweight, this makes your code simpler and more straightforward, making it harder for bugs to hide and stay hidden. It also makes it easier for you to refactor and redesign as needed. People who promote TDD often say the main point of it is to allow you to safely refactor. But actually it hampers refactoring. Who wants to bother refactoring code if you not only then have to refactor all the dependent code, but also all the artificially existing dependent test code? By spending less time writing and maintaining tests, you will have more time to look into and fix known bugs and to address underlying issues in the code and database design that lead to bugs.

People often say TDD helps generate helpful documentation in the form of tests. But by saying that those people are often contradicting their own opinions about documentation. Those same people often believe that we should strive make our code self documenting as much as possible, and only add documentation when we can’t achieve this or need to explain the “why” of the code. If you need to write tests just to make your code understandable, you probably need to redesign the code.

Instead of looking to manually maintained tests as the solution to our problems, we would be better served by continued exploration into automated, automated testing, combined with intuitive and straightforward coding techniques. Things like static code analysis, static type checking, code style validation, automatic code complexity analysis, automatically generated test cases that are validated by automatically running them as tests with runtime contracts that exist as part of the code, and new languages, frameworks, libraries, and tools, that merge these concepts together in more intuitive ways.

TDD is the manual way to do test driven development (MTDD). If you want to do automated test driven development (ATDD), you should learn to write your tests first by writing constraints directly into the language of your code before you nail down all the details of implementation.

Supplementary information:

I realize using TDD and writing tests are not the same thing, but I focused on TDD because if I don’t, people will say the reason I don’t believe more in tests is because I am not aware of or thinking of using them in the context of TDD. I can assure you I have done plenty of TDD, have done extensive research on doing it well, and the reasons for and benefits of doing it.

--

--