Testing, Pytest & Debugging
Automated software testing guarantees that code changes and refactoring do not introduce regression bugs.
Unit Tests vs Integration Tests:
- Unit Tests: Test a single isolated function or class in memory with all external dependencies (databases, APIs) mocked out. They execute in milliseconds.
- Integration Tests: Test how multiple components work together (e.g. testing whether a Flask route properly writes a record to a real PostgreSQL database).
Why Pytest is the Industry Standard:
Unlike Python's built-in unittest module which requires verbose classes and methods like self.assertEqual(a, b), pytest uses standard Python functions and plain assert statements with rich, detailed error diffs!
# Production Pytest Test Suite Architecture Blueprint:
"""
import pytest
from banking import BankAccount
# 1. Reusable Test Fixture:
@pytest.fixture
def active_account():
# Setup: Create a fresh test account before each test
account = BankAccount(owner="Balaji", initial_balance=5000.0)
return account
# 2. Unit Test using standard assert statements:
def test_initial_balance(active_account):
assert active_account.balance == 5000.0
assert active_account.owner == "Balaji"
def test_deposit_funds(active_account):
active_account.deposit(2000.0)
assert active_account.balance == 7000.0
def test_withdraw_insufficient_funds_raises_error(active_account):
with pytest.raises(ValueError, match="Insufficient funds"):
active_account.withdraw(10000.0) # Attempting to overdraft!
# 3. Parameterized Testing: Run multiple test cases with one function!
@pytest.mark.parametrize("deposit_amount, expected_balance", [
(500.0, 5500.0),
(1500.0, 6500.0),
(10000.0, 15000.0)
])
def test_multiple_deposits(active_account, deposit_amount, expected_balance):
active_account.deposit(deposit_amount)
assert active_account.balance == expected_balance
"""
print("Pytest Test Suite Blueprint Configured.")
Fixtures ensure clean test isolation: each test function receives a freshly instantiated, pristine object, preventing state pollution between consecutive test runs.
Python 3.7+ introduced the built-in breakpoint() function. Calling breakpoint() anywhere in your code pauses execution and opens an interactive Python Debugger (PDB) shell right in your terminal!
| PDB Command | Short | Action / Meaning |
|---|---|---|
next | n | Execute the current line and advance to the next line. |
step | s | Step into the function call on the current line. |
continue | c | Resume normal program execution until the next breakpoint. |
print(var) | p var | Inspect the current runtime value of any variable. |
quit | q | Terminate the debugging session and exit Python. |
# Demonstrating Interactive Debugging Execution Flow:
def compute_discounted_cart(items, discount_pct):
total = 0.0
for item in items:
price = item["price"]
qty = item["qty"]
# breakpoint() <-- Un-commenting this pauses execution and drops into PDB shell!
subtotal = price * qty
total += subtotal
final_amount = total * (1 - (discount_pct / 100))
return final_amount
sample_cart = [
{"name": "Python Book", "price": 899.0, "qty": 2},
{"name": "USB Hub", "price": 1299.0, "qty": 1}
]
total_charge = compute_discounted_cart(sample_cart, 10)
print(f"Final Discounted Cart Total: โน{total_charge:,.2f}")
With breakpoint(), you can dynamically modify variable values in memory, step through recursive calls line by line, and evaluate expressions without constantly restarting the script.
Unit tests that make live HTTP network calls will fail whenever the network is slow or third-party servers are down. Always use unittest.mock or pytest-mock to mock HTTP responses in unit tests.
Write a simple assertion test function test_email_validation() that tests whether an email validator returns True for "balaji@example.com" and False for "invalid_email".
def is_valid_email(email):
return "@" in email and "." in email.split("@")[-1]
def run_tests():
assert is_valid_email("balaji@example.com") == True, "Valid email failed!"
assert is_valid_email("invalid_email") == False, "Invalid email passed!"
assert is_valid_email("user@domain") == False, "Missing TLD passed!"
print("โ
All 3 Email Validation Tests Passed Successfully!")
run_tests()
Q What is Code Coverage in testing?
Code Coverage (measured via pytest-cov) is the percentage of your application source code executed by your test suite, helping identify untested branches and edge cases.
Q What does unittest.mock.patch do?
patch() temporarily replaces a real function or network class (like requests.get) with a Mock object that returns pre-configured fake data during test execution.
Q What is TDD (Test-Driven Development)?
TDD is a software development process where you write a failing test first (Red), write the minimal code to make the test pass (Green), and then clean up the code (Refactor).