skip to content

What do you gain by passing a case's test data into a helper as parameters instead of letting the helper read a shared holder?

level: juniorimportance: should knowfreq 40%

answer

  1. Everything it uses arrived through the call
  2. Read one signature, know every input
  3. Same helper, different data, no setup ritual
  4. The price is threading values through

basics

~20 s

The helper's inputs become visible in its signature: a reader knows what it needs, a caller can supply different data, and nothing outside the call can change it midway. The cost is longer signatures on every step of the chain.

solid answer

~50 s

Explicit parameters make the data flow provable by reading. If everything a helper consumes arrived through its parameters, then reading the helper tells you its complete set of inputs — no setup hook to check, no surrounding scope to know about. That has practical payoffs: the same helper serves two cases with different data and no ritual in between, it can be called from a seeding script or a small reproduction program with no harness around it, and two cases running side by side cannot disturb each other's values because nothing is shared. The price is verbosity: a value needed five calls deep is threaded through five signatures, and adding a sixth touches them all again. The usual answer is to name the values that always travel together, pass that one object, and keep genuinely run-wide facts out of the chain.

code

pseudocode · 14 lines
pseudocode
# implicit: reads whatever the harness happened to set
create_invoice(amount):
    account = shared.current_account
    region  = shared.current_region
    return build_invoice(account, region, amount)

# explicit, threaded
create_invoice(account, region, amount):
    return build_invoice(account, region, amount)

# explicit, grouped once the values always travel together
# actor = { account, region, locale }
create_invoice(actor, amount):
    return build_invoice(actor.account, actor.region, amount)

go deeper

for a junior

Be able to give the plain version: a helper that receives what it needs can be read and called on its own, and a helper that reaches for a shared value cannot. Naming the readability benefit is enough here.

for a middle

Name both sides. Explain how values get threaded through intermediate calls that never use them, and when grouping them into one named object is the better answer than either extreme.

for a senior

Show judgement about which values earn a place in a signature and which are run-wide facts, and describe how you would migrate an existing suite without one rename touching every call site.

for a principal

Own the convention. State what may be ambient in this harness at all, make explicit the default, and make sure each new exception is a recorded decision rather than a habit that spreads.

Passing what a helper needs as arguments is the oldest advice in programming, and it earns its keep in a test harness for a reason worth stating precisely: it makes the data flow of a case **provable by reading**. ## What provable means here If every value a helper consumes arrived through its parameters, then reading the helper tells you its complete set of inputs. There is nothing else to check — no setup hook, no base type, no surrounding scope. Three practical consequences follow. - **A reader is never wrong about what it needs.** The signature is the contract, and it cannot be quietly extended by something the helper reaches for. - **A caller can supply anything.** The same helper serves a case that needs a fresh account and a case that reuses an existing one, with no ritual in between. - **The helper runs anywhere.** A data-seeding script, a small reproduction program, or a single case run outside the full suite can call it directly, because the harness is not part of its requirements. There is a fourth consequence that matters once the suite runs on more than one test worker: values that live only on the call stack cannot be replaced by another case, because no other case can reach them. That is a real benefit, but it is a consequence of the design rather than its motivation. ## What it costs, honestly The cost is **threading**. A value needed five calls deep travels through five signatures, including intermediate calls that do nothing with it except hand it on. Adding a sixth value touches every one of those signatures again, and the diff makes a small change look large. Reviewers see churn; authors feel the friction; and the shared holder starts to look attractive again. That friction is real and should be answered rather than dismissed. ## The comparison | Approach | Inputs visible | Reusable with no harness | Cost of one more value | | --- | --- | --- | --- | | Helper reads a shared holder | no | no | one line | | Every value threaded as a parameter | yes | yes | every signature on the path | | Values grouped into one named object | yes | yes | one field, one construction site | ## The usual middle position When several values always travel together — the acting account, the target address, the locale a case runs under — they are not three parameters, they are one concept nobody has named yet. Name it, construct it once at the top of the case, and pass that single object down. Signatures stay short, inputs stay visible, and adding a fourth value touches one type and one construction site instead of a whole call chain. Two guard rails keep this from turning back into a shared holder in disguise: 1. **Keep it small and specific.** A parameter object with a name like *the case's actor* stays honest. One called *the context* accumulates everything, and within a few months a reader can no longer tell what a helper actually uses. 2. **Never pass the harness itself.** Handing a helper an object from which it can reach anything — the runner, the configuration, the results collector — restores every problem explicit passing was meant to remove, while looking like a parameter. ## What explicit passing does not buy you It removes one class of hazard: the harness's own values can no longer be shared by accident. It does not make cases independent by itself. Two cases that both mutate the same record in the deployed target, or both depend on a global setting they toggle, still interfere with each other however clean the signatures are. Explicit passing is a statement about where the harness keeps its data, not a guarantee about the product's data. ## How to move an existing suite Do it by value, not by sweep. Pick the value with the most readers, add it as a parameter with the helper still falling back to the shared holder, migrate call sites in a few small changes, then delete the fallback and the holder in one final change that cannot be partially applied. The intermediate state is ugly for a week and reviewable throughout, which is a far better trade than one rename that touches four hundred call sites and cannot be reasoned about at all. Judge the result by a simple test: pick a helper at random, read only its signature and its body, and say what it needs. If you can, the flow is provable. If you have to open another file, it is not.

  • A value is needed five calls deep. Do you thread it or reach for a holder?
    Thread it, but look for the group first. If those five calls all need the same three values, those values are one concept that has not been named; name it and pass one object. Reaching for a holder buys short signatures and pays with an input nobody can see. The exception is a genuinely run-wide fact no caller would ever choose.
  • Does explicit passing on its own make a suite safe to run in parallel?
    It removes one whole class of hazard, because values on the call stack cannot be reached or replaced by another case. It says nothing about resources outside the harness: two cases mutating the same record, sharing one account, or toggling the same setting still interfere however clean the signatures are. Explicit passing is a statement about the harness's data, not the product's.
  • Why is passing the harness object itself as the single parameter a bad answer?
    Because it looks explicit and behaves like a shared holder. A helper handed an object from which it can reach the runner, the configuration and the results collector declares one input and actually consumes any of them, so a reader still cannot tell what it uses. Pass the values, or a small named group of them, never the doorway to everything.

A recipe that lists its ingredients can be cooked in any kitchen. One that says the usual flour only works in the kitchen where it was written.

saying these in an interview costs you the question

  • Says parameters are always better without naming a cost
  • Threads ten separate values instead of naming the concept
  • Claims explicit passing alone makes any suite parallel-safe
  • Cannot say what a shared holder actually buys the author
  • Passes the whole harness object as the one parameter