clj.orcery

Language, Expression and Design

Thursday

15

September 2016

develop, test, communicate

by Chris Zheng,

hara version 2.4.4 has been pushed to clojars with documentation added for hara.io.file and hara.test.

I want to focus a little bit on the test framework, having previously written a little bit concerning the initial release. I feel that this sits somewhere in between midje and clojure.test in terms of clarity and feature count. midje comes with it's own test runner as well as it's file watcher/reloading mechanism and countless other features. As much as I liked working with lein midje :autotest, I felt that the library could be pushed further.

What I was working on was a way to manage function metadata through tests. This resulted in midje-doc which evolved to hydrox. It was quite well recieved when it came out but I think people had better things to do than worry about documentation. Initially, a question was raised: Can a failed test be successfully reflected in the output of the generated docs?

My answer to that was: maybe. But only if I could somehow hook into midje and get it to play nicely. I asked around a little bit on the midje forums but got nowhere. I tried looking at the source but there was just so much there. I decided that midje just wasn't designed like that and adding that piece of functionality on would have been very difficult.

Because of the whole document generating thing, my entire workflow then revolved around midje - so much so that it did affect collaboration at certain stages. At any place I worked at, I was staunchly in the midje camp because I felt that both the syntax and tools were better. It led to a lot of discussion between collegues, mostly around why midje shouldn't be used on projects. I was definitely influenced.

For a while, clojure.test really sucked. However, with Clojure's current direction hell bent on giving Haskell a run for it's money in terms of stricter definitions on data as well as auto-generation of test data, we now have a glut of testing libraries and test runners - all of them are based around the clojure.test framework.

I believe in testing/specification as THE most important thing in developing awesome software. This is how I see the current development process as a whole:

  • (exploration) in-repl/in-editor development is already quite test-driven as the feedback cycle is very quick, the developer can constantly adjust the code and have immediate feedback.
  • (clarification) writing tests is like pouring concrete because the developer is then starting to write contracts against the code, allowing the testing framework to validate or invalidate the contracts going into the function.
  • (refinement) generative testing is kind of like load/stress testing where many different inputs are thrown at the function to see what breaks. At this stage, only slight adjustments are possible to fix a potential bug and to make the function more robust
  • (comprehension) spec is like doing the final polish where all the functions are buffed and made super clean and shiny. If major changes are made to the function, the spec author will have a hissy fit because it wasn't part of the initial agreement.

I see the testing and increasing the quality of software as the same thing. Therefore, testing is essential to the development process. Currently, I've listed 4 stages of 'development', each one leading to a better 'product' or 'library'. At each point, 'testing' or 'feedback' is used to ensure better quality of software. The testing tools and mindsets used in each phase are slightly different and it's the culmulation of all of the phases that give the best quality of product.

You can see how Clojure's development as a language has evolved in stages to these overarching themes:

  • Initially, the biggest pain point it solved was in-repl development. I was in fanboy mode for the longest time - learning emacs, watching videos, reading clojure source code... basically doing everything I could to ween myself off of the Java development process.
  • Then midje came along and there was a huge debate over whether we should do TDD or not do TDD and Rich's "TDD is like driving a car by bouncing off guard rails" comment.
  • Then we had typed-clojure, schema, test.check and all sorts of tools to provide auto-validation. Most people decided we need BOTH Hammock Time AND TDD.
  • Finally, clojure.spec is going to drop in version 1.9 and if we're not careful, it's going to lead to something more vindictive than the Haskell compiler.

So where do I stand on all of this?

I think that between the second stage (clarification) and the third stage (refinement), there needs to be another stage:

  • communication.

In this stage, the concrete has been poured and is drying off. However, there is still room for some hefty adjustments if need be. This is where code should be discussed and tentatively shared in order to get feedback on what to do next as well as consensus. By sharing code that we have written, we as developers are also forced to think of how the consumers of our code - our fellow developers would like to use it. And for this, documentation is critical.

Why do people like Macbooks? Because they are pretty effortless to set up and get going. Why do people prefer projects with documentation over projects without documentation? The same reason. We, as the library author - or the so called expert - should be able to give a very clear explanation as to what our creation does and how to best use it to do what we want it to do.

Of course, we can indeed jump back into the code and attempt to make it as correct as possible through the latter two phases. but I believe that by coding to communicate, instead of coding to be correct, we develop into much better programmers much more quickly, with much more joy and with less effort.