Hello again, everybody! Today’s article is made possible by Leonardo Giordani. He wrote a pretty good book called Clean Architectures in Python which is pay-what-you-want. I’m not quite done with it yet, but it’s a pretty good book so far, especially considering its cost.
He opens up by talking about TDD for several chapters in order to make sure you know what’s going on for the latter half, where he builds a simple Clean application from the ground up giving you his unit tests before the code. He didn’t go step-by-step, so it’s not tedious in that way. But there’s a problem I have with a part of the book that I’d like to address. To do that, I need to do the most basic primer on Clean Architecture first.

Clean architecture is effectively the same as the Onion Architecture and Hexagonal Architecture (at least in principle). You have a domain model (also called Entities) that stays the same, no matter the technologies or frameworks you’re using. Then there’s the Use Case layer, which wraps the domain model. It defines what actions can be done with domain as well as the interfaces needed in order to interact with the outside world, such as databases or messaging systems. Those interfaces can then be implemented in the next layer where Controllers, Gateways, and Presenters exist. This is where frameworks and databases and such come in. You make types that conform to those interfaces and interact with technologies you want to use.
Here’s where we dig into the issue in his book. He wrote an integration test for a Controller to test that it properly processes the request and response data through the Use Case. But he mocked out the Use Case. Later, he changed the Use Case to accept a set of filters as a parameter, but the test still passed even though running it in real life always caused an error. This is mostly due to the dynamic nature of Python and Python’s mocking system, since the mocks don’t automatically change themselves to fit the interface they’re representing (mostly because they don’t KNOW what they’re representing).
Unit tests, and especially integration tests that test interaction between layers need to break when the API for that interaction makes a breaking change. Because of this, the Use Case should not have been mocked.
You could argue that this isn’t something you have to worry about in statically-typed languages or in languages where the mocking system makes sure it matches the type it’s representing. And, technically, that’s true. But I would argue that the Use Case and Entity layers should never be mocked out. Only the things which would slow down the test noticeably should be mocked. These layers shouldn’t slow you down directly; only through using implementations of their outside world should that happen, and those things should be mocked.
If the domain is making tests slow at this point, then it’s an indicator that you may want to change something in your domain.
Do you have any arguments for the reverse? Best I can think of is that a problem caused by a change in the deepest part of the domain can cause failing tests all over the place, rather than in just the tests that are focused on that area. I would rebut with the fact that, if you’re doing TDD, then you shouldn’t need the locality of the tests to show you where the problem is; it’s with what you just changed. And, if the change created a problem several layers up rather than more locally, then you should be glad that the broader tests were there to catch it.




Pingback: Don’t Decouple When it Hurts Cohesion | Programming Ideas With Jake