PurelyFunctional.tv Newsletter 325: Tip: don’t use a protocol to make testing easier

Clojure Tip 💡

don’t use a protocol to make testing easier

Warning ⚠️: this may be a controversial tip.

People often use protocols to make testing easier. For instance, they might make a protocol that represents the database. In production, they use a type that implements the protocol that hits the database. But for testing, they use a type that implements the same protocol that uses an in-memory version. Because they are programming to the methods of the protocol, the switch is easy.

I’ve done this pattern before myself. And I regret it. It’s not worth it.

Let me explain.

Until recently, protocols require you to create types. (You can now do dispatch using only metadata, but that is not commonly used in this pattern.) I rarely create new types, so I had to create types just to implement this pattern. They served no production purpose. They were just there to make testing easier.

Further, the protocols themselves only grew with time. As I accessed the database in more and more ways, I had to add new protocol methods. I had to figure out a common denominator for accessing the database and for implementing the database fake. While this has the potential to be a useful exercise, it more often led to poor abstraction. There was rarely any rhyme or reason for the methods. They were just what I needed at the time.

Some might argue that I was just not designing my protocol correctly. And I might agree. But this puts the cart before the horse. Why unnecessarily introduce a place where design is so important?

Some would argue that this pattern allows for extension to other databases. But that extension has never happened. If it did happen, would it fit the protocol I have? Dreams of switching databases as easily as re-implementing a handful of well-chosen methods are unrealistic. And if this is the argument, the razor of YAGNI would shave it off pretty quickly. If I ever need to extend to other databases, I’ll implement it then.

Some would argue that this makes your tests harder to write. And they are right. But all it does is shift the burden. Your tests are easier to write, but your production code is harder to write. That is not better.

In the end, protocols are just a layer of indirection. And there is already plenty of indirection between your code and the functions it calls. Define your database access using regular defns. Code as normal. When you go to test, I recommend using with-redefs to install fakes for the functions you need. Writing simple fakes is much easier than implementing a feature-complete version of a database (or whatever system you’re swapping out with the protocol pattern). If you can reuse the fake, set up a test fixture that calls with-redefs.

This approach is straightforward. No testing code affects your production code. There is no unnecessary indirection. And functions are easy to grow and evolve with time. Don’t follow the protocol-for-testing pattern. It’s not worth it.

Scientific debugging goes over commands for viewing the current namespace, understanding what’s in it, requiring new namespaces, changing namespaces, and removing namespaces. And be sure to catch me messing up the current namespace and having to use the commands we learn to get out of that situation.

Interactive documentation talks about tools for learning about what functions and variables are available in the loaded namespaces.

I’ve been getting lots of great advice from people watching it. Thanks! This is going to be a much better course because of it. If you watch it, please let me know how it can improve.

There are already 7.25 hours of video. The recording is starting to wind down. I’ve got a couple more lessons I’d like to record. If there is something in there you would like that you don’t see, now is your chance to ask for it.

Of course, members of PurelyFunctional.tv get the course as part of their membership for no extra cost. Just another benefit of being a member.

Brain skill 😎

Embrace “calendar time”.

Learning a big skill requires two kinds of time. One is the time you spend actively learning. This is usually measured in hours. Let’s call it “clock time”. But the other is time your brain spends making new connections and reorganizing to fit the patterns you need. This time is measured in weeks or months, and I call this “calendar time”.

You can’t just study all your hours in one chunk and expect the same results as if you spread those hours out throughout a year. You need to log a lot of both clock time and calendar time. You can’t cram for an exam. Spread the learning out over calendar time.

Clojure Puzzle 🤔

Last week’s puzzle

The puzzle in Issue 324 was to implement a simple automaton called Langton’s ant.