Python Module 12
Testing, debugging and code quality interview questions
Interview questions on testing, debugging and code quality: warm-ups, then what each harder question tests and how a good answer starts.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
What is the difference between a unit test and an integration test?
-
Fresher
What does doctest check, and how do you run the doctests of a module?
Answered in the lesson doctest: examples that test themselves
-
Fresher
What is the difference between setUp() and setUpClass() in unittest?
Answered in the lesson unittest: test cases, fixtures and discovery
-
Fresher to mid
What is the difference between a linter and a formatter, and why do most Python projects use both?
Answered in the lesson Ruff and pre-commit: lint and format Python
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
A test for send_reminders() passes from Monday to Friday and fails every Saturday and Sunday, although nobody changed the code. What is going on, and how would you change the code and the test so that the result no longer depends on the day the suite runs?
- What it tests
- Spotting a hidden input such as the clock, turning it into a parameter, and writing tests that fix the day, weekend boundaries included.
- First step
- Look for what the test does not control. The function almost certainly reads date.today() or datetime.now() itself, so its answer changes with the day the suite runs. Name that hidden input first, then show how passing the day in as a parameter lets one test choose a Friday, a Saturday and a Monday.
- Follow-ups
-
- The function is called from 30 places. How do you change its signature safely?
- What changes if the rule depends on the user's time zone, not the server's?
- When would you freeze the clock with a library or a mock instead?
From the lesson Why and how to test Python code
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Your project has 60 end-to-end tests that take 25 minutes in CI, one of them fails at random about once a week, and there are almost no unit tests. How would you rebalance the suite, and what would you measure to know that it worked?
- What it tests
- Test strategy: what each kind of test is for, moving checks to faster tests, handling a flaky test, and judging a suite with numbers.
- First step
- Start from what the end-to-end tests actually check. Most of them test rules and calculations through the whole program, and those checks can move down into unit tests that run in milliseconds. Keep a handful of end-to-end tests for the journeys that must never break, and agree on a time budget for the suite.
- Follow-ups
-
- How do you find the flaky test, and what do you do with it today?
- The product team trusts the end-to-end tests most. How do you win them over?
- Which numbers would you track for the suite, and what targets would you set?
From the lesson Why and how to test Python code
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A doctest of a function that returns a set of usernames passes on your laptop and fails in CI about half the time, with the same Python version. What is happening, and how do you fix both the example and the habit behind it?
- What it tests
- Hash randomisation and set order, why doctest compares text, and writing examples whose output is the same on every run.
- First step
- Point at what the example compares: printed text. A set prints in iteration order, which follows the hashes of its strings, and Python seeds those hashes at random in every new interpreter, so each CI run is a new draw. Then fix the example by printing sorted(...) instead of the set itself.
- Follow-ups
-
- Why not set PYTHONHASHSEED=0 in CI and move on?
- Would a dict have the same problem?
- How would you find other examples with the same fault in a large codebase?
From the lesson doctest: examples that test themselves
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A teammate proposes replacing the project's unit tests with doctests "because they double as documentation". Where would you agree, and where would you push back?
- What it tests
- Knowing what doctests do well and badly: documentation, setup, many cases, varying output and failure messages, and how the two kinds of test run together.
- First step
- Agree with the goal first: checked examples in docstrings are valuable and cheap. Then separate examples written to teach from checks written to catch bugs, and show where doctests struggle: setup such as files or databases, dozens of edge cases, output that varies between runs, and failures that only compare text.
- Follow-ups
-
- How many examples would you allow in one docstring?
- How do you make sure the doctests run in CI with the unit tests?
- A doctest needs a temporary file. What do you do?
From the lesson doctest: examples that test themselves
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A doctest that expects InvalidCoupon passes when you run python coupons.py but fails under python -m doctest coupons.py and under pytest --doctest-modules. Why, and what are two ways to write the example so that it passes everywhere?
- What it tests
- How doctest compares an exception, how a module's name depends on the way it is run, and portable ways to check an exception.
- First step
- Look at the last line doctest compared. Run as a script, the module is called __main__ and the class prints as InvalidCoupon; imported by doctest or pytest, it prints as coupons.InvalidCoupon. The expected line matches only one way of running, so the example must stop depending on the module's name.
- Follow-ups
-
- IGNORE_EXCEPTION_DETAIL also ignores the message. When is that a problem?
- Why does the stack between the traceback header and the last line never matter?
- How would you check the message in a unit test instead?
From the lesson doctest: examples that test themselves
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Your unittest suite passes when each test file runs on its own but fails when CI runs python -m unittest on the whole project. Two test files change the same settings dictionary. How do you prove the cause, and how do you fix it for good?
- What it tests
- Diagnosing order-dependent tests, shared state between test modules, fixture design, and keeping tests isolated as a suite grows.
- First step
- Reproduce it deterministically first: run the failing test alone with -k, then with the one test file that runs before it, and confirm that the pair fails in that order and passes the other way round. That proves a test leaves state behind; then find what it changes, here a module-level settings dictionary.
- Follow-ups
-
- The settings object is used by 200 tests. How do you fix them all without editing each one?
- How would you stop a new test from mutating shared state next month?
- Would running the tests in a random order help, and what would it cost?
From the lesson unittest: test cases, fixtures and discovery
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
After CI moves from Python 3.11 to 3.12, 140 tests fail with AttributeError: object has no attribute 'assertEquals'. What happened, how do you fix the codebase safely, and how do you hear about the next removal before it breaks the build?
- What it tests
- Reading an error after an upgrade, knowing that deprecated aliases are removed, fixing many files mechanically, and using deprecation warnings as an early signal.
- First step
- Read the error as a fact about Python, not about the tests: assertEquals was a deprecated alias of assertEqual, and Python 3.12 removed it along with other old aliases. The fix is a mechanical rename, so the questions are how to do it safely across 140 tests and how to see such removals coming.
- Follow-ups
-
- Which other aliases went at the same time, and how do you find them all?
- How do you make deprecation warnings visible in the test run?
- The project must still run on Python 3.8 for one customer. Does the fix still work?
From the lesson unittest: test cases, fixtures and discovery
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A new CI job has been green for a month, and you discover that it ran 0 tests: the tests sit in a tests folder. What are the likely causes, and how do you make sure that a run with no tests can never pass again?
- What it tests
- How unittest discovery finds tests, what makes it skip files and folders, exit statuses, and guarding a pipeline against silent gaps.
- First step
- Run the same command the job runs, python -m unittest, from the same folder, and read the last lines: "Ran 0 tests" and "NO TESTS RAN". Then compare what discovery searched, the folder it started in and the file names it accepts, with where the test files really are and what they are called.
- Follow-ups
-
- The job's script runs the tests in a pipe. Why did it still report success?
- How do you find test files that discovery skips without a word?
- Which numbers would you print or check in the job?
From the lesson unittest: test cases, fixtures and discovery
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to staff
You join a team with a 200,000-line Python codebase and no linter. Ruff's default rules report 4,800 findings. How do you introduce linting without stopping other work, and what do you fix first?
- What it tests
- Rolling out a quality tool on legacy code with a baseline, ordering fixes by risk, and keeping the team's trust and the history readable.
- First step
- Separate the old findings from the new: the first goal is that the count never grows, not that it reaches zero. Pin the Ruff version and commit its configuration, so the count cannot change by itself, then rank the findings by risk, because a few rule families are real bugs and most of the rest are style.
- Follow-ups
-
- Which rule families would you fix first, and why?
- The team also wants ruff format. How do you avoid one giant diff that ruins git blame?
- How do you know the baseline is shrinking?
From the lesson Ruff and pre-commit: lint and format Python
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to senior
In a code review you see a bare # noqa added to silence a B006 finding on a function whose default dictionary is meant to cache results across calls. Is that acceptable, and what would you ask the author to change?
- What it tests
- Judging when a lint exception is deliberate, how noqa comments should be written, and safer designs than a shared mutable default.
- First step
- Decide whether the shared default is the design or an accident. Here it is deliberate, so silencing the finding can be fine. Then look at how it was silenced and at what the comment tells the next reader, and question whether a mutable default is the right way to cache.
- Follow-ups
-
- What would a clearer design for the cache look like?
- How can CI stop bare noqa comments from being merged?
- When is silencing a lint rule the wrong answer?
From the lesson Ruff and pre-commit: lint and format Python
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 2 minutes
-
Mid to senior
Developers keep committing with git commit --no-verify, so formatting and lint problems reach pull requests and CI fails several times a day. How do you fix the process, not just the latest failure?
- What it tests
- Understanding what local hooks can and cannot enforce, making checks fast and helpful, and making CI the real gate.
- First step
- Ask why people skip the hooks: slow hooks, hooks that change files without saying why, or hooks that block work in progress. Local hooks are a convenience anyone can skip, so make CI the gate, and make the hooks fast and quiet enough that skipping them is not worth it.
- Follow-ups
-
- The hooks take 40 seconds. Where does the time go, and what do you cut?
- Should CI fix formatting itself and push a commit?
- How do you handle a hook that needs a tool not everyone has installed?
From the lesson Ruff and pre-commit: lint and format Python
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes