Making code easy to test
Instead of testing hard code.
Enjoy a whiskey moment with Arlo talking about why our code drives us to whole-system tests and what we can do about it.
Want to chat with Arlo more? Register for our virtual Ask Me Anything in two weeks or join our Code by Refactoring slack channel.
Responsibility for the Complex Ecosystem
Software developers are big damn heroes.
For each product, we attempt to organize a community of a thousand humans to carefully grow and maintain twenty million lines of code that deeply interacts with each other, other people’s software, and the natural world. Not only are we within that complex context, we also change software rapidly in response to the business environment while ensuring nothing unexpected happens.
This is why we depend so heavily on testing.
Tests to Protect the Big Picture
Automated tests, manual tests, and monitoring are our way to sense what’s going on in the invisible world of software. Each software project is a complex, adaptive system, which means it has spooky action at a distance. In other words, a change in AB interaction can have an unknown impact to XY interaction. You simply don’t know.
As such, a test for any part of a complex, adaptive system is invalid. The only way to see the behavior of the whole system is to test the system as a whole. That pushes us towards live site monitoring, test in production, and integration testing.
Unfortunately, these whole-system tests are problematic. They are slow because they have to execute so many components. They report many false failures because they execute so much code unrelated to their purpose. They miss lots of bugs because each test can only look at one case while the code has hundreds of combinations of different interactions.
Enter advanced testing techniques. Mocks, Monte-Carlo simulations, machine learning, and combinatorial evaluation are some of the many techniques we have developed to make whole-system tests more affordable. The nature of the problems remain though. Tests are still slow, fragile, and miss bugs. And code changes still cause spooky action at a distance.
Instead testing this hard code, how can we make code easy to test?
Can this be Different?
What if we had a decomposable system? What if we were essentially moving from an ecosystem to a clock?
In fact, small software projects behave like clocks. Start-ups and open source projects use unit testing as their primary approach. That is true even when they are competing against large, established projects. Both the start-up and the established company solve the same complex problem, yet the start-up solves it with a clock while the established company tries to manage an ecosystem. And, because the start-up has a clock, they test it differently.
Want to avoid the ecosystem mire with your new project? Take a workshop!
David Bernstein, author of Beyond Legacy Code, shows how to solve a complex problem with a clock like approach in new code in his class.
More details at the bottom!
Testing by Parts & Interactions Instead
The start-up’s code is complicated, but it isn’t a complex adaptive system. The start-up can decompose the code without altering its behavior. This lets them test each component in isolation using automated unit tests.
Interactions between components are deterministic and consistent, so the interactions themselves can be turned into more components. That allows us to test the interactions with isolated and automated unit tests.
The result is that the start-up can verify whole-system correctness without ever assembling the whole system. They still have some whole-system tests, especially monitoring, but that is for a different purpose. The startup uses monitors for A/B Testing to find a better market fit.
A/B Testing is often unavailable to the established product simply because they need to spend all of their effort maintaining whole-system tests to verify basic correctness. As a result, the start-up learns more about the customers and gains a market advantage.
Being behind on market knowledge leads developers at established companies to call for a rewrite. After all, the start-up created their clock by effectively re-writing the established product. Shouldn’t the established company do the same?
Transitioning Complex System into Complicated System
Rewrites are risky though. We have heard the same story from many enterprises on the difficulty of changing an established product. Different groups of customers expect contradictory behaviors, so any improvement for one makes things worse for another. The existing system has adapted over decades to handle this internal contradiction. A rewrite could get there eventually, but it will take many years and the system will get more complex than the start-up’s current clock.
So what other option is there? Large products can evolve over time.
Refactoring allows developers to work incrementally. Each testing obstacle identifies one specific component with complex interactions. Developers can then target precise refactorings to those precise problems. They just need to ensure those refactorings don’t create side-effects in their ecosystem.
Refactoring can be safe, but it depends on exactly what “refactoring” practice you do.
Product Owner won’t let you refactor? Send them to our webinar!
Deep Roots is offering a special webinar about the business value of refactoring. Your PO gets a hands-on experience to personally feel the value and safety provided by refactoring.
More details at the bottom!
“Refactoring” vs Refactoring
When most people mean say “refactoring” they mean “edit small pieces of code while using automated tests to detect side-effects.” That approach is more properly called Remodeling, and is very different from what Martin Fowler documented as Refactoring.
Remodeling allows the developer to make any desired code change, some of which create side-effects, and then attempts to detect and undo those side-effects. By contrast, Refactoring limits the developer to only making a few kinds of code changes. The only changes allowed are those that are guaranteed by the rules of the programming language to result in code with the same effects. Thus there are no side-effects to observe, and it doesn’t matter whether there are tests to detect those side-effects.
The word “refactoring” creates confusion because it is commonly used to describe these two very different practices. We use the term Disciplined Refactoring to describe the original meaning of “Refactoring.”
Disciplined Refactoring is the solution we need in order to safely change our ecosystem. Unlike Remodeling, Disciplined Refactoring cannot create a side-effect. The rest of the ecosystem cannot tell that one interaction has changed.
Each refactoring resolves one complexity without altering anything else - turning part of the ecosystem into a little bit of clockwork. Our article on Solving 80% of Testing Problems provided options for many of the most common cases. Our Legacy to DevOps Series shows a 10-step path that will guide you through the year-long process to evolve an ecosystem into a clock. Finally, developers can learn the core refactorings and then develop and document their own sequences to solve a specific problem.
Taken together, Disciplined Refactoring applied to the testing blockers allows the established product to evolve over time. Developers can change it from behaving like an ecosystem to behaving like a clock. And that enables isolated and automated unit testing - just like the start-up.
Master Extreme Programming Practices
Want to master the practices of Extreme Programming like TDD, refactoring, and emergent design? Join David Bernstein for his next online Scrum Developer Certification class where you'll discover principles and practices used by top developers for rapidly creating extensible code. Registrations for May and June classes are now open.
Attending CRAFT 2022?
If so, check out talk by Marian Hartman, Arlo’s business partner and instructional designer! She’ll be presenting Using Empathy to Make Tests Easy and Code Safe.
Business Value of Refactoring
…for your PO
Does your PO reject refactoring? Ask them to register for our June webinar that addresses the business value of refactoring. Both you and your PO will be grateful!
Give them a 50% discount code: “newsletter”.