This felt manageable at first and then kind of sprawled.
Pick a concrete, non-trivial unit (e.g., a pricing engine or retry decorator) and walk through the test design systematically: enumerate cases, isolate dependencies, define assertions, handle non-determinism, and measure quality. Emphasize trade-offs and how you'd prioritize tests for maintainability and confidence.
Pro tip: Frame your answer around risk: focus tests on the code's contract and edge cases that are most likely to break, not on achieving 100% coverage. Mention mutation testing as a way to validate test effectiveness.
Select a specific, non-trivial function or class from production code and clearly state its responsibilities, inputs, outputs, and side effects. This sets the scope for the test suite.
Cover happy paths, edge cases (empty, null, boundaries), error conditions, and state transitions. Use techniques like equivalence partitioning and boundary value analysis to ensure thoroughness.
Identify dependencies (databases, APIs, time, randomness) and replace them with test doubles (mocks, stubs, fakes) or dependency injection. Ensure tests are fast, deterministic, and focused on the unit's logic.
Assert on observable behavior and outputs, not implementation details. For non-determinism (e.g., time, random), inject controlled values or use libraries to freeze/seed them, and assert on ranges or invariants when exact values are impossible.
Use coverage metrics (line, branch) as a guide, but prioritize mutation testing to assess fault detection. Also consider test runtime, flakiness, and maintainability; refactor tests as needed.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.