skip to content

Where may an assertion live in a suite layered into cases, shared business actions and protocol adapters?

level: middleimportance: should knowfreq 50%

answer

  1. The case owns pass or fail
  2. Shared steps guard, they do not judge
  3. Adapters translate, they never judge behaviour
  4. A failure should name the claim it broke
  5. Arrangement failures read differently from verdicts

basics

~20 s

Verdicts about product behaviour belong in the case, which names the claim being proved. A shared business action may only guard its own postcondition, reported as a broken arrangement, and an adapter judges nothing about behaviour.

solid answer

~50 s

Keep the **verdict** in the case. The case names the behaviour under test, so the check that decides pass or fail belongs beside that name. A shared business action may still **guard** itself — confirm the record it was asked to create exists before it returns — but that guard should surface as a broken arrangement, not as a judgement about the product. Adapters translate calls and hand back values; they raise only when the transport itself failed. Put a product assertion inside a shared action and every case that used the action merely as arrangement now fails on a claim it never made, while the report points at the shared module instead of the behaviour. The test of a placement: from the failure line alone, can a reader name the broken behaviour and the case that claimed it?

code

pseudocode · 14 lines
pseudocode
case "a returning customer sees the loyalty discount":
    order = actions.place_order(customer, cart)
    assert order.total == 90.00                  # the verdict, beside the claim

action place_order(customer, cart):
    result = adapter.submit(customer.id, cart.lines)
    guard result.created else
        arrangement_failed("place_order: no order created for " + customer.id)
    return order_from(result)                    # a guard, never a verdict

adapter submit(customer_id, lines):
    response = transport.send(build_request(customer_id, lines))
    if response is empty: raise transport_error()   # about the transport only
    return response

go deeper

for a junior

Recall that the case owns the check that decides pass or fail, and that shared steps exist to arrange the situation. Be ready to say why a failure should point at the behaviour that broke.

for a middle

Explain the mechanics: the difference between a verdict and a guard on a step's own postcondition, why a verdict inside a shared step makes twenty unrelated cases fail, and how the two should be reported differently.

for a senior

Show the triage consequence on a real suite: a wave of red cases traced back to one assertion buried in a shared step, how you separated arrangement failures from behavioural ones, and what the result model needed to make that visible.

for a principal

Own the tradeoff between repeating a check in ten cases and centralising it once. Argue when a genuinely universal rule earns a cross-cutting home, and what the team loses in failure attribution when it does.

## Two different things called an assertion A layered suite contains two checks that look identical in code and mean opposite things. - A **verdict** decides whether the behaviour under test is correct. It is the reason the case exists, and its failure means the product is wrong. - A **guard** confirms that a step did what it claims — the record it was asked to create exists, the flow it was asked to run reached the end. Its failure means the arrangement broke, and it says nothing at all about the behaviour the case was going to prove. The placement rule follows directly from the difference: | Layer | Verdict about the product | Guard on its own work | What a failure there means | | --- | --- | --- | --- | | Case | Yes — this is where it belongs | Rarely needed | The claim in the case name did not hold | | Business action | Never | Yes | The flow could not be completed as asked | | Protocol adapter | Never | Only about the transport itself | The product could not be reached | ## Why a verdict inside a shared action is un-attributable Suppose one shared action places an order, and someone adds an assertion inside it that the order confirmation shows a discounted total, because three cases needed that check. The suite now has a particular kind of rot. 1. **The failure names the wrong thing.** The report points at a line inside a shared module. To learn what broke, a reader has to open the action, work out which of its callers was running, and reconstruct what that caller was trying to prove. 2. **Cases fail on claims they never made.** Twenty other cases use that action purely as arrangement — they wanted an order to exist so they could test cancellation. They now fail on a discount rule they never mentioned, and their names lie about what is broken. 3. **The blast radius stops matching the defect.** One product change turns a one-case failure into twenty red cases, and the count no longer tells anyone how much is wrong. 4. **The action stops being reusable.** The next case that wants an order but a different total cannot use the action, so someone copies it, and the copy drifts. The check that catches all four before you write the line: **from the failure alone, can a reader name the behaviour that broke and the case that claimed it?** If the answer requires opening a shared module, the assertion is in the wrong layer. ## Where guards do belong, and how they should read Guards inside actions are not a compromise; they are how a shared step stays honest. An action that returns without confirming its own postcondition pushes a silent failure downstream, and the case then fails somewhere unrelated — usually with a missing-value error three steps later, which is the most expensive kind of failure to read. The discipline is that a guard must be **reported as a different kind of result** from a verdict: - Fail it with a message that says the arrangement did not complete, naming the action and the arguments it was given. - Where the result model supports it, give it a status distinct from a behavioural failure, so triage can separate product defects from broken arrangement without reading code. - Never let a guard grow into a business rule. "The order exists" is a guard. "The order total is discounted" is a verdict, and it belongs to whichever case is claiming it. ## Adapters assert nothing about behaviour An adapter translates a call into a transport interaction and hands back a value. It may raise when the transport itself failed — nothing answered, the response could not be parsed, the element it was asked to operate never became actionable — because that is a statement about the adapter's own job. It must not decide whether the product's answer was the right one. The moment an adapter judges a value, every caller inherits a rule none of them asked for, and the rule is invisible from the case that fails. ## The practical consequences - A shared step that both performs a flow **and** judges its outcome is really two things; splitting it usually costs one line at each call site and repays it the first time triage is fast. - Assertion helpers are fine as a leaf module — a comparison that produces a good message is worth sharing. What is not shareable is the **decision** about which behaviour matters, because that decision is the case's identity. - If a check genuinely must run for every case — every response carries a correlation identifier, no page shows an error banner — that is not a verdict hidden in an action either. It is a cross-cutting rule, and it belongs where the suite states rules for all cases, with a message that says which rule was broken. The final test is the one a report reader applies without knowing any of this: a good failure line names a behaviour and a case. A bad one names a shared step and leaves the reader to guess.

  • A guard inside a shared business action fails. How should that read differently from a failed verdict?
    It should report as a broken arrangement, naming the action and the arguments it was given, and where the result model allows it, carry a status distinct from a behavioural failure. Triage can then separate product defects from setup breakage without opening code, and a wave of arrangement failures reads as one environment or data problem rather than twenty defects.
  • Twenty cases share one action that asserts on the product. What is your first move?
    Move the assertion into the one case whose name actually claims that behaviour, and leave the action with a guard on its own postcondition. If several cases genuinely wanted the check, give them each an explicit line — the duplication is small and it restores what a failure means. Then look at whether the action was doing two jobs.
  • Is a check that must run for every case an exception to the rule?
    No. A rule that applies to all cases — no error banner appears, every response carries a correlation identifier — is a cross-cutting rule, not a hidden verdict inside a shared step. Express it where the suite states rules for all cases, and make its message say which rule was broken so the failure is still attributable.

A shared step is a shared tool. A tool that decides whether your work is acceptable will veto jobs it was never told about.

saying these in an interview costs you the question

  • Puts product assertions in shared steps to avoid repeating them
  • Says any failing assertion is equally diagnosable wherever it sits
  • Lets an adapter fail the case when a returned value looks wrong
  • Treats an arrangement failure and a verdict as the same result
  • Counts shared assertions as evidence of coverage