In “What Is Functional Programming?” I spotted a nice example of how duplication in tests leads to a suggestion to improve the design.
When you use relative include paths in your code, you bind each source code file to its current location in the project’s file layout. You relegate yourself to having to run it from a very specific location on the file system. You generally make it unnecessarily difficult to automate running your application (or its tests) outside your development environment, such as in platform-as-a-service environments. You make things like Docker not just helpful, but necessary.
No matter what one writes, no matter how carefully one tries to articulate it, a non-trivial segment of the readership will interpret it differently from what one intended. So it has gone with “Integrated Tests are a Scam”. I’d like to address some common differences of interpretation.
The entry point. Sometimes it’s
main(). Sometimes it’s an extension point in some complicated framework. Most importantly, it is the border where Their Stuff runs Your Stuff. When I see entry points in other applications, I don’t see a lot of evidence of having thought about the design much. It becomes a kind of junk drawer for code. This happens especially when programmers are working in environments they don’t feel particularly comfortable in. (I don’t know where to put things, so I’ll put them here, since that seems to work.)
You might have noticed dependency injection gaining popularity in recent years. You might also have noticed some notable figures (I’m thinking of Kevlin Henney) who appear to be bad-mouthing dependency injection, when really they are simply taking away what they feel is a veneer of mystery from the term.1 I must say that I appreciate their doing so without pretentious use of the annoying word “demystifying”. (Simply ignore my own “demystifying” article by most certainly not clicking here). While on a three-hour-long car trip it occurred to me—some would say much too late—that injecting dependencies, particularly through the constructor, acts precisely like partially applying functions. This leads to a couple of potentially useful conclusions:
I consider it categorically unfair to interpret what Kevlin writes or Chris Oldwood writes as “bad-mouthing” the concept of dependency injection, but unfortunately, a careless reading of their work might suggest that in the mind of the careless reader. They mostly object to coining a new term for an old idea, with which I agree. Even so, dependency injection remains known as a term of art, so I just want to roll with it.↩
A Twitter follower asked me how to recover from feeling overwhelmed by too many integrated tests, calling it “a common struggle of mine”.
This article describes an evolutionary design microtechnique. Specifically, it describes a weak signal that I use to guide myself towards more modular designs. I find it both simple and surprising: simple because it involves using a single value, but surprising because it involves a value that programmers largely recommend against using. I refer to the humble
We recently ran a legacy code retreat based on your format. People were questioning the value of subclass-to-test in a real world scenario. The best reason I could come up with for myself was that it’s a smaller step to separating out responsibilities from a class.
How would you frame the value of subclass-to-test, and in which contexts would you use it?
I’ve written in the past that integrated tests are a scam, but that’s not what I mean here.
When clients ask me to help them with legacy code it generally takes less than 30 minutes for me to run into a debilitating constructor—a term I use to describe a constructor that does too much. You might think me melodramatic for calling it “debilitating”, but these constructors not only slow us down when we try to understand the code, but block our every effort to improve it. These constructors often hardwire a dependency either to some horrifying external resource (a database or a web service end point) or into a framework (even something as “simple” as threads or a lifecycle manager). These constructors also often grow to dozens, and in some extreme cases hundreds, of lines. These constructors kill code bases and, by extension, suck the life out of programmers.