2024 Public Training Schedule
November 18 – 21, 2024 – Agile Analysis and Design Patterns
Half-Day Sessions
December 9 – 12, 2024 – Agile Analysis and Design Patterns
Half-Day Sessions
(c) 2024 To Be Agile
I can learn a lot about the kind of coupling in a system by listening to how people talk about testing it. If someone says, “I couldn’t test that without instantiating half the system,” then I know they have some coupling issues. Unfortunately, most systems I have seen—and most systems I know other people have seen—have an enormous amount of poor coupling. That, more than anything else, makes it really difficult to test code.
Code that directly calls a service cannot be decoupled from that service. That may not be a problem when you think about normal usage, but I want our software to be able to run not just in normal usage but also in a test mode that’s reflective of normal usage but more reliable and more robust.
In order to implement automated testing in a system, our task must be completely reliable. Which means the code we write that depends on some external code has to be built in such a way that when we’re testing it, those external dependencies don’t need to be present. Fortunately, there’s a whole discipline and set of techniques around this that we call “mocking,” working with test doubles, or faking implementation.
Bad coupling is one of the places we feel the pain of testability the most. When a class has multiple dependencies a developer who wants to test it has to write a test harness and mock out all the dependencies. This can be very difficult and time-consuming. But there are techniques for writing code to interface with dependencies that make it easy for us to inject test doubles as needed.
One technique for making code that’s coupled to external dependencies easier to work with and to test is to separate out locating a resource from using it.
For example, if I write an API called ParseDocument(URL Webpage) that takes the webpage as input, in order to test it I need to bring up a web server that supplies that webpage, which is a lot of work just to test the parsing of the document.
So instead I could break this task into two steps: first locating the document on the web and then passing the document in to be parsed.
By separating this task out into two steps, I gain the flexibility of testing just the parsing of the document, which is the code that has my business rules. I can separate this code from the code that locates a document on a web server and returns it because it’s fair to assume that code was tested by the provider of the operating system or language and it works just fine. I certainly don’t feel the need to test it myself. But if I write an API that requires me to pass in a URL in order to parse the document, I’ve now required it to test a bunch of stuff I really don’t need or want to test. In fact, the whole idea of injecting dependencies as needed instead of just newing them up and using them has tremendous potential for decoupling systems.
I’m always surprised when even the most brilliant developers I know don’t really pay attention to these things, when they’re among the most fruitful areas for allowing us to build maintainable systems.
{3 Comments }
Previous Post: « Pathologies of Tight Coupling
Next Post: Quality Code is Encapsulated »
Thank you for this post, David. For your example, what if your API wanted to provide that method for convenience? It may also provide ParseDocument(HtmlDocument webpage). The convenience method would be implemented like this:
ParseDocument(URL webpage) {
website = GetHtmlDocument(webpage)
ParseDocument(website)
}
In this case, ParseDocument(HtmlDocument) would be tested as you said, but would ParseDocument(URL) need to be tested?
Thanks.
Hi Carlos,
When unit testing we want to test our code and not be forced to test other people’s code. My goal here is to test my parsing code but if my API takes a URL as a parameter then in order to test my parsing code I also have to bring up a web server and load the page to be parsed.
Instead of doing this I can split out the API into a fetcher and a parser, as you have done in your example. I don’t have to test GetHtmlDocument(webpage) if it’s just retrieving a web page but I still want to test ParseDocument(website). I don’t have to test wrapper method ParseDocument(URL webpage) since it just calls the other two methods.
This is a technique sometimes referred to as “pealing” and it can greatly simplify testing by allowing us to only test our code and avoid testing external dependencies.
I hope this helps,
David.
Thank you for that explanation!