skip to content

questions

4

In an automated case, why should record provisioning happen in a setup step rather than between the assertions?

level: juniorimportance: must knowfreq 62%

answer

  1. Two kinds of work, two kinds of failure
  2. A broken premise is not a defect
  3. One block builds, one block checks
  4. Blocked is not the same as failed
  5. Ask whether the product told you anything

basics

~10 s

Setup that breaks is not a failed check. Keeping provisioning in one step before the assertions lets a run report could-not-build-the-premise separately from the-product-behaved-wrongly — two different problems with two different owners.

solid answer

~40 s

A case does two kinds of work: building the state it needs, and checking what the product does with it. Mixed together, every failure looks the same — the run reports a failed case, and a reader spends an hour on product behaviour for what was really a provisioning call timing out. Separating them makes failures self-classifying: a break in the setup step means the case could not run, a break after it means the product is wrong. The separation also keeps the checks readable, because the reader sees what is being verified without wading through creation calls. And it bounds what a run can do about it — a single setup block can be reported as blocked, or its records reused, without touching assertions that already passed.

code

pseudocode · 20 lines
pseudocode
# mixed: whichever line breaks, the run reports the same thing
case "second order appears in history":
    a = provisioning.account()
    assert history(a).is_empty()
    o1 = provisioning.order(a)      # if this times out -> reported as failed
    assert history(a).count() == 1
    o2 = provisioning.order(a)
    assert history(a).count() == 2

# separated: a break in setup is reported as blocked, not as a defect
case "second order appears in history":
    setup:                          # premise - not under test
        a  = provisioning.account()
        o1 = provisioning.order(a)
    act:                            # part of the scenario, named as an action
        o2 = provisioning.order(a)
        page = app.open_history(a)
    check:
        assert page.rows().count() == 2
        assert page.rows().contains(o1.id, o2.id)

go deeper

for a junior

Be ready to point at the part of your case that builds what it needs and the part that checks behaviour, and to keep them apart when you write a new one.

for a middle

Explain why a break while building the premise means something different from a failed check, and what a run should report for each so triage lands with the right owner.

for a senior

Show the triage angle. An interviewer expects you to describe reading a red run and separating the product is wrong from we could not obtain the data, and to name what the suite must emit for that to be possible.

for a principal

Own the reporting contract. Statuses that separate blocked from failed only pay off if every case respects the boundary, so it has to be enforced by the harness rather than requested in a style guide.

Almost every automated case contains two activities that look alike in code and mean completely different things when they fail. Keeping them in separate parts of the case is one of the cheapest structural decisions available, and one of the few whose payoff is visible in every red run afterwards. ## Two kinds of work in one case The first activity is **building the premise**: obtaining the records the case needs so the behaviour under test is even reachable. The second is **exercising and checking**: doing the thing, then verifying what the product did. The premise is not under test. Nobody wrote the case to find out whether an account can be created; they wrote it to find out what happens to an account in a particular state. That distinction is what makes the placement rule more than tidiness. A failure while building the premise is a statement about the suite's supporting machinery or the target it runs against. A failure after the premise is established is a statement about the product. Those two statements have different owners, different urgency and different fixes. ## Why a mixed case cannot be triaged | | Break while provisioning | Break in a check | | --- | --- | --- | | What it means | The case never got to run | The product did something wrong | | Who acts | Whoever owns the seam or the target | Whoever owns the behaviour | | What the run should report | Blocked, or errored | Failed | | What a mixed case reports | Failed | Failed | | Cost of the confusion | Time spent reading product code for a data problem | Real defects diluted in noise | The last row is the expensive one, and it compounds. Once a run reports thirty failures of which most are provisioning trouble, the honest response — read every failure — stops being affordable, and the suite starts being skimmed. Separating the two kinds of work is what keeps a red count meaningful. ## Blocked is a status, not a nicety A result store that carries a blocked status distinct from failed lets a run say something true that a single status cannot: *two cases found defects, thirty could not obtain their records*. That sentence changes what happens next. It routes work correctly, it stops a provisioning outage from reading as a product regression, and it keeps failure trends honest over time — a suite whose blocked count is climbing has an infrastructure problem that a pure pass rate hides. The structure of the case is what makes the status assignable. A run can only classify a failure by where it happened, so the boundary has to exist in the case before the reporting can use it. ## The honest exception Sometimes a record genuinely has to be created partway through, after an earlier check. A case verifying that a second order appears in the history has to create that second order between two assertions. That is not a violation of the rule, because the creation is part of the scenario being exercised rather than a premise being established. The repair is to name it for what it is: an action step, reported as an action. What must not happen is the creation being tucked inside the assertion block for brevity, where a failure will be reported as a failed check on behaviour nobody tested. The test to apply is a single question: **if this call fails, has the product told me anything?** If yes, it is part of the scenario. If no, it is a premise and it belongs in the setup step. ## Making the boundary structural Style guides do not hold this line; structure does. A few habits that make it stick: - **Give the case named sections.** Whatever the harness offers — distinct blocks, distinct methods, a lifecycle step that runs before the case body — the boundary should be visible without reading the calls. - **Let a setup failure raise a different kind of error from an assertion failure.** That is what a run needs in order to assign a status without parsing messages. - **Keep the setup step free of checks.** A verification inside setup reintroduces exactly the ambiguity the split removed. If a premise genuinely needs verifying, let it fail as a setup error, not as an assertion. - **Keep the checking section free of creation.** Anything created there should be an action the case is deliberately taking, named as one. - **Watch the length of the setup step.** A block that grows past a handful of lines usually means the provisioning vocabulary is missing a request the case had to assemble by hand. ## What it buys in the end The gain is not aesthetic. It is that a reader who opens a red run can tell, without opening any code, whether the product is broken or the data was. That reader is usually you, on a Monday, looking at a run from Friday night — and the version of the case that mixed its work is the one you will spend the morning on.

  • A case genuinely needs a new record created halfway through, after its first check. Where does that call belong?
    In a named action step, not in the checking block. Creating a record partway through is part of the scenario being exercised rather than a premise being established, so it should read as an action the case takes and be reported as one. The question that settles it: if the call fails, has the product told you something? If yes it is an action, if no it is a premise.
  • How should a run report a case whose setup step broke?
    With a status distinct from failed — blocked or errored — so failure trends and triage do not treat it as evidence about the product. A run where thirty cases were blocked by one provisioning outage and two genuinely failed tells a very different story from thirty-two failures, and only separated statuses can tell it.
  • Is a verification inside the setup step ever acceptable?
    Only as a guard that fails the way setup fails, never as an assertion. Checking that the premise really holds before proceeding is reasonable and often kind to the next reader, but if it raises the same kind of error an assertion does, the run reclassifies a data problem as a product defect and the whole separation is undone.

A kitchen marked down because the delivery van never arrived has learned nothing about its cooking. The inspection needs to record a missed delivery, not a bad dish.

saying these in an interview costs you the question

  • Reporting a provisioning failure as a product defect
  • Interleaving record creation with the checks for brevity
  • Treating every red result in a run as the same signal
  • Assuming a reader can tell setup breakage from a defect
  • Putting assertions inside the setup step
open as a page

Why should an automated case ask its provisioning seam for an account already in the condition it needs, rather than name a known record identifier?

level: middleimportance: should knowfreq 55%

basics

~20 s

Naming the condition — an account whose trial has ended — lets the seam satisfy it on any deployed target and puts the case's premise in the case. A pinned identifier assumes a record whose meaning can drift unnoticed.

open as a page

Why is the provisioning port an automated case obtains its records through worth designing before the implementation behind it?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The port is the contract every case is written against; the mechanism behind it is replaceable. Design the request vocabulary first and cases survive when record creation moves from direct writes into the store to the product's own creation interface.

open as a page

What should a provisioning call hand back so an automated case can prove which records it created?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

A handle rather than a bare identifier: the record asked for, the identities of everything created alongside it, and a tag naming the case and run that asked. That tag is what makes ownership answerable after the run has ended.

open as a page