A shared base class prepares every case in a suite — what does a case pay for setup it never uses?
answer
- Nobody opted in
- The steps run whether needed or not
- Time cost multiplied by case count
- Unrelated preparation failure reddens the case
- One commit at a time, never removed
basics
~10 sAn unused inherited setup step costs runtime and reliability: it still runs on every execution, so the case is slower and can fail for reasons unrelated to what it checks.
solid answer
~50 sInherited preparation is unconditional: if the base class signs a user in, seeds account data and starts capture, every descendant pays for all three whether or not it touches them. The direct cost is wall-clock time multiplied by the case count. The larger cost is a widened failure surface — a case that only checks a validation message goes red when the seeding step breaks, and the report names the case, not the step. Diagnosis slows too, because the reason each step exists is not written anywhere the case can see; a reader has to walk the chain upward to learn why an account exists. Ancestors reach this state one commit at a time: each new suite needs one more thing, the shared parent is the cheapest place to put it, and nothing is ever removed.
code
pseudocode · 13 linesclass CaseBase:
prepare():
sign_in(default_user) # added for the checkout suite
seed_account(plan = "basic") # added for the billing suite
set_toggle("new_nav", on) # added for the navigation suite
start_capture() # added after a red week
class EmptyEmailIsRejected extends CaseBase:
run():
open_signup_form()
submit()
expect_message("email is required")
# inherits four preparation steps, reads none of themgo deeper
Be ready to say what your own case inherits and why. Know that preparation defined in a parent class runs whether your case uses it or not, which makes your case slower and gives it more ways to fail.
Explain the two distinct costs — runtime multiplied by the case count, and a failure surface that reddens cases which never touched the broken step — and describe how the ancestor accumulated its steps one commit at a time.
An interviewer expects you to spot the pattern in review before it hardens: chosen values and guards inside shared preparation, thin subclasses that exist only to skip a step, and preparation that outweighs the behaviour being checked.
Own the rule that decides where new shared preparation lands, and argue it in cost terms: the blast radius of a preparation defect equals the size of the subtree beneath it, so implicit reuse prices every future change.
## What inheriting setup actually means Most harnesses put shared preparation in a **base class**: a class every case class extends, holding steps the runner executes before each case. A descendant acquires those steps simply by existing in the chain. It does not opt in, it does not list what it received, and it usually cannot see the steps without opening the ancestor and reading it. That single property — **acquisition without declaration** — is what makes the arrangement so cheap to add to and so expensive to live in. Adding a step to the ancestor is one commit and immediately serves every suite. Nothing in the design pushes back. ## How one ancestor ends up carrying everything The growth is never a decision; it is a sequence of individually reasonable ones: 1. The first suite needs a signed-in user. Sign-in moves into the base class, two suites share it, and the change is an obvious win. 2. A second suite needs seeded account data. Putting it in the shared ancestor is a one-line change; giving that suite its own parent is a refactor with a review attached. The one-liner wins. 3. A third suite needs a feature toggle flipped. Same argument, same outcome. 4. A red week prompts someone to add artefact capture — a screenshot, a log tail, a request trail — to the ancestor, because it is the one place guaranteed to run for everything. 5. The suite that needed step 2 is deleted, but the seeding step stays, because nobody can prove that no remaining case depends on it. Every commit is locally correct. The accumulated result is an ancestor whose steps no individual case can justify, and which nobody feels entitled to touch. ## The four bills a case pays **Runtime, multiplied.** Suppose sign-in costs 3 seconds, seeding 2 and toggling 1, and the suite holds 400 cases. That is 40 minutes of preparation per run, and for a case that only checks a validation message on an anonymous form, its whole share of that is waste. Parallel execution hides the wall clock but not the machine cost. **A widened failure surface.** Every inherited step is another way for an unrelated case to go red. When the seeding call starts returning an error, every case beneath the ancestor fails — including the ones that never read seeded data — and the report names the cases, not the step. The blast radius of a preparation defect equals the size of the subtree beneath it. **Slower diagnosis.** The reason each step exists is not written down anywhere the case can see. A reader who wants to know why an account exists before their case runs must walk the chain upward, and the commit that added the step is usually years old with a message that explains nothing. **Lost isolation.** A case that inherits four preparation steps cannot be lifted into another suite, cannot be run against a target where one of those steps is impossible, and cannot be read on its own. Its real preconditions are unstated, so nothing can check that they still hold. | Property | Setup inherited from an ancestor | Setup named by the case itself | |---|---|---| | How it is requested | implicitly, by extending | explicitly, by naming it | | Where you read it | walk the chain upward | at the top of the case | | Cost when unused | paid on every run | not paid at all | | Failure attribution | the case is blamed | the named step is blamed | | Adding one more | one line, affects everything below | one line, affects one case | ## Symptoms worth naming in review - The ancestor's preparation is longer than the average case body. - A step is guarded by a value descendants set, which is the hierarchy admitting the step is not universal. - Someone has added a thin subclass whose only purpose is to skip part of the ancestor. - Deleting a step is blocked because nobody can enumerate its dependants. - Average case runtime is dominated by preparation rather than by the behaviour under check. ## Keeping the next step out Prevention is much cheaper than cure, and it is a rule about **where new shared code lands** rather than a rewrite: - New shared preparation goes into a small named unit that a case calls by name; the ancestor does not grow. - Anything that is not true of *every* descendant does not belong above them. - A step that needs a value the case chooses is case-specific by definition — parameters are the tell. - Keep the ancestor to work that is genuinely invariant and unconditional, and prefer one level of depth so the effective behaviour is one hop away. Unpicking a step that live cases already rely on is a separate and larger piece of work with its own sequencing. The point here is to stop the pile growing while you still have the choice.
- How would you measure what the inherited preparation actually costs this suite?Time the preparation separately from the case body and emit each step's duration as part of the run's own output. Multiply the per-case total by the case count, then split it by which steps the case actually reads. The gap between those two numbers is the waste, and it is usually large enough to end the argument without appealing to taste.
- A step in the ancestor is guarded by a value that subclasses set. What is that telling you?That the step was never universal, and the hierarchy is being asked to express something it cannot. A guard is an opt-out bolted onto a mechanism whose whole premise is that descendants do not choose. The honest form is a named unit the case invokes when it wants the behaviour; the guard form hides the default and grows a second guard the next time.
- Why is deleting an inherited step harder than adding one?Because addition is verified by the suite going green, while deletion has to prove a negative: that nothing below relies on it. Nothing records which descendants read the state a step produced, so the only evidence is a full run — and if a case fails afterwards, the cause looks like the deletion rather than the declaration that was always missing.
A shared ancestor is like a service charge added to every room in a hotel: each addition was justified by one guest, and everyone pays it forever.
saying these in an interview costs you the question
- Says shared preparation is always good because it removes duplication
- Claims unused inherited steps are free because they are fast
- Blames the failing case rather than the preparation step that broke
- Treats the base class as the natural home for any new shared step
- Cannot say which inherited steps their own case actually depends on