Suppose the order manager is becoming difficult to maintain. You want to split its responsibilities without changing what an accepted order records or sends. The existing acceptance scenarios give you a way to verify those obligations while the internal organization changes.

About the examples: Meridian Ordering is a private training reference project. The inline listings illustrate the techniques; the full C# and Python repositories are not currently public.

The test still invokes the same public operation, reads its results from the database and broker, and compares its email capture. That stability comes from asserting behavior through the boundary and retaining the real production path. Architecture, coding, failure handling, and review make the path understandable and its failures meaningful.

What refactoring preserves

Martin Fowler defines refactoring as improving internal structure while preserving observable behavior, using small behavior-preserving transformations. In this discussion, the business requirements have not changed. We may clean up code, redistribute responsibilities, or work toward a different internal architecture, but the system still owes its callers and downstream consumers the same behavior.

The public contract matters here. Renaming a public operation or changing its input or output models changes what callers, including tests, must use. The benefit described here assumes that the Domain Facade’s public contract and the required observable effects remain stable.

The refactoring promise and how to achieve it

One promise I took from Kent Beck’s Test-Driven Development: By Example was the ability to refactor with existing tests protecting the behavior. Refactoring is part of the TDD cycle Fowler describes. I read their work and followed Beck’s and Fowler’s talks looking for a practical demonstration of how to reorganize an entire system without rewriting its tests. I did not find the whole-system demonstration I was looking for.

Fowler does explain the coupling problem. In Mocks Aren’t Stubs, he describes how expectations about calls to collaborators can break when the implementation changes and interfere with refactoring. What I wanted to see was the complete arrangement that lets the same tests survive a substantial internal redesign.

Functional acceptance testing at the boundary provides that arrangement. The tests know the Domain Facade and the public classes going into and coming out of it. Test support supplies the arrangements and observations needed to verify the complete outcome. The tests have no knowledge of the internal implementation, including how a spy captures an observation or how the Test Mediator connects it to the scenario. That separation is a deliberate design decision.

With unchanged requirements and a stable public contract, we can refactor bits of the system or the entire implementation behind the Domain Facade. The same scenarios, invocations, and assertions continue to verify the required behavior. An internal redesign alone does not require new acceptance tests. I have used this approach many times to refactor systems while keeping those tests unchanged.

Keep the contract stable while changing the internals

Stable outcomes through internal refactoring A refactor reorganizes internal implementation while keeping the public operation and required observable effects stable. The same scenario and outcome comparisons continue to verify that contract; support may change when genuine transport seams change. BeforeAfterPublic operationSame operationOriginal internalorganizationReorganizedimplementationRequired effectsSame requiredeffectsSame scenario and outcome comparisonsTest support may change with real seams
Keep assertions tied to the public contract and required effects. Internal reorganization can preserve the behavioral suite while legitimate setup or transport-support changes remain explicit.

The public invocation stays visible:

/// Act: the public operation remains the same across an internal refactor.
OrderPlacementResult actualOrderPlacementResult = await domainFacade.PlaceOrderAsync(orderPlacementRequest);

The complete test verifies the returned outcome and the required effects. It has no dependency on the number of manager methods or the identity of an internal helper. Moving composition or splitting orchestration leaves the scenario and its assertions intact when those required outcomes remain the same.

This is a refactoring example, not a claim that a particular refactor was applied for this article. The executable suite supplies the verification a real refactor would run. If an externally meaningful contract changes, review and change the affected scenarios deliberately.

Recognize legitimate test-support changes

The tests remain unchanged because test support absorbs implementation details. A refactor may require moving or rewriting a transport spy, updating a readback query for a reorganized internal schema, or changing assembly and configuration. The spy remains in testing code and still performs its required capture or delivery control. Production gateways continue to execute.

Keep the Test Mediator’s instructions and observation models stable while adapting that support. The scenario still requests the same business arrangement and compares the same required outcome. Rewriting a spy’s implementation does not require rewriting the test that consumes its observation.

An externally meaningful wire-field change is different: downstream consumers now receive a changed contract. Review that change and its affected scenarios against the requirements. For an internal refactor, retain complete comparisons and keep support capable of observing the same obligations.

Begin adoption with one complete feature

For an existing system:

  1. Identify a public operation and its required caller, store, and downstream outcomes.
  2. Establish business requirements, acceptance criteria, functional requirements, and non-functional requirements with their owners.
  3. Derive scenarios and independently review their arrangements and expectations.
  4. Make required failures observable through meaningful production contracts.
  5. Provision the real infrastructure and isolate records and message observations.
  6. Introduce a selected transport seam where recipient safety or precise failure arrangement requires it, retaining production behavior above that seam.
  7. Implement one complete accepted scenario, then refusals and failure states.
  8. Protect that feature’s required suite in CI and continue feature by feature.

Starting with one feature makes the work concrete. It does not mean declaring the whole system verified when only that feature has correct coverage. Record what has been brought under the discipline and what remains outside it.

Make production failures useful

A meaningful domain exception tells the caller and operator which condition occurred. Gateways translate provider failures into that language while preserving appropriate cause and context. Required post-commit handling records pending obligations and logs the failure according to the feature’s policy.

These contracts support both production diagnosis and assertions. They reduce the need for tests to infer failures from arbitrary nulls or vague success flags. The failure article shows those results in Meridian.

Validate at the owning trust boundary. After that boundary establishes typed domain data and the required invariants, internal methods can rely on their documented contracts. Database and external-provider responses remain separate boundaries requiring translation and validation. Removing duplicated internal checks must not remove validation of untrusted input.

Use a smaller production boundary for inherited rule complexity

I once inherited a badly structured cluster implementing USPS address formatting rules. Refactoring safely required coverage of its rule permutations first. We placed an abstraction around the cluster, verified the rules through that boundary, and then reorganized its internals.

That is the reason for conditional class-composite verification: a dense inherited cluster can make full-system arrangement structurally impractical for all of its rule cases. The abstraction remains a real production boundary around the cluster, and system-level scenarios still verify that it participates in the assembled operation. It is not a license to replace that operation’s acceptance suite.

Meridian does not demonstrate that USPS cluster or a class-composite suite. It does demonstrate independently tested composers that its expectations borrow. Their complete email and fulfillment tests verify those components’ content responsibilities.

Distinguish development tools from the maintained release artifact

A developer may use a small temporary check to understand or construct code. The thesis does not prohibit that activity. The maintained authority for the feature remains its reviewed boundary scenarios with complete outcomes.

Supporting tests need an explicit responsibility: oracle verification, assertion infrastructure, interface translation, or an admitted dense rule boundary. Avoid retaining arbitrary internal-interaction tests merely because they were convenient during construction. The suite should explain which requirement each maintained scenario verifies.

Protect the gate under delivery pressure

Detailed testing takes time. The business needs to understand the purchase: continuing regression protection and confidence in the functionality it deploys. Agree that required scenarios and expectations are reviewed and that the required suite must pass before behavioral release readiness is claimed.

Developers own readable arrangements, complete comparisons, useful failures, and appropriate support maintenance. Review owns gaps that a green run cannot discover on its own. The approach is a team discipline; a testing technique cannot create competence or resolve missing business decisions automatically.

Adoption grows by preserving verified behavior as features and internals change. The regression article explains the continuing business value, and the release article explains the responsibilities after the suite is green.


Previous article | Series contents | Next article