Python Testing and Debugging Quiz: unittest, Mock and pdb

Test your Python testing skills with 12 questions and three programs on unittest, pytest fixtures and parametrize, Mock and patch, tracebacks and logging.

  • Course: Python study plan
  • Module: Testing and debugging
  • Kind: Checkpoint — cleared at 70%
  • Reading time: 25 min
  • Runtime: CPython 3.11

Checkpoint — Testing and debugging is the checkpoint that closes the Testing and debugging module: a graded quiz and whole-program exercises, passed at 70%.

Instructions

This checkpoint covers the whole module: the testing mindset — kinds of test, arrange–act–assert, what to test and designing for testability by injecting the clock, seed and collaborators; unittest with its assertion family, fixtures, subTest and in-process runs; pytest's plain asserts, fixtures and parametrize and the mechanisms behind them; test doubles with Mock, side_effect, patch and the rule about where to patch; and debugging by reading tracebacks, logging, pdb and the reproduce–minimise–bisect method.

How it works. Twelve questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.

Before you start, make sure you can answer these from memory:

  • What are the three parts of a test, and why one behaviour per test?
  • Why is assertEqual(a, b) better than assertTrue(a == b)?
  • What does subTest change about a failing table?
  • How does a yield fixture guarantee teardown?
  • What does side_effect=[TimeoutError(), 42] make a mock do?
  • Where must patch target a name for the code under test to see the mock?
  • In which order do you read a traceback, and what does "During handling of the above exception" mean?

The three programs are a unittest suite for a bank account that must catch each of three injected bugs by name, a retry helper tested against a scripted Mock with a counted number of attempts, and a counting logging.Handler that classifies a stream of records by level and reports what a threshold would have shown.

Common questions

How does a yield fixture guarantee teardown?

A yield fixture is a generator: the runner calls next() to get the value, runs the test, then calls next() again inside a finally block, so the code after the yield runs whether the test passed or failed.

What does side_effect=[TimeoutError(), 42] make a mock do?

The first call raises TimeoutError and the second returns 42; a third call raises StopIteration because the list is used up. It is how a retry helper is tested against a counted number of attempts.

Where must patch target a name for the code under test to see the mock?

In the module under test, where the name is looked up at call time, not in the module that defines it. After from rates import fetch_rate in billing.py, the target is billing.fetch_rate.

Exercises

A suite against injected bugs

The starter defines BankAccount with a bug injected by the first input line: none, negative (deposit accepts non-positive amounts), overdraw (withdraw allows going below zero) or interest (add_interest adds the rate instead of the percentage). Write TestBankAccount with exactly: test_deposit_increases_balance (100 + deposit 50 → 150), test_deposit_rejects_non_positive (assertRaises(ValueError) for deposit(0) and for deposit(-5)), test_withdraw_rejects_overdraw (balance 100, assertRaises(ValueError) for withdraw(150)) and test_interest (balance 200, add_interest(0.05), assertAlmostEqual 210). Run on a StringIO stream and print ran <n>, failures <f>, errors <e>, ok <bool>, then failed <name> per failure, sorted.

Input: the bug name. Output: four lines, then any failed names.

none

prints

ran 4
failures 0
errors 0
ok True

Retry against a scripted mock

Write retry(fn, attempts) that calls fn() up to attempts times, returning the first result and swallowing ConnectionError between tries; when every attempt fails it raises the last error. Read attempts on the first line and a script on the second: tokens that are fail (a ConnectionError("down")) or an integer (a result). Build Mock(side_effect=script), call retry and print result <value> or gave up after <attempts>, then calls <mock.call_count>.

Input: the attempt count, then the script. Output: two lines.

3
fail fail 42

prints

result 42
calls 3

A counting handler

Subclass logging.Handler as Counting: its emit(record) counts records per levelname and keeps (levelname, message) for records at WARNING or above. Read a threshold level on the first line, then lines <LEVEL> <message> until EOF; attach the handler to a logger named app (propagate = False) set to DEBUG so every record reaches it, and emit each line with logger.log. Print counts DEBUG=<n> INFO=<n> WARNING=<n> ERROR=<n> CRITICAL=<n>, then at <threshold>: <k> shown where k counts records whose level is at least the threshold, then one line <LEVEL>: <message> per kept record in order.

Input: the threshold, then records. Output: the counts, the threshold line, the kept records.

INFO
DEBUG a
INFO b
WARNING c
ERROR d

prints

counts DEBUG=1 INFO=1 WARNING=1 ERROR=1 CRITICAL=0
at INFO: 3 shown
WARNING: c
ERROR: d

In this module: Testing and debugging

← Debugging — reading tracebacks, logging, pdb and the method · The object model — objects, references, reference counting and the cycle collector →