Design Pattern Evangelist Blog

Smart pointers about software design

Humble Objects - Designing Code You Don’t Hate Testing

A pattern to help test code that's difficult to test


Introduction

Oh Lord, it’s hard to be humble when you’re perfect in every way.Mac Davis: American songwriter, singer, performer, and actor

When tests are painful, it’s often because our code wasn’t built for testing. That’s where seams come in — and sometimes, the best seam is a Humble Object.

I first encountered the Humble Object Pattern in Bob Martin’s Clean Architecture book in Chapter 23 - Presenters and Humble Objects, which starts with:

The Humble Object pattern is a design pattern that was originally identified as a way to help unit testers to separate behaviors that are hard to test from behaviors that are easy to test. The idea is very simple: Split the behaviors into two modules or classes. One of those modules is humble; it contains all the hard-to-test behaviors stripped down to their barest essence. The other module contains all the testable behaviors that were stripped out of the humble object.

In hindsight it makes perfect sense to me now, but it left me a bit baffled at the time. Bob moves immediately in showing how the Humble Object can be used to help test GUIs. Not being a front-end developer, I didn’t quite grasp the broad applicability of the Humble Object Pattern at the time.

In the spirit of this pattern’s humble nature, I’m going to try to keep this blog entry humble and concise.

The Humble Object Pattern

Most patterns I’ve presented have featured structure or behavior. They have included UML class diagrams and/or code examples. The Humble Object Pattern tends to be more procedural and conceptual than structural or behavioral making it somewhat similar to the Façade Design Pattern in that aspect.

This pattern is about making code easier to test especially when coupled to a software element, often an external dependency, that makes testing challenging in its original form.

The Humble Object Process

I use the Humble Object pattern. Push as much intelligence as possible out into a testable component. Humiliate the [remaining small, humble and] dirty component to the point that it is obviously correct and requires no tests.Bob Martin on X/Twitter

Introducing the Humble Object Pattern to an implementation consists of these basic steps:

  1. Recognize that a section of code is difficult to test because of a tight dependency that cannot be easily be emulated via a test double.
  2. Isolate the smallest portion as possible of the dependency via extract method. Since the extracted method cannot be easily unit tested, even after extraction, keep it as small as possible, i.e., humble. Correctness is usually verified upon visual inspection of the humble code and how it functions when integrated with other parts of the system. This is fine — because humble objects deliberately avoid logic worth unit testing.
  3. Declare the extracted method as package-private or possibly extract it as an implementation to an interface so that it can be replaced with a test double in a unit test.

The original code can be unit tested with test doubles emulating the Humble Object’s behavior.

The Humble Object Pattern is introducing a seam, but in the context of isolating the code that cannot be unit tested and keeping it as small and humble as possible.

Humble Object Examples

Several previous blog entries have already illustrated the Humble Object Pattern even though I had not introduced the term yet. I won’t repeat details here, since they can be obtained in the original referenced posts.

Suril, the Semaphore and Me

In Suril, the Semaphore and Me, Suril and I resolved a challenging unit test problem by extracting a Semaphore call as a package-private method, and then we injected test double behavior to inject Semaphore behavior in the test cases.

Approval Testing

In Some Behaviors Require Observation, I described how the Humble Object Pattern can be used to separate the element that renders the GUI from the ASCII representation of the content to be rendered.

The element that renders the GUI, which could be a browser, is the Humble Object. We want to test the ASCII content that’s rendered by the browser, but not the browser itself.

In one of Bob Martin’s videos, Clean Code: Advanced TDD, Episode 23, Part 2, Mocking (See References), he provides another version of this where he tests the rendering itself. His demo features Java Swing code that contains just enough code to render a small window with a few elements on it. All functional behavior is delegated to an interface. In his GUI test, he provides a Test Double Fake that provides just enough behavior to render the GUI realistically.

Then Bob confirms via his own visual inspection that the Test Double Fake supported GUI displays the faked behavior as desired.

Time Lord Clock

In Time Lord Clock, I described how to take control of time in unit tests by introducing a Clock interface, which would be resolved with the humble system clock in production and a stubbed clock in the unit test.

Adapters

Adapters are often Humble Objects. Their implementations tend to be small, and they tend to be coupled with external dependencies, making them difficult to test, but relatively easy to override with test doubles.

Flaky Tests

Flaky tests are the worst. A flaky test is a non-deterministic unit test that sometimes passes and sometimes fails. We want our tests to be deterministic. They should always render consistent results.

We can’t trust flaky tests. What’s the true nature of a flaky test? When it passes, is it representing the true nature of the software being tested or is it a false positive? When it fails, does the code really have an error or is it a false negative?

A failing flaky test can abort a CI/CD pipeline build, delaying delivery. When that happens, we often start the pipeline again, hoping that all of the flaky tests will pass. I’ve done this myself. Are we really confirming that the code works, or are we just using the retry as a workaround to detour an inconvenience that’s preventing us from delivering our changes?

If the implementation is flaky enough to exhibit inconsistent behaviors in our tests, what’s to prevent the same code from exhibiting inconsistent behaviors in production?

Any form of non-deterministic dependency can cause tests to be flaky. Race conditions are a common culprit. In Semaphore with Suril, I had been contemplating tests with multiple Threads, Sleeps, Timeouts and more before considering the Humble Object Pattern. I can almost guarantee that any non-Humble Object tests would have been flaky, slow or both.

While the Humble Object Pattern won’t be a panacea for curing all flaky tests, it can provide quite a bit of relieve. Isolate the non-determinist elements of the implementation into a Humble Object and replace them with a deterministic Test Double in the tests.

Summary

Most code is testable — until it isn’t. GUIs, APIs, random numbers, clock time: they fight back. The Humble Object pattern is a reminder that good design cares about your tests too.

Try to write code you don’t hate testing.

References

Here are some free resources:

Flaky Tests:

Here are some resources that can be purchased or are included in a subscription service:

Comments

Previous: Mastering Time in Software Testing - Strategies for Temporal Behavior Verification

Next: Preface, Primer, Table of Contents, Etc.

Home: Design Pattern Evangelist Blog