Debugging

Read the traceback, print the inputs, then add a failing test.

When something fails, read the traceback from the bottom: file, line, exception type, message. Then print the inputs, then add a test that fails the same way.

Goal

Cause a ValueError, read it, fix the call, and lock the rule in a test.

Read the traceback

def line_total(units, price):
    if units < 0 or price < 0:
        raise ValueError("units and price must be >= 0")
    return round(units * price * 1.16, 2)

print(line_total(2, 10))

Now the failing call:

def line_total(units, price):
    if units < 0 or price < 0:
        raise ValueError("units and price must be >= 0")
    return round(units * price * 1.16, 2)

try:
    print(line_total(-2, 10))
except ValueError as err:
    print(type(err).__name__, err)

Print the inputs

def city_label(city):
    print("debug city=", repr(city))
    return str(city or "").strip().title()

print(city_label("  nairobi "))

Remove the debug print once you see repr(city) — leftover debug noise is a bug of its own.

Then a test

import unittest

def line_total(units, price):
    if units < 0 or price < 0:
        raise ValueError("units and price must be >= 0")
    return round(units * price * 1.16, 2)

class PriceTests(unittest.TestCase):
    def test_negative(self):
        with self.assertRaises(ValueError):
            line_total(-2, 10)

suite = unittest.defaultTestLoader.loadTestsFromTestCase(PriceTests)
result = unittest.TextTestRunner(verbosity=2).run(suite)
print("ok" if result.wasSuccessful() else "failed")
Pitfall

There is no interactive pdb prompt in this tab. print + traceback + a failing test is the loop.