skip to content

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%

answer

  1. Not every base class is a defect
  2. True of every descendant, no exceptions
  3. Chosen values mean it is not shared
  4. Could any case want it off?
  5. One level deep stays readable

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.

solid answer

~50 s

Four conditions, all of them rather than most: the behaviour is true of every descendant without exception; it takes no value the case picks, since a chosen value means the step is configured rather than shared; no descendant could plausibly want to decline it, because inheritance offers no honest opt-out; and the chain is one level deep, so the effective behaviour is readable in a single hop. Cross-cutting, boring, unconditional work fits — attaching artefact capture on failure, naming the case in the run's own output, establishing a contract that defines what a case in this suite is. Anything a case configures, selects or skips becomes a named unit instead. The test that settles most arguments is the opt-out test: if you can imagine one case wanting the step off, the ancestor is already the wrong home.

code

pseudocode · 14 lines
pseudocode
# ancestor: invariant, unparameterised, nobody declines it
class SuiteCase:
    around_each(run_case):
        result = run_case()
        if result.failed: capture_artefacts(result)
        report(result)
        return result

# named units: anything the case selects
class RefundIsIssued extends SuiteCase:
    run():
        user    = identities.signed_in(role = "support")
        account = accounts.seed(plan = "basic", balance = 0)
        assert_refund(user, account)

go deeper

for a junior

Know that a base class is not automatically wrong. Be able to point at the one or two things your suite's ancestor does that genuinely apply to every case, and say why the rest are different.

for a middle

Explain the conditions that make inheritance honest — invariant behaviour, no value the case picks, no plausible opt-out — and give an example that fits, such as attaching artefact capture to every failing case.

for a senior

An interviewer expects judgement rather than a rule. Argue both sides for one specific step, name the condition that decides it, and say what you would do when a condition that held for years stops holding.

for a principal

Own the standard the team judges by and the discipline of revisiting it: a design that was right with two suites can be wrong at twenty, and the cost of noticing late is paid by everyone beneath the step.

## The question behind the question "Is inheritance a smell in a harness?" is the wrong question, and answering it *yes* is a common way to fail an interview that was actually testing judgement. Inheritance is a mechanism with one distinctive property: **descendants acquire behaviour without asking, and cannot decline it.** Everything good and bad about a base class in a harness follows from that single property. So the useful question is narrow: *is there work here that every descendant needs, unconditionally, and will keep needing?* ## Four conditions — all of them, not most 1. **Universality.** The behaviour is true of every descendant, with no exception today and none you can plausibly imagine. "Almost all" is not a pass: the minority still pays for it, and still cannot decline it. 2. **No value the case chooses.** A step that needs a value picked per case is not shared work, it is configured work. A chosen value is the clearest tell that a step has escaped the ancestor's remit. 3. **No plausible opt-out.** If you can picture a case wanting the step off, inheritance has no honest way to grant it. Every mechanism that appears to — a guard, an emptied replacement, a thin subclass whose only job is skipping — is a workaround that hides a real requirement. 4. **One level of depth.** The effective behaviour of a case should be readable in a single hop. Depth is what turns a defensible ancestor into an archaeology problem. ## What passes, and what does not | Work | Home | Why | |---|---|---| | Attaching failure capture to every case | ancestor | invariant, unparameterised, nobody declines it | | Naming the case in the run's own output | ancestor | a contract about what a case in this suite *is* | | Establishing a signed-in identity | named unit | which identity is a case-level choice | | Seeding account data | named unit | the shape of the data is a chosen value | | Setting feature toggles | named unit | which toggles matter varies by suite | | Standing in for a downstream dependency | named unit | only some cases exercise that path | The pattern in the left column is that the work is **cross-cutting and boring**: it concerns the case as an artefact — how it reports, what it captures, what contract it fulfils — rather than what the case is checking. The pattern on the right is that the work is about the **scenario**, and scenarios differ by definition. ## The opt-out test Most arguments about a specific step are settled by one question: *could any descendant ever want this off?* - If **yes**, the step belongs somewhere a case can decline it by simply not naming it. No amount of hierarchy design fixes this, because declining is exactly what inheritance does not offer. - If **no** — genuinely, not "not right now" — then an ancestor is a reasonable home, and moving the step to a named unit buys ceremony and nothing else. The test is quick, it is answerable without a whiteboard, and it survives disagreement about taste, which is why it is worth saying out loud in a design discussion rather than trading preferences. ## The answer has an expiry date A base class that was correct when the harness had two suites can be wrong at twenty, because the conditions above are statements about the population of descendants, and that population grows: - The first case that wants a step off has falsified condition three, however long the step has been there. - The first chosen value added to a shared step has falsified condition two, even if it defaults sensibly. - A second level added to the chain has falsified condition four, even if each level is small. The honest response is to move the step out to something cases name, accepting that unpicking one that live cases already rely on is a piece of work in its own right with its own sequencing. What is not honest is defending the position with a guard, because a guard keeps the step where it is while quietly admitting it no longer belongs there. ## Over-correcting is also a failure mode The mirror-image mistake is banning ancestors outright and making every case assemble everything by hand. That produces cases which all begin with the same eight lines — not readable either, just a different kind of noise — and it turns a genuine change to how every case reports into a two-hundred-file edit. The defensible middle is narrow and easy to state: a thin ancestor for the invariant, unconditional, unparameterised work; named units for everything a case selects; and a standing rule that new shared work goes into a unit a case can name until someone demonstrates that all four conditions hold.

  • A condition that once held has stopped holding — one suite now wants the step off. What now?
    Read it as the design telling you the step is no longer universal, and stop defending the position with a guard. The step becomes a named unit that the cases wanting it invoke. Doing that on a tree with live dependants is real work with its own sequencing, but the decision itself is simple: the moment an opt-out is needed, the ancestor is the wrong home.
  • Every case now names the same four units. Does that argue for putting them back in an ancestor?
    Only if the four never vary and never will, in which case the repetition buys nothing. The middle answer is usually better: one named composed unit that starts the four, invoked explicitly by the cases that want it. That keeps the requirement visible where the case is read and still leaves any case free to take three of the four.

saying these in an interview costs you the question

  • Says inheritance is always wrong in automation code
  • Keeps a base class because changing the suite would be work
  • Puts a step with a per-case chosen value in an ancestor
  • Judges the design by chain depth alone and ignores opt-out
  • Cannot name a single thing that legitimately belongs in an ancestor