TDD: Red Red Green
Testing gets easier the more you do it. I guess it’s like most things in that regard. For me my comfort level has risen as I have forced myself to practice the small steps that make up Test Driven Development. That was the next step in my TDD journey.
If you recall from my previous TDD blog post I was starting from nothing. No experience dong TDD or testing. My uncomfortableness was physical, a mini anxiety attack, a moment of doubt. Each step of the process caused me to pause and ponder on the bigger picture.
When I set out to learn TDD I started with trust in the process. I started with the thought that many people use TDD successfully every day. They see the red-green-refactor process and understand how it shapes their code and improves the projects. This has given me faith to believe that the TDD process is beneficial and useful. With this faith I set out to follow the same process, even if it, at first, felt backwards.
TDD is small steps, but not that small
I started small. I thought of the smallest test I could write and I began. My instinct was to start even smaller. My view of TDD was that it iterated at a pace of one character at a time. For me, the reality turned out to be different.
My tests came together quite well. I would create a data set; a slice of structs, with each struct having input and output data. Then simple iterating over each, running the input into my function I wanted to test, and comparing it’s output to the known output.
At this point I would become a little stressed. In my original reading around TDD this is where I would begin running the test, and seeing it fail. However, with Go, it doesn’t fail in an informative way. It’s a compiler error complaining that there is no function with that name. I suppose this is the appropriate “red” state. The expected failure. But to me this is unhelpful. TDD is intended to guide the shape of the software, to model it’s real world use. compiler errors are not real world use in a language like Go because they no longer are possible once a binary is sitting on a server answering requests.
Language types and TDD
I feel like this is one of the large differences between a compiled language and a dynamic (or scripting) language. The compiler is intended to break early. They dynamic languages are optimistic and try to execute for as long as the can. From a TDD perspective the dynamic language allows for smaller steps in the process.
This was a helpful realization to me. It allowed me to let go of the idea of tiny small steps. It is okay to setup more boilerplate code to prevent compiler errors. This lead to my very next step, which was to create my needed function in a
working form, as minimal as possible, to prevent compiler errors, and allow my
test to run. This resulted in a named function that returns the zero value of
the type it intends to return. For int
it would return a 0
.
This was my turning point. It gave me a moment of calm, where I can run my test, with my test data, against a real function. Almost all of the tests failed, but it was the testing the reality of my program, not the correctness of my code. For me this felt like progress. A step towards actually using TDD as a process to shape the production code.
TDD shapes the code
With this platform in place I began the traditional red-green-refactor cycle. I would tweak and add to the production code, writing bits and parts to massage the input into the desired output. All the while running the test between each edit.
At first this again put me in a place where I was getting a lot of compiler errors, but gradually I shifted towards longer moments of writing code. I was unconsciously avoiding these compiler errors, like an unruly boy avoiding the swift attention of the Nun’s ruler.
I would write more code, make more assumptions about what was needed to fulfill the contract that the function promises.
I got to a good place, where I would code out my assumptions and test them when they felt right. Most of the time the tests would run and let me know something needed changing; sometimes the compiler barked up an error. In both situations it guided me to my needed solution.