PurelyFunctional.tv Newsletter 325: Tip: don't use a protocol to make testing easier
Issue 325 - May 06, 2019 · Archives · Subscribe
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 defn
s. 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.
Do you have a tip you'd like to share? Let me know. Just hit reply.
Currently Recording 🎥
I am currently recording a course called Repl-Driven Development in Clojure. There are a several new lessons ready this week.
-
Playing with values at the repl contains three tips for working with values directly at the repl.
-
In Printing values and controlling printing I go over five ways to print and two knobs to control printing.
-
In Controlling namespace reloading with defonce I explain three different situations where you would want to use defonce to avoid reloading code each time---and a hack to reload the defonce when you do need to.
-
Navigating namespaces at the repl talks about how to work with exceptions at the repl, and how to read stack traces.
-
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.
Check out the course. The first lesson is free.
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 lo g 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.
You can see the submissions here.
I got a few cool images of people visualizing their ants. They kind of all look similar. But Andreas Werner's stood out for being animated:
This week's puzzle
Conway's Game of Life
Conway's Game of Life is a famous cellular automaton. The rules are simple:
- Cells are arranged in a square grid.
- A cell is either alive (black) or dead (white).
- Cells have eight neighbors that surround it.
- A dead cell comes to life if it has exactly three live neighbors.
- A live cell dies if it has four or more neighbors.
- A live cell dies if it has one or zero neighbors.
- All other cells continue as they are.
Build this automaton.
Bonus points for allowing an infinite board.
As usual, please send me your implementations. I'll share them all in next week's issue. If you send me one, but you don't want me to share it publicly, please let me know.
Rock on!
Eric Normand