BugForge
All lessons
Python · 08/10 6 min

Reading Python test failures

unittest and pytest side by side, tracebacks bottom-up, assertRaises, approx and mocking time.

Read tracebacks from the bottom

Traceback (most recent call last):
  File "tests/test_ledger.py", line 31, in test_transfer
    transfer(a, b, Decimal("5"))
  File "bank/ledger.py", line 48, in transfer
    dst.deposit(amount)
AttributeError: 'NoneType' object has no attribute 'deposit'

The last line is the error; the frame just above it is where it happened. Here dst is None, and transfer withdrew from a before finding that out.

unittest (what the labs use)

import unittest
class LedgerTests(unittest.TestCase):
    def setUp(self):                       # runs before EACH test
        self.acct = Account()
    def test_deposit(self):
        self.acct.deposit(Decimal("10"))
        self.assertEqual(self.acct.balance, Decimal("10"))
    def test_negative(self):
        with self.assertRaises(ValueError):
            self.acct.deposit(Decimal("-1"))
unittest pytest
self.assertEqual(a, b) assert a == b
self.assertTrue(x) assert x
self.assertIn(x, xs) assert x in xs
self.assertAlmostEqual(a, b) assert a == pytest.approx(b)
with self.assertRaises(E): with pytest.raises(E, match="msg"):
setUp / tearDown fixtures

Assertion messages

AssertionError: Decimal('9.99') != Decimal('10.00'): the first value is what your code produced (actual), the second is the expected one, in the order the test wrote them. Read both before guessing.

Controlling time

from unittest import mock
with mock.patch("bucket.time.monotonic", return_value=100.0):
    ...

Patch the name where it's used (bucket.time.monotonic), not where it's defined. Better still, design classes to take a clock argument so tests pass a fake.

Rules

  • Don't edit tests to pass them.
  • Tests must not depend on order: shared module-level state (a global list, a class attribute) makes the second test fail only when run after the first.
  • Re-run the whole file after each fix.
Next in Python
Scope, closures and function arguments