TDD in Clojure, Part 2 — The Detroit way

Sidharta Rezende
11 min readDec 12, 2021
Sorry, when I hear Detroit, RoboCop is the first thing that pops on my mind

This is the second part of the 3(?) installments on my studying of Clojure guided by TDD. In part 1 we saw how TDD cycle works, created our Clojure project, reviewed how the Quadratic formula works and created the first test and production code to calculate the discriminant part of the quadratic formula. If you have not read the first part yet, please take a look at it before going further.

In this part we will complete the Quadratic formula by using the Detroit School of TDD and then dive into some features of Clojure, such as Java Interop, dynamic typing and the use of schemas.

But before we begin, what exactly means Detroit School?

The Motor City School of TDD

What we call TDD today originated in Detroit, the biggest city of the midwestern state of Michigan, in the USA. Detroit is known as Motor City for the city's ties to the Auto industry. And TDD has it's roots connected to it.

In the late 1990’s, Kent Beck, the father of eXtreme Programming and rediscover of TDD worked for Chrysler as lead engineer for their Comprehensive Compensation System, when he first started promoting XP as an opposition to the waterfall model. He released his first book on TDD, Test Driven Design by Example, in 2000, and gathered some very known proponents for his methods, such as Uncle Bob, Martin Fowler and Ron Jeffries.

To understand how what came to be known as Detroit School of TDD, or Classist school differentiates from the other famous approach, the London School, I'd like first to review the concept of dependencies.

Please take a look at this image, with the mathematical notation of the Quadratic Formula:

The complete Quadratic formula

One thing jumps to the eye: That Delta greek letter (the triangle). So, whichever is this Quadratic Formula, we see it has a "Delta" as an important part of it.

Now, take a look at what Delta means:

Discriminant, or Delta

We have 2 formulas that share a connection between them. Delta (or Discriminant) exists independently of the Quadratic formula, but the later relies on the Discriminant to exist, thus we can say that the Quadratic formula depends upon the Discriminant.

For those of us that has Object Oriented Programming background such as myself, if we were talking about classes (but we are not), it could be represented this way:

The dependency flow goes from Discriminant to Quadratic

In our case, Functions are first class citizens, akin to Classes in OOP. Our function discriminant, that we just created in part 1, will be a dependency of the next function we will create, quadratic.

And that is what the Detroit School of TDD is about: Creating our code following the dependency flow, starting from the more independent module and going towards the modules that depends upon the others that are already created.

Back to coding

Now that we have an introduction to what the Detroit school is about, let's finish creating our implementation of the Quadratic Formula using it.

First, let's create a new Clojure namespace and our first test:

At line 7, we have a compilation error, as we can see in the image below:

Our first test doesn't even compile!

This test requires a function that we didn't create yet. In fact, we didn't even create the namespace. When I am using TDD on real life projects, I often skip this not-even-compile step, creating first a skeleton function with the right signature and returning some default value, but I thought it was good to do this in this example for clarity sake. Now let's create the function, but only to make the test compile and make sure it fails.

This #{0} thing is how we create a set in Clojure. A set is a collection that does not allows duplicates, which is very handy for our problem, since if the discriminant is zero, we will end up with the same value twice.

And after we start the REPL, load the test file and run the test (please re-read part 1 if you need a refresher on how to do this), we can see our test fails, so it is reliable.

Yes! Our test fails

Let's take a closer look at the details that clojure.test provides on the failing test:

FAIL in (quadratic-formula-test) (quadratic_test.clj:7)
given a=-1, b=1 and c=-12 when using quadratic formula should return a set containing -3 and 4
expected: (= #{4 -3} (quadratic-formula 1 -1 -12))
actual: (not (= #{4 -3} #{0}))

We expect that for

(quadratic-formula 1 -1 -12)

we get a set that contains -3 and 4

#{-3 4}

but instead we got a set that contains 0

#{0}

And that makes complete sense, since it is what we just defined in our skeleton implementation:

(ns quadratic.formula)

(defn quadratic-formula [a b c]
#{0}) ; <- HERE

Now let's work on this code in order to make the test pass:

Lines 6–9 defines aliases that I use later to write a code that reads very closely to how someone could describe the Quadratic Formula in english.

I think you agree that

#{(-> minus-b (+ sqrt-discriminant) (/ two-times-a))
(-> minus-b (- sqrt-discriminant) (/ two-times-a))}

sounds very similar to

Minus b plus or minus the square root of the discriminant, divided by 2 times a

Java Interop

One of the characteristics that make Clojure so special is that it runs on top of the JVM. This allows Clojure to leverage from the whole Java Ecosystem.

You may have noticed that I did some interop gymnastics in the last code, and I think it could be nice to dive into that.

The reason why I had to it do was because I decided earlier I'd like to use java.math.BigDecimals as both parameters and return values from my function.

Despite being a good opportunity to study java interop, I really like the way BigDecimals treat floating points. Since our roots will not always be integer numbers, I enjoy using BigDecimals for this job.

In fact, BigDecimals are so popular that they are very easy to define in Clojure. Since part 1, I have been writing code such as this one:

#(* 4M %1 %2)

The "M" after 4 means that it is a java.math.BigDecimal 4, not a Clojure Long.

We can validate this using the REPL:

4 is Long, 4M is a java.math.BigDecimal

Yeah, BigDecimals are great, but nothing is so good that it can't be made more complicated. Java 9 introduced the need to have a MathContext as parameter on the method sqrt. Let's hear directly from the cow's mouth, or, in this case, the javadoc, what is a MathContext:

[MathContext are] Immutable objects which encapsulate the context settings which describe certain rules for numerical operators, such as those implemented by the BigDecimal class.

This is why I had some extra work on this piece of code

(.sqrt discriminant (new MathContext 10))

That is the equivalent of java's

discriminant.sqrt(new MathContext(10))

I really like Clojure's syntax better.

Dynamic Types and plumatic/schema

If someone is asked what is Clojure, one common answer would be something like:

Clojure is a Lisp that runs on top of the JVM

Okay, we already discussed what run on top of the JVM means. Let's focus on what means that Clojure is a Lisp.

Lisp is one of the first programming languages, being around since the 1950’s. Uncle Bob summarizes Lisp's resilience on this tweet:

Lisp is so relevant that up until a few years ago it was used as MIT introductory language for Computer Science and Electronic Engineering graduation. The text-book used in the course is considered one the most important books on Computer Science, Structure and Interpretation of Computer Programs, by Abelson and Sussman. If Clojure for the Brave and True, which I recommended in part 1 is the goto book to get the basics of Clojure, this book will let you understand the philosophy behind every bit of the language. It is also available for free online!

One of Lisp's main characteristics is that it is a dynamic typed language. It means that its types are not evaluated at compile time.

This can be troublesome in the code we just wrote, for example what can happen if in the lines

(.sqrt discriminant (new MathContext 10))

and

(.negate b)

the values provided are not java.math.BigDecimal?

Let's run in the REPL and find out

At line 8 we tried to call negate on a java.lang.Long, and the method doesn't exist. It exists only in java.math.BigDecimal

By the way, we could have used

(* -1 b)

Instead of calling the negate method, but… what would be the fun?

So, how can we still use everything we gain with JVM classes, and also keep being a dynamic typed Lisp? One way to do so is to protect our function at run time, so if any argument is not of the expected type, the function is not executed. Plumatic/schema is a very popular library in Clojure that allows that.

First, let's add it as a dependency of our project, by adding prismatic/schema as a new dependency on project.clj

(defproject quadratic "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.10.3"]
[prismatic/schema "1.2.0"]] ; <- HERE
:repl-options {:init-ns quadratic.core})

Think about Schema as a runtime validation that runs against our function arguments. It does much more, and to understand in detail I recommend reading it’s documentation on the project’s webpage. But for now, we will use only to make sure that the parameters sent to our functions are, in fact, of the type java.math.BigDecimal.

First we will write a new test, to check the discriminant against "a" as a Long:

This test brings something new. Validation of schema brings an overhead to processing, and to avoid this overhead in production environment when the highest performance is a must, the validation may be disabled. In fact, is disabled by default.

We can activate it before executing our tests using

(s/set-fn-validation! true)

and deactivate it after by using

(s/set-fn-validation! false)

Clojure.test provides a way to execute functions before and after tests run by using fixtures. I made use of this to activate and deactivate schema validation:

(defn schema-validation [f]
(s/set-fn-validation! true)
(f)
(s/set-fn-validation! false))
(use-fixtures :once schema-validation)

When executed, the tests fail, since we haven't yet added schema validation to our function:

Let's take a closer look at the details provided by the exception

FAIL in (discriminant-test) (discriminant_test.clj:15)
given a is not a BigDecimal should throw an exception
expected: (thrown? ExceptionInfo (discriminant 1 -1M -12M))
actual: nil

We expected an exception taking place if "a" was not a BigDecimal, but nothing happened. Let's correct that changing our function discriminant to make this validation

Take a look at line 4. Instead of the regular defn macro, we are using s/defn, that is defined in schema.core, that we imported at line 2. This macro allows us to use :- to verify if the parameter sent complies to the type we want.

After this change, our test should pass:

Uncle Bob's 3 laws of TDD

You may have noticed that so far I only changed our code to validate parameter "a" from discriminant function. We need to do the same for "b" and "c", and also for all the parameters on the quadratic-formula function.

Since we already tested it on "a", why can't us only add

:- BigDecimal

to the other parameters?

This is where we enter the less popular aspects of Test Driven Development. It is all about discipline. Making changes ahead of the tests violates what came to be known as The 3 laws of TDD.

1-You must write a failing test before you write any production code.

2-You must not write more of a test than is sufficient to fail, or fail to compile.

3-You must not write more production code than is sufficient to make the currently failing test pass.

In my opinion, it is about making sure to have tests covering all possible aspects of your code that, in the future, may diverge. It is not to be considered an early optimization, since generalizing a behavior can be considerate what is described by David Thomas and Andrew Hunt as Programming by Coincidence in their wonderful book The Pragmatic Programmer (Pearson, 1999–2019).

We should not rely on a single test and infer the same result to other parts of code. A good test suite is, among other qualities, comprehensive.

So now I'll write, Test by test, Production Code by Production Code, all the schemas validations for "a", "b" and "c". After a few minutes, I ended up with:

1 A new Clojure namespace to keep the fixture in one place, so I can reuse on both tests

2Tests for discriminant, including independent validations for "a", "b" and "c" not been BigDecimals. Please note in line 7 the use of the fixture that now lies in another namespace.

3 Discriminant production code, making use of schema.core macros s/defn and :-

4Tests for quadratic-formula, including independent validations for “a”, “b” and “c” not been BigDecimals. It also uses the fixture.

5 quadratic-formula production code, making use of schema.core macros s/defn and :-

The complete source code is available at

Final words and what is next

That one was a bit longer than I first expected, but I really enjoyed going through the definition of what the Detroit School of TDD is about, Java Interop, Schema.core and fixtures.

In the next and (hopefully) final part of this series I'll start again from the ground using the London School of TDD, discussing the limits of TDD and using schemas for more than only validating types! Looking forward to it!

I started this article with one pop quote involving Detroit, and will give my goodbyes with other one:

Update: Read now

Part 3 is available

References

--

--

Sidharta Rezende

Skatista de downhill, amante da vida e Engenheiro de Software