skip to content

questions

4

A shared base class prepares every case in a suite — what does a case pay for setup it never uses?

level: juniorimportance: must knowfreq 62%

answer

  1. Nobody opted in
  2. The steps run whether needed or not
  3. Time cost multiplied by case count
  4. Unrelated preparation failure reddens the case
  5. One commit at a time, never removed

basics

~10 s

An 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 s

Inherited 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 lines
pseudocode
class 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 them

go deeper

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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
open as a page

Why does a four-level base-class chain stop an automated case from declaring the setup it needs?

level: middleimportance: should knowfreq 54%

basics

~20 s

Depth converts a requirement into a location: what a case gets is decided by where it sits in the chain, not by anything it states. Composition returns the declaration — the case names the units it uses.

open as a page

Adding a per-case setup step to a subclass breaks cases that passed before — how does inherited setup ordering explain it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Preparation spread across a class chain runs in an order no single file states; it comes from the runner's rule and from whether each level invokes its parent. A new step can land ahead of what it assumed.

open as a page

When is a shared base class still the right reuse mechanism in an automation harness, rather than a defect?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

Inheritance earns its place when the work is invariant across every descendant, takes no value the case chooses, and no case could ever want it off — failure capture and result reporting qualify. Once an opt-out is thinkable, compose instead.

open as a page