- Outside In development with Double Loop TDD
- By Emily Bache
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: Yong Li
- Proofread by Liao Malin
Double loop TDD is used for outside-in development
In my last article, I started talking about the London school of Test-driven Development (TDD) and two features that I think make it different from traditional TDD. The first is outside-in development using dual-loop TDD, which I’ll discuss in detail in this article. The second point, which I’ll discuss in the next article, is object-oriented design of “say, don’t ask.”
Double loop TDD
When you do dual-ring TDD, the time you spend on the inner ring is measured in minutes and the time you spend on the outer ring is measured in hours or days. Outer ring tests are written from the perspective of external users of the system, typically covering coarse-grained functionality, and have been deployed in a real (or at least near-real) environment. In my book I call these “Guiding Tests.” Freeman and Pryce call them “Acceptance Tests.” These tests should fail when customer expectations are not met — in other words, they provide good backtracking protection. They also document how the system should behave. (See also my article “Principles of Agile Automated Test Design.”)
I don’t think dual-ring TDD is unique to London school TDD, and I believe it will be adopted by traditional TDD developers as well. This idea was written in Kent Beck’s first book on extreme programming. But I think the uniqueness of the London school is the design from the outside in, supplemented by the use of mock.
Designed from the outside in
If you use dual-loop TDD, you will usually start by writing a tutorial test that illustrates how a user interacts with your system. This test helps you identify the top-level function or class that is called first as the entry point for the required functionality. This is often a GUI component, a link on a web page, or a command line flag.
In the case of London TDD, when you start designing the inner TDD classes or methods that are invoked by that GUI component, web link, or command line flag, you quickly realize that the new code can’t do the whole thing by itself, but needs to be done by other collaborating classes.
The user looks at the system and expects certain functionality. This means that the boundaries of the system require a new class. This class, in turn, requires more collaboration classes that do not yet exist.
These collaboration classes don’t exist yet, or at least don’t provide all the functionality you need. Instead of suspending TDD at this point and developing these new classes right away, you can mock them in your tests instead. It’s usually easy to change mock and experiment code before you develop interfaces and protocols to meet your requirements. This way, when you’re designing test cases, you’re also designing production code.
You can mock out collaboration objects so that you can design interfaces and protocols between them.
Once you’re happy with your design and your testing has passed, you can go to the next level and actually implement a collaboration class. Of course, if a class further needs other collaborators, you can mock them out to further design these interfaces. This approach can be sustained throughout the system design, reaching across architectural and abstraction layers.
Now that you’ve completed the system boundary class, you can develop one of its collaboration classes and mock out any further collaboration classes that the class needs.
This way of working allows you to break down problems into manageable parts, specifying and testing existing parts thoroughly before you start a new one. You can start by focusing on user needs and build from the outside in, tracking user interactions component by component through the system until guided tests can be passed. You don’t usually mock out parts of the system during guided tests, so you can be sure that you haven’t forgotten to implement any of the collaboration classes when the guided tests eventually pass.
In traditional TDD, from the outside in
It is also possible to go outside-in in a traditional TDD approach, but in a way that requires little mock. There are several different strategies to solve the “collaboration class doesn’t exist yet” problem. One is to design from a degraded use case, where almost nothing is happening from the user’s perspective. This is a special case when the output is much simpler than the actual use case, or the happy path. This allows you to build the class structure and methods required for this simplified version of the functional requirements using only the most basic empty implementation, or dummy return values. Once the structure is in place, you can flesh it out (perhaps from the inside out).
Another outside-in strategy in traditional TDD is to write tests from outside-in first, and when you find that you can’t get a test to pass before a collaboration class is implemented, comment out that test and implement the required collaboration class instead. Eventually you’ll find that you can fully implement a class with just the collaborators that already exist, and work your way up from there.
Outside-in sometimes doesn’t work in traditional TDD methods. You start with a class at the heart of the system and pick out a piece that can be fully implemented and tested by existing collaborators alone. This is usually a class at the center of the application’s domain model. When it’s done, you continue to develop the system from the center outward, adding new classes one by one. Because you only use existing classes, you almost never need to mock. You’ll also find that you’ve completed all the features and passed the guided tests.
The advantages and disadvantages
I think the outside-in approach has significant advantages. It helps you stay focused on what your users really want, and allows you to build something that actually works, instead of wasting time polishing something they don’t need. I think the outside-in approach requires skill and training for both traditional TDD and London TDD. Learning how to break down functionality into incremental pieces that you can develop and design step by step is not easy. But if you work from the center outwards, there’s a risk that you’ll build things that users don’t need, or that when you get to the outer layer you’ll end up finding the system doesn’t work and have to refactor.
However, assuming you’re already working from the outside in, I still think there’s a difference depending on whether you write fake implementations in real production code or just mock. If you write them in production code, you gradually need to replace them with real features. If you mock mock features, they will remain in your test code forever, even after the real features have been implemented. This is useful for documentation and allows your tests to continue to execute quickly.
That being said, there is some debate about maintainability when you use a lot of mocks in your tests. When the design changes, updating all the mocks in addition to the production code may be too costly. Once the actual implementation is complete, perhaps the inner ring tests should be removed? After all, guided tests already provide all the backtracking protection you need, so tests that are only useful for your initial design aren’t worth keeping? I don’t think it’s a blameless thing to do. From my discussions with some Of the London supporters, even if they remove some tests, they don’t remove all tests that use mocks.
I’m still trying to understand these arguments, and trying to figure out where London TDD can bring the most benefit. I hope I’ve outlined the differences between the various approaches to outside-in development. In my next article, I’ll explore how London school TDD is promoting object-oriented design of “say, don’t ask.”
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.