ASAmol Shukla
Projects
Courses
Prompts
Skills
Contact
Resume
Course Outline
Syllabus Overview

Complete Python Course: From Zero to Professional

Courses/Complete Python Course: From Zero to Professional/Lesson 24: Testing & Debugging Like a Pro
50 mins lesson duration•10 mins read

Lesson 24: Testing & Debugging Like a Pro

assert, pytest, the debugger, and logging — write code that proves itself.

Tests Are Not Optional

A professional program ships with tests that prove it works. Every time you change code, tests catch what you broke — instantly. Without tests, you are trusting "it worked when I tried it once".

This is the mindset shift that separates hobby code from production software. Companies ship with test suites that run on every commit — a failing test blocks the release. This lesson gives you that professional toolkit.

What You'll Learn in This Lesson

  • Write quick checks with assert
  • Build real tests with pytest
  • Debug interactively with breakpoint()
  • Log professionally with the logging module

assert — The Quick Check

assert condition, "message" raises AssertionError if the condition is False:

assert len("python") == 6, "expected six characters"
print("Assertion passed")

Great for sanity checks and quick validations. But asserts vanish with python -O — for real tests, use pytest.

Assertions are the grammar of tests. Every test you will ever write is, at heart, a collection of assertions: "this equals that", "this raises that", "this is True".


pytest — The Professional Standard

Tests are plain functions starting with test_ containing asserts. pytest discovers and runs them automatically:

# test_price.py
def price_after_tax(price, tax_rate=0.18):
    return round(price * (1 + tax_rate), 2)

def test_normal_price():
    assert price_after_tax(100) == 118.0

def test_zero_price():
    assert price_after_tax(0) == 0.0

def test_high_tax():
    assert price_after_tax(100, 0.5) == 150.0

Run with pytest:

collected 3 items
test_price.py .....                                               [100%]
============================= 3 passed in 0.02s =============================

Useful flags: pytest -v (verbose), pytest -k name (run a subset), pytest -x (stop on first failure).

Mental model: each test is a referee with a checklist — green for pass, red for fail. The suite is your safety net: change code, run tests, and the net catches any broken behavior instantly.

Testing exceptions with pytest:

import pytest

def test_division_by_zero():
    with pytest.raises(ZeroDivisionError):
        result = 1 / 0

The Debugger — Better than print()

print() debugging works, but the interactive debugger is far more powerful:

def mystery(a, b):
    result = a * b
    breakpoint()      # execution PAUSES here
    return result + 1

When breakpoint() runs, you drop into an interactive prompt:

Command Meaning
n Next line
s Step into a function
c Continue to the next breakpoint
p var Print a variable's value
q Quit

Mental model: breakpoint() is a pause button that freezes the program mid-flight so you can inspect every variable.

Why the debugger beats print(): you can inspect any variable at the pause point without editing code and re-running; you can step line by line; and you can continue from where you stopped. print() requires guessing what to print and re-running each time.


Logging — Professional Print

The logging module writes timestamped, leveled messages to console or files — far more useful than print() in production:

import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

logging.debug("details only during development")
logging.info("normal flow events")
logging.warning("something might be wrong")
logging.error("something failed")

Levels (increasing severity): DEBUG < INFO < WARNING < ERROR < CRITICAL.

Why logging over print in production:

print() logging
Screen only Console, files, network
No levels DEBUG → CRITICAL levels
No timestamps Timestamps & formats built in
No easy disabling level= filters whole categories
One-off Structured, searchable output

TDD in One Sentence

Write the test FIRST (it fails), then write the minimum code to make it pass — red, green, refactor.

The loop:

  1. Red: write a failing test for the behavior you want.
  2. Green: write the smallest code that makes it pass.
  3. Refactor: clean up, keeping the tests green.

This forces you to think about behavior before implementation — and guarantees every feature is covered.


Common Mistakes to Avoid

  • Mistake: Testing the implementation instead of the behavior — Fix: assert on outputs and effects, not internals.
  • Mistake: Only testing the happy path — Fix: test edge cases: zero, negatives, empty inputs, maximums.
  • Mistake: print() debugging in production — Fix: use the logging module.
  • Mistake: A test that asserts nothing — Fix: every test needs at least one assert (or pytest.raises).
  • Mistake: Tests that depend on other tests or on global state — Fix: keep tests independent and deterministic.

Professional Tips & Tricks

  • Name tests descriptively: test_withdraw_insufficient_funds().
  • Test edge cases: zero, negatives, empty inputs, and the maximums.
  • Use pytest -v for verbose output and pytest -k keyword to run a subset.
  • Use breakpoint() + n/s/p instead of print-debugging.
  • Replace production print() with logging — leveled, timestamped, filterable.

Key Takeaways

  • assert gives quick checks; pytest gives professional test suites.
  • breakpoint() + n/s/p beats print-debugging.
  • logging with levels is the production-grade replacement for print.
  • TDD: write the failing test first, then make it pass.
  • Test behavior, not implementation — and cover edge cases.

Next up: The capstone — build a complete CLI expense tracker.

Interactive Lesson Code Snippet
# A function we want to trust
def price_after_tax(price, tax_rate=0.18):
    """Return price plus tax, rounded to 2 decimals."""
    return round(price * (1 + tax_rate), 2)

# Tests using pytest style (run with: pytest)
def test_normal_price():
    assert price_after_tax(100) == 118.0

def test_zero_price():
    assert price_after_tax(0) == 0.0

def test_high_tax():
    assert price_after_tax(100, 0.5) == 150.0

# Logging instead of print
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logging.info("Calculating price for 100")
print("Result:", price_after_tax(100))
Language: python

Lesson Code (Python)

# A function we want to trust
def price_after_tax(price, tax_rate=0.18):
    """Return price plus tax, rounded to 2 decimals."""
    return round(price * (1 + tax_rate), 2)

# Tests using pytest style (run with: pytest)
def test_normal_price():
    assert price_after_tax(100) == 118.0

def test_zero_price():
    assert price_after_tax(0) == 0.0

def test_high_tax():
    assert price_after_tax(100, 0.5) == 150.0

# Logging instead of print
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logging.info("Calculating price for 100")
print("Result:", price_after_tax(100))

Console Output

INFO: Calculating price for 100
Result: 118.0

# Running pytest:
# ============================ test session starts ============================
# collected 3 items
# test_*.py .....                                                      [100%]
# ============================= 3 passed in 0.02s =============================

Code Visualization Tips

  • 🧠Picture each test as a referee with a checklist — green for pass, red for fail.
  • 🧠Visualize breakpoint() as a pause button that freezes the program mid-flight so you can inspect it.
  • 🧠Draw the red-green-refactor loop: fail (red) -> pass (green) -> clean up (refactor).

Professional Tips & Tricks

  • ⚡Name tests descriptively: test_withdraw_insufficient_funds().
  • ⚡Test edge cases: zero, negatives, empty inputs, and the maximums.
  • ⚡Use pytest -v for verbose output and pytest -k keyword to run a subset.

Python Code Judge & Practice Arena

LeetCode Style

Run real Python 3.12 WebAssembly code directly in your browser against automated test suites.

Solved:0 / 20
0 / 370 XP
Challenges:
Problem 1 of 20

Problem 1: Email Format Validator 1

Easy+10 XP
Write a function `validate_email_1(email)` that verifies an email contains exactly one '@', a domain suffix after a dot '.', and non-empty username/domain parts.
Sample Test Cases:
Input: validate_email_1('amol@example.com')
Expected: True
Input: validate_email_1('invalid.com')
Expected: False
main.pyPython 3.12 (WASM)
1
2
3
4
5
6
7
8
9
10
11
12
Press Run Code to test or Submit to verify test cases

Test Your Knowledge

Instant feedback

Quick Check: Lesson 24: Unit Testing with Pytest & Unittest, Logging & Debugging

1 / 20
How do you write a test assertion in `pytest`? def test_add(): assert 2 + 2 == 4

Up next · Continue learning

Capstone — Build a CLI Expense Tracker

Bring everything together: functions, dicts, files, JSON, and a menu loop in one complete project.

14 mins read90 mins
Start next lesson
Previous: JSON & Working with DataNext: Capstone — Build a CLI Expense Tracker
Made withbyAmol Shukla·amolshukla.online
ASAmol Shukla

AI Developer, Trainer & Agentic AI Expert building practical learning systems and real-world AI applications.

Explore

  • Projects
  • Courses
  • Prompts
  • Skills
  • Contact
  • Experience
  • Blogs

Connect

  • Resume
  • Contact
© 2026 Amol Shukla·Created withbyamolshukla.online
Back to top