{"id":179,"date":"2026-04-13T08:00:00","date_gmt":"2026-04-13T13:00:00","guid":{"rendered":"https:\/\/harmonic-framework.com\/?p=179"},"modified":"2026-04-13T08:32:43","modified_gmt":"2026-04-13T13:32:43","slug":"bdt-in-10-minutes-testing-as-architectural-evidence","status":"publish","type":"post","link":"https:\/\/harmonic-framework.com\/es\/bdt-in-10-minutes-testing-as-architectural-evidence\/","title":{"rendered":"BDT in 10 Minutes: Testing as Architectural Evidence"},"content":{"rendered":"<p>Boundary-Driven Testing begins with a single observation: <strong>testing difficulty is not a testing problem.<\/strong><\/p>\n\n\n\n<p>When a unit test requires a running database, the test is not broken. When a change to a business rule breaks seventeen tests that have nothing to do with that rule, the test suite is not fragile. When a component cannot be exercised in isolation, the coverage tooling is not lying to you.<\/p>\n\n\n\n<p>The structure is wrong. The tests are just telling you so.<\/p>\n\n\n\n<p>Most teams respond to test pain by improving the tests. Better mocking libraries, more granular coverage requirements, shared fixture factories, test lifecycle hooks. The tests improve. The pain persists \u2014 because the source of the pain is not in the test suite.<\/p>\n\n\n\n<p>Boundary-Driven Testing addresses that directly by treating the test spiral as an architectural map, and testing difficulty as a diagnostic signal about structure.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Test Spiral Is a Structural Map<\/h2>\n\n\n\n<p>The test spiral \u2014 unit, integration, end-to-end, system, user acceptance \u2014 is not a testing methodology. It is an architectural map.<\/p>\n\n\n\n<p>Each ring of the spiral corresponds to a level of architectural scope, and that scope is determined entirely by where boundaries have been placed. Unit scope is a single component. Integration scope is one collaboration across one boundary. End-to-end scope is a complete journey. Where the boundaries sit determines what each level can see.<\/p>\n\n\n\n<p>Get the boundaries right and the spiral populates itself. Get them wrong and it collapses \u2014 unit tests become integration tests in disguise, end-to-end becomes the only reliable safety net, and the suite grows expensive while providing diminishing confidence.<\/p>\n\n\n\n<figure class=\"wp-block-pullquote\" style=\"font-size:1rem\"><blockquote><p>A unit test that takes three seconds to run is not slow. It is not a unit test. Something that should be behind an Accessor is embedded in the component under test, and the test is paying the latency penalty for it.<\/p><\/blockquote><\/figure>\n\n\n\n<figure class=\"wp-block-table hf-iso-table\"><table><thead><tr><th>Level<\/th><th>Scope<\/th><th>What It Verifies<\/th><\/tr><\/thead><tbody><tr><td>Unit<\/td><td>One component, all dependencies replaced<\/td><td>Behavior of a single responsibility in isolation<\/td><\/tr><tr><td>Integration<\/td><td>One seam between components, outer boundary mocked<\/td><td>Contracts and orchestration wiring between roles<\/td><\/tr><tr><td>End-to-End<\/td><td>Complete user flow, full stack<\/td><td>The system behaves correctly from the outside<\/td><\/tr><tr><td>System<\/td><td>Integrated system under realistic conditions<\/td><td>Non-functional qualities: load, failure, configuration<\/td><\/tr><tr><td>User Acceptance<\/td><td>Real users or proxies<\/td><td>What was built matches what was intended<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">The Test Spiral<\/h3>\n\n\n\n<figure class=\"hf-diagram\" style=\"flex-direction:column;align-items:center;gap:24px;\">\n<svg width=\"300\" height=\"300\" viewbox=\"0 0 300 300\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"display:block;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,sans-serif;\">\n  <circle cx=\"150\" cy=\"150\" r=\"140\" fill=\"#f0fdf4\" stroke=\"#22c55e\" stroke-width=\"2\"\/>\n  <circle cx=\"150\" cy=\"150\" r=\"112\" fill=\"#faf5ff\" stroke=\"#a855f7\" stroke-width=\"2\"\/>\n  <circle cx=\"150\" cy=\"150\" r=\"84\"  fill=\"#f0fdfa\" stroke=\"#14b8a6\" stroke-width=\"2\"\/>\n  <circle cx=\"150\" cy=\"150\" r=\"56\"  fill=\"#fff7ed\" stroke=\"#f97316\" stroke-width=\"2\"\/>\n  <circle cx=\"150\" cy=\"150\" r=\"28\"  fill=\"#eff6ff\" stroke=\"#3b82f6\" stroke-width=\"2\"\/>\n  <text x=\"150\" y=\"155\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"700\" fill=\"#1d4ed8\">Unit<\/text>\n  <text x=\"150\" y=\"107\" text-anchor=\"middle\" font-size=\"10\" font-weight=\"600\" fill=\"#c2410c\">Integration<\/text>\n  <text x=\"150\" y=\"75\"  text-anchor=\"middle\" font-size=\"10\" font-weight=\"600\" fill=\"#0f766e\">End-to-End<\/text>\n  <text x=\"150\" y=\"46\"  text-anchor=\"middle\" font-size=\"10\" font-weight=\"600\" fill=\"#7e22ce\">System<\/text>\n  <text x=\"150\" y=\"18\"  text-anchor=\"middle\" font-size=\"10\" font-weight=\"600\" fill=\"#15803d\">UAT<\/text>\n<\/svg>\n<figcaption style=\"text-align:center;font-size:0.82rem;color:#888;max-width:380px;margin:0 auto;line-height:1.5;\">Each ring corresponds to a structural scope. The boundaries in your architecture determine which rings exist and how much each one costs to maintain.<\/figcaption>\n<\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Testing Is Not Separate from Design<\/h2>\n\n\n\n<p>The conventional framing of testing as a discipline separate from design produces a particular kind of pain. Teams adopt frameworks, mandate coverage minimums, write guidelines. The tests improve. The pain persists. A change to a business rule breaks seventeen tests, most of which are not about business rules. An integration test requires spinning up four services to assert one value.<\/p>\n\n\n\n<p>The reframe: a component designed around a coherent responsibility \u2014 with explicit inputs, explicit outputs, and dependencies passed rather than acquired \u2014 is inherently testable. The same structural choices that allow a component to change without cascading effects allow it to be tested without elaborate setup. This is not a coincidence. It is the intended consequence of decomposing correctly.<\/p>\n\n\n\n<p><strong>A component that cannot be unit-tested without mocking half the system is not badly tested \u2014 it is badly structured. Fix the structure; the tests follow.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Test Profiles by Role<\/h2>\n\n\n\n<p>The structural models defined in Volatility-Based Decomposition and Experience-Based Decomposition produce components with well-defined responsibilities, explicit interfaces, and declared dependencies. That structure has a direct consequence: each component role has a natural test profile, derived from where the role sits in the hierarchy.<\/p>\n\n\n\n<figure class=\"wp-block-table hf-iso-table\"><table><thead><tr><th>Role<\/th><th>Test Levels<\/th><th>What to Mock<\/th><\/tr><\/thead><tbody><tr><td>Utility \/ Interaction<\/td><td>Unit<\/td><td>Nothing (pure) or external sink only<\/td><\/tr><tr><td>Resource Accessor \/ API Accessor<\/td><td>Unit<\/td><td>Data source driver<\/td><\/tr><tr><td>Engine \/ Flow<\/td><td>Unit + Integration<\/td><td>Resource Accessor at unit; Accessor contract per state at integration<\/td><\/tr><tr><td>Manager \/ Experience<\/td><td>Unit + Integration<\/td><td>All Engines and Accessors at unit; per-seam mocking at integration<\/td><\/tr><tr><td>Full System<\/td><td>End-to-End<\/td><td>Nothing \u2014 real infrastructure, real data flow<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>A <strong>Utility<\/strong> is pure input-output: given X, assert Y. A <strong>Resource Accessor<\/strong> is translation only \u2014 mock the data source driver, verify the mapping. An <strong>Engine<\/strong> encapsulates business rules with no workflow awareness and no peer Engine calls, so the only mock needed is the Accessor below it; integration tests then verify that the Engine handles every state the Accessor&#8217;s contract can emit. A <strong>Manager<\/strong> orchestrates without executing: unit tests verify routing logic with all collaborators mocked; integration tests verify the seams.<\/p>\n\n\n\n<p>The same rule that prevents an Engine from calling a sibling Engine is what makes the Engine mockable. The same boundary that keeps the Manager out of business logic is what makes the Manager&#8217;s orchestration testable in isolation. The structure and the testability are the same thing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Testing Looks Like Without Boundaries<\/h2>\n\n\n\n<p>Consider a team that has done the right things. They are not writing procedural scripts. They have an application service that coordinates order submission. They have a domain model. They have interfaces behind their external dependencies. The code passes review.<\/p>\n\n\n\n<p>Their <code class=\"\" data-line=\"\">OrderService<\/code> validates the order, calculates the price, saves to the repository, and triggers payment \u2014 all in sequence, all in the same orchestration path. The domain model is shared between validation and pricing because both need order data. Tests are reasonable. Coverage is decent.<\/p>\n\n\n\n<p>Then a compliance requirement arrives: high-value orders must be checked against a fraud rules engine before submission.<\/p>\n\n\n\n<p>The fraud result must flow through the shared domain model so the application service can act on it. The domain model changes to carry fraud status. The application service changes to branch on that status. Because the domain model is shared, the unit tests for the pricing logic now break \u2014 not because pricing changed, but because they depend on the same model. The integration test for the full service must be updated. Mock expectations across multiple test files need to be revised to account for orders that are now rejected before they are saved.<\/p>\n\n\n\n<p>The compliance requirement touched one domain concern. The test changes touched five files that had nothing to do with it.<\/p>\n\n\n\n<figure class=\"wp-block-pullquote\" style=\"font-size:1rem\"><blockquote><p>The tests are not fragile. The structure is fragile. The tests are accurate.<\/p><\/blockquote><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">The Same System Under BDT<\/h2>\n\n\n\n<p>Under Volatility-Based Decomposition, the <strong>ValidationEngine<\/strong> owns fraud evaluation. It uses a <strong>RulesAccessor<\/strong> to query the fraud rules store. The <strong>OrderManager<\/strong> coordinates the sequence \u2014 but does not know what rules exist or how they are evaluated. It invokes the ValidationEngine and responds to what it returns.<\/p>\n\n\n\n<p>The fraud check requires two changes: the RulesAccessor gains a query, and the ValidationEngine gains the logic to evaluate the result. The unit test for the ValidationEngine mocks the RulesAccessor and asserts on the new branch. The integration test for the Engine-to-Accessor seam verifies that the Engine handles every state the Accessor can return \u2014 fraud detected, clean, rules unavailable.<\/p>\n\n\n\n<p>The OrderManager unit test does not change. The PricingEngine does not change. The payment path does not change. The repository mock expectations do not change.<\/p>\n\n\n\n<figure class=\"wp-block-table hf-iso-table\"><table><thead><tr><th>Component<\/th><th>Without BDT<\/th><th>With BDT<\/th><\/tr><\/thead><tbody><tr><td>Application Service \/ Manager<\/td><td>Changes \u2014 must handle fraud result<\/td><td>No change<\/td><\/tr><tr><td>Shared domain model<\/td><td>Changes \u2014 must carry fraud status<\/td><td>Does not exist as shared model<\/td><\/tr><tr><td>Validation logic<\/td><td>Changes<\/td><td>Changes (ValidationEngine only)<\/td><\/tr><tr><td>Rules data access<\/td><td>Added to the aggregate or service<\/td><td>Changes (RulesAccessor only)<\/td><\/tr><tr><td>Pricing tests<\/td><td>Break \u2014 shared model changed<\/td><td>Untouched \u2014 no shared model<\/td><\/tr><tr><td>Payment path tests<\/td><td>Potentially affected<\/td><td>No change<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Two components change. Their tests change. Nothing else is aware the requirement arrived.<\/p>\n\n\n\n<figure class=\"wp-block-pullquote\" style=\"font-size:1rem\"><blockquote><p>A test that breaks because of a change in a component it has nothing to do with is not fragile. It is accurate. The coupling was already there. The test is just the first place it became visible.<\/p><\/blockquote><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">One Scenario, Three Levels<\/h2>\n\n\n\n<p>The same scenario \u2014 order submission \u2014 can be run at every level of the spiral. Each level asks a different question about the same system.<\/p>\n\n\n\n<p><strong>Unit<\/strong> \u2014 Does each Engine handle its rules correctly in isolation? ValidationEngine mocks its RulesAccessor: missing field returns failure, fraud detected returns rejected, valid order returns pass. PricingEngine mocks its Accessor: high-volume returns tier discount, promotion order returns promo applied. One Engine, all its rule branches, nothing else in scope.<\/p>\n\n\n\n<p><strong>Integration<\/strong> \u2014 Does OrderManager route correctly against every contract state its collaborators can emit? Submit an invalid order: the Manager invokes the ValidationEngine (mocked), receives a failure, and the PricingEngine is never called. Submit a valid order: the full sequence runs to confirmation. Engines and Accessors are mocked \u2014 what is under test is the Manager&#8217;s orchestration logic, not its dependencies.<\/p>\n\n\n\n<p><strong>End-to-End<\/strong> \u2014 Does a real order submitted through the real API surface correctly? No mocks. Real stack. POST \/orders, real OrderManager, real ValidationEngine, real PricingEngine, real repository \u2014 201 returned, order visible downstream.<\/p>\n\n\n\n<p>Each question corresponds to a structural scope. The test suite is organized around the architecture, not around coverage targets.<\/p>\n\n\n\n<figure class=\"wp-block-pullquote\" style=\"font-size:1rem\"><blockquote><p>If you cannot write a unit test for a component without mocking its caller, the component knows too much about where it sits in the system. That knowledge is coupling. The test is surfacing it.<\/p><\/blockquote><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Mock Placement Is Architectural Evidence<\/h2>\n\n\n\n<p>Where you place mocks tells you where your boundaries are. Where you are <em>forced<\/em> to place mocks tells you where your boundaries <em>should<\/em> be.<\/p>\n\n\n\n<p><strong>Mock at the role boundary, not inside the role.<\/strong> Each component role has one natural mock point \u2014 the interface at which it hands off to the next tier. An Engine mocks the Accessor below it. A Manager mocks the Engines and Accessors below it. That is the entire mock surface for those roles.<\/p>\n\n\n\n<p>When a unit test requires mocking more than the single boundary below the component under test, something is wrong. Either the component has absorbed responsibilities that belong at a different tier, or its dependencies are implicit rather than injected, or an Accessor is missing and the component is reaching directly into infrastructure. Mock proliferation is always a structural signal \u2014 not a testing problem, and not one that better mocking frameworks solve.<\/p>\n\n\n\n<figure class=\"wp-block-table hf-iso-table\"><table><thead><tr><th>Role<\/th><th>Unit Test Mock Surface<\/th><\/tr><\/thead><tbody><tr><td>Utility<\/td><td>Nothing \u2014 pure input\/output<\/td><\/tr><tr><td>Resource Accessor<\/td><td>Data source driver only<\/td><\/tr><tr><td>Engine<\/td><td>Accessor below only<\/td><\/tr><tr><td>Manager<\/td><td>All Engines and Accessors below<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Reading the Signals<\/h2>\n\n\n\n<p>Testing difficulty is a signal. The nature of the difficulty points to the specific structural problem.<\/p>\n\n\n\n\n<figure class=\"wp-block-table hf-iso-table\"><table><thead><tr><th>Signal<\/th><th>What it means<\/th><th>Fix<\/th><\/tr><\/thead><tbody><tr><td>Mock proliferation<\/td><td>Unit test mocking more than one or two dependencies. Component spans too many concerns.<\/td><td>Decompose the component or consolidate dependencies behind a single interface.<\/td><\/tr><tr><td>Slow unit tests<\/td><td>Unit tests hitting real I\/O. Something that should be an Accessor is embedded in an Engine.<\/td><td>Extract the Accessor. The Engine should never know the data source exists.<\/td><\/tr><tr><td>Brittle E2E<\/td><td>E2E tests breaking for reasons unrelated to user-visible behavior. Orchestration is making decisions on transient state.<\/td><td>Examine the Manager first. E2E flakiness is a Manager health signal.<\/td><\/tr><tr><td>Inverted pyramid<\/td><td>E2E suite larger than unit suite. Business logic has migrated into Managers or Accessors.<\/td><td>Return business logic to Engines and Flows.<\/td><\/tr><tr><td>UAT surprises<\/td><td>Behaviors no automated test predicted. Structural model doesn&#8217;t reflect product usage.<\/td><td>Treat as an architectural discovery. Revise the structural model, not just the tests.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Practitioner Observations<\/h2>\n\n\n\n<p><strong>The Test Migration Pattern.<\/strong> When architecture improves \u2014 when an Engine is extracted from a Manager, or an Accessor is separated from inline infrastructure calls \u2014 tests naturally migrate from integration scope to unit scope. Logic that previously could only be exercised through the Manager can now be tested directly against the extracted Engine. A system with a growing unit test count and a shrinking integration test count is decomposing correctly. A system where unit tests plateau while integration tests multiply is accumulating orchestration logic in the wrong places. This ratio reveals architectural trajectory more reliably than any static coverage metric.<\/p>\n\n\n\n<p><strong>The Mock Boundary Audit.<\/strong> Periodically reviewing where mocks are placed across the test suite reveals architectural drift before it becomes visible in production. When a unit test that once required a single mock now requires three, a boundary has leaked. The Engine has acquired a dependency it should not have, or a new collaborator was introduced without going through the established interface. The audit is mechanical: list every mock in every unit test, group by component, compare to the expected mock count for that role. Deviations are structural findings that point to specific refactoring targets.<\/p>\n\n\n\n<p><strong>The E2E Stability Correlation.<\/strong> E2E test stability correlates directly with Manager and Experience stability. When E2E tests become flaky \u2014 passing on one run, failing on the next \u2014 the instability almost always traces to the orchestration tier, not to infrastructure. The Manager is making decisions that depend on transient state, or sequencing operations in ways sensitive to timing that unit and integration tests never reach. When E2E flakiness spikes, the first diagnostic step is not to add retries or increase timeouts. It is to examine what changed in the orchestration layer. The E2E suite is a Manager health monitor.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>The next time a test is painful to write, resist the instinct to fix the test. Sit with the discomfort for a moment and ask what it is describing. Mock proliferation is describing a component that spans too many concerns. Slow setup is describing infrastructure that has no business being inside a unit boundary. A cascade of broken tests is describing coupling that was there before the test was.<\/p>\n\n\n\n<p>The test did not create the problem. It found it.<\/p>\n\n\n\n<p><strong>A test suite that is hard to maintain is not a testing problem. It is a structural inventory \u2014 accurate, current, and waiting to be read.<\/strong><\/p>","protected":false},"excerpt":{"rendered":"<p>Boundary-Driven Testing begins with a single observation: testing difficulty is not a testing problem. When a unit test requires a running database, the test is not broken. When a change to a business rule breaks seventeen tests that have nothing to do with that rule, the test suite is not fragile. When a component cannot &#8230; <a title=\"BDT in 10 Minutes: Testing as Architectural Evidence\" class=\"read-more\" href=\"https:\/\/harmonic-framework.com\/es\/bdt-in-10-minutes-testing-as-architectural-evidence\/\" aria-label=\"Read more about BDT in 10 Minutes: Testing as Architectural Evidence\">Read more<\/a><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uag_custom_page_level_css":"","footnotes":""},"categories":[3],"tags":[],"methodology":[],"class_list":["post-179","post","type-post","status-publish","format-standard","hentry","category-tutorials"],"uagb_featured_image_src":{"full":false,"thumbnail":false,"medium":false,"medium_large":false,"large":false,"1536x1536":false,"2048x2048":false,"trp-custom-language-flag":false,"post-thumbnail":false,"hf-card":false,"hf-hero":false},"uagb_author_info":{"display_name":"William Christopher Anderson","author_link":"https:\/\/harmonic-framework.com\/es\/author\/admin\/"},"uagb_comment_info":0,"uagb_excerpt":"Boundary-Driven Testing begins with a single observation: testing difficulty is not a testing problem. When a unit test requires a running database, the test is not broken. When a change to a business rule breaks seventeen tests that have nothing to do with that rule, the test suite is not fragile. When a component cannot&hellip;","_links":{"self":[{"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/posts\/179","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/comments?post=179"}],"version-history":[{"count":11,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/posts\/179\/revisions"}],"predecessor-version":[{"id":711,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/posts\/179\/revisions\/711"}],"wp:attachment":[{"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/media?parent=179"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/categories?post=179"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/tags?post=179"},{"taxonomy":"methodology","embeddable":true,"href":"https:\/\/harmonic-framework.com\/es\/wp-json\/wp\/v2\/methodology?post=179"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}