skip to content

You asked for unit tests on a leave-accrual calculator and got only ordinary dates. What should the request have named?

level: middleimportance: should knowfreq 48%

answer

  1. More tests gets more of the middle
  2. The interesting case is the missing one
  3. Name situations as rules, not as values
  4. Your list doubles as the acceptance check

basics

~20 s

Name the situations, not the quantity. A model drafting from the code produces cases the code already implies. The awkward ones live in the rule, so list them yourself: the period that ends before it starts, the joiner on the boundary.

solid answer

~50 s

Ask for tests and you get tests for the code as written. A drafting model works from what it can see - the signature, the body, the names, whatever sits nearby - and from those it can infer typical inputs and the branches somebody already wrote. The cases worth the most are often the ones the code never considered, because a missing case and a missing behaviour are usually the same omission, and nothing in the code implies a situation it does not handle. So name them, as rules rather than as values: the last day of the policy year, a period whose end precedes its start, service that has not completed a month, a joining date that does not exist in every year. Then read the returned suite back against your own list - the cases that did not come back are the interesting ones.

code

text · 16 lines
text
WEAK REQUEST
  Write unit tests for accrue(joined, as_of). Cover the edge cases.

NAMED REQUEST
  Write unit tests for accrue(joined, as_of). One test per case below,
  each named after its case, and tell me which ones you could not write
  and why:
    - joined on the first day of the policy year
    - joined on the last day of the policy year
    - as_of falls before joined
    - as_of one day before the first month of service completes
    - a period crossing the policy-year boundary, where accrual stops at it
    - a joining date that does not exist in every year

  Do not take expected values from the implementation; leave any
  expectation you cannot derive from the rule above marked TODO.

go deeper

for a junior

Remember that a request for tests gets you tests for the code as it stands. If you want the awkward situations, write them into the request as situations.

for a middle

Explain why the missing case and the missing behaviour are often the same omission, and show the difference between naming a value and naming a rule.

for a senior

Demonstrate the loop: a list written from the rule, one test per case, an explicit ask for refusals, then the returned suite read back against the list.

for a principal

Decide where the case list lives for work your team repeats - who writes it, where it is kept, and how a refusal from a drafting run reaches the people who own the design.

## Why "cover the edge cases" returns the middle of the range A drafting model produces cases from what it can see. Handed a function, that is the signature, the body, the names, and whatever else was in the request. What it can infer *reliably* from that material is what the code already accounts for: the branch that exists, the guard that is written down, the shape the parameters force. Beyond that it is guessing from the domain the names suggest - sometimes a good guess, since a model carries plenty of general knowledge about dates and money, but a guess is not a list, and nothing in the output says which situations were considered and passed over. Ask for **edge cases** as a phrase and what tends to come back are the edges that are generic to code - empty, absent, very large, zero - because those need no knowledge of the domain at all. The cases that would have caught the defect are somewhere else entirely. **A missing case and a missing behaviour are usually the same omission**: nobody handled a period that ends before it starts, so there is no branch for it, so nothing in the code implies a test for it. That is not carelessness in the tool. It is what generating from context means, and it is the most useful sentence to carry away from this subject: *what it could see determines what it could get right.* Hand it the written policy or the ticket alongside the code and it does better - what you supply up front is its own subject - but hand it the code alone and it will test the code alone, faithfully. ## Name the situation, not the value | a weak ask | what tends to come back | the named case | what the named case can catch | | --- | --- | --- | --- | | "cover the edge cases" | empty, absent and extreme values for the types in the signature | "a period that ends before it starts" | a rule nobody implemented | | "test all the branches" | one test per branch that exists today | "service that has not completed a month" | a boundary counted from the wrong end | | "add some more tests" | more of the range that is already covered | "the last day of the policy year, where accrual stops" | a limit the code runs straight past | | "test the date handling" | a spread of plausible dates | "a joining date that does not exist in every year" | arithmetic that assumes every date recurs | The column that matters is the third. **State each case as a rule, not as a value.** "Test with 29 February" pins one fixture and goes stale; "a joining date that does not exist in every year" states the situation, survives a change of test data, and tells the next reader why the case is in the file at all. ## Your list is also the acceptance check 1. Write the list **before** the request, from the rule rather than from the code. That is the part of the work generation cannot do for you. 2. Ask for one test per case, each named after its case, so the mapping back to your list is mechanical. 3. Ask it to say which cases it could **not** write, and why. 4. Read the suite against the list. Cases that came back missing, or quietly merged into one test, are the ones to look at first. 5. A merged pair usually means the code treats the two situations identically. Sometimes that is correct and sometimes it is the defect - either way you have learnt something the coverage report would not have told you. ## What a refusal is worth A reply that says a case cannot be written is often the most valuable line in it. Usually it means the code offers no way to reach the situation: no constructor takes a period that runs backwards, no path leads to a joiner with no completed month. That is a design finding rather than a test finding, and it is exactly the kind of thing that does not show up when tests are only ever written for paths that exist. ## Three ways the named list goes wrong - **Too many cases in one request.** A long list tends to come back flattened - several situations folded into one test with several assertions, which is harder to read and harder to attribute when it fails. Ask in batches you can review in one sitting. - **Naming values instead of rules.** A value can be satisfied while the rule is ignored: a test that uses the last day of the year proves nothing if the assertion it carries would pass on any date. - **Naming one case three ways.** Three restatements of the same boundary buy one test's worth of detection and three tests' worth of maintenance. ## What naming the cases does not do It gets you the right situations; it does not get you the right expectations. A well-chosen case can come back with an expected value lifted straight from the code, in which case you have an excellent situation and no check at all. The case list decides **what gets exercised**; verifying what each test asserts is a separate pass, and it is the one that decides whether any of it is evidence.

  • Does naming the cases yourself not remove the point of generating the tests?
    No, because the expensive part of a test was never the typing. Deciding what must be checked is the part that needs the domain; the setup, the fixtures, the boilerplate and the arrangement of each case are still work you get for free, and a named list makes what comes back reviewable in a way an open-ended batch is not.
  • What do you do with a case the model says it could not write?
    Read it as a report about the code rather than about the tool. Most often the situation cannot be reached - nothing constructs a period that runs backwards, no path produces a joiner with no completed month. Ask for the reason, check it yourself, and treat a confirmed one as a design question for the function, not a gap in the suite.

saying these in an interview costs you the question

  • Say cover all edge cases and the tool works out which ones matter.
  • If the code has no branch for it, there is nothing to test.
  • More generated tests means the boundaries are covered.
  • Listing the cases yourself defeats the point of generating them.
  • A model that has seen the whole repository does not need the cases named.