How TDD is wrong
I’m a pretty big test advocate. After all, it’s in my profile. So how can I say that TDD is wrong?
“Test-bitten script junky…” — opening of my profile
The “test-bitten” means I’ve been bitten by the automated testing bug. In a previous post, I mentioned having built the equivalent of a CI solution early in my career without knowing it. So how can I advocate such a heretical point of view?
The answer is subtle. To me, the benefit of automated testing is in having the testing and automating it.
- Nowhere do I see primary benefit in writing the test first.
- Nowhere do I see it better to write the test then write the code that solves the test.
Transitioning into my current role on the Spring team has moved me into the land of framework design. Building frameworks is quite different than end-user apps. That’s because the code you write is meant to serve not one but many. And in the case of Spring, we’re talking millions of developers (no exaggeration).
When serving this many people, you are building APIs, implementations of APIs, and ensuring that all kinds of scenarios don’t break that API. So I often have to start writing the API first. I try to create some fields. Add the accessors I need. Try to chain stuff together. And then I begin to write test cases that poke at it.
Several Spring projects also use Project Lombok. This is a really neat toolkit that I’ve known about for years, but only in the past two years have I truly come to appreciate it’s power. It makes it possible to stop writing getter/setters/equals/hashCode functions, customize visibility of accessors, define data classes, value classes, builders, and other stuff. All with a handful of easy-to-read annotations.
Trying to write a test case and then writing a Lombok-based class is ineffective. I’d rather create the class first and then use it’s pre-packaged structure in the unit test. Using Lombok in this way ensures a lot of very consistent structure that makes the overall API easier to consume. For example, it’s Builder annotation produces an API that looks like this:
This example is the builder I defined for the hypermedia format of Collection+JSON. It lets you lay out all the parts of a CollectionJson record, which is then serialized into JSON. The class behind it looks like this:
This class has several things, so let’s take it apart.
- @Data and @Value create setters and getters, along with equals, hashCode and toString methods. This is the core for a Java object, but not the bits directly needed for a builder.
- @Builder creates the fluent builder shown earlier with collectionJson the static function to create a new instance of this class.
- @JsonCreator is simply used to connect Jackson to this class when it comes to deserialization.
- And because the Item class is also a builder, I have item() as a convenience method to dive into this “sub” builder.
That’s it! This class is highly coherent because there is little “code” as in logical stuff being done. Instead, it’s mostly declarative. This class isn’t buried in logic, because it’s focused on defining a data model.
I can’t imagine noodling my way through this in a unit test and then trying to bend Lombok to support my test case. Like I said, it’s easier to define all the properties (version, href, links, and items), then flagging it as a Builder, Data, and Value class. Go into the unit test code, and start using it. Avoid much heartache.
And because I can still submit pull requests with gobs of test cases, achieving 100% coverage of things, I see little value in this “test first” approach advocated by TDD.
So…am I wrong? In what way? Jump in on the comment cause I’m dying to hear.