Excellent.
This is PHASE 10 — Testing, Debugging & Logging, taught at production-grade + senior interview depth.
Most developers “write tests”.
Senior engineers design testable systems, debug systematically, and log with intent.
After this phase, you’ll know how real teams trust Python code in production.
🧪 PHASE 10 — TESTING, DEBUGGING & LOGGING (DEEP)
10.1 WHY TESTING IS A DESIGN PROBLEM
Key truth:
Untestable code is badly designed code.
Testing forces:
- Clear boundaries
- Dependency injection
- Deterministic behavior
- Separation of concerns
📌 Interview line
Testing is feedback on design, not just correctness.
🧪 TESTING WITH pytest (INDUSTRY STANDARD)
10.2 WHY pytest OVER unittest
| Aspect | unittest | pytest |
|---|---|---|
| Boilerplate | High | Minimal |
| Assertions | Verbose | Pythonic |
| Fixtures | Complex | Elegant |
| Parametrization | Weak | Excellent |
| Ecosystem | OK | Excellent |
📌 pytest is de facto standard.
10.3 BASIC PYTEST TEST
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
Run:
pytest
10.4 ASSERTIONS — USE PYTHON, NOT FRAMEWORKS
assert x > 0
Pytest rewrites assertions to show:
- Expected
- Actual
- Expression breakdown
📌 No need for self.assertEqual.
10.5 PARAMETRIZED TESTS (INTERVIEW FAVORITE)
import pytest
@pytest.mark.parametrize(
"a,b,expected",
[(1,2,3), (0,0,0), (-1,1,0)]
)
def test_add(a, b, expected):
assert add(a, b) == expected
✔ Reduces duplication
✔ Improves coverage
10.6 FIXTURES — CONTROLLED SETUP & TEARDOWN
import pytest
@pytest.fixture
def db():
conn = connect()
yield conn
conn.close()
Use:
def test_query(db):
assert db.is_alive()
📌 Fixtures replace setup/teardown methods.
10.7 FIXTURE SCOPES (IMPORTANT)
| Scope | Lifetime |
|---|---|
| function | Per test |
| class | Per class |
| module | Per module |
| session | Entire run |
📌 Scope controls performance & isolation.
10.8 AUTOUSE FIXTURES (USE SPARINGLY)
@pytest.fixture(autouse=True)
def setup_env():
...
⚠️ Can hide dependencies
Use only for:
- Global config
- Environment setup
10.9 EXPECTING EXCEPTIONS
import pytest
def test_error():
with pytest.raises(ValueError):
int("abc")
✔ Clean
✔ Explicit
10.10 TESTING LOG OUTPUT
def test_logs(caplog):
logger.info("hello")
assert "hello" in caplog.text
📌 Shows you know observability.
🧪 MOCKING & PATCHING (INTERVIEW-CRITICAL)
10.11 WHY MOCKING EXISTS
Mocking is used to:
- Isolate unit tests
- Avoid external systems
- Control nondeterminism
📌 Rule
Mock at the boundary, not the core logic.
10.12 BASIC MOCK (unittest.mock)
from unittest.mock import Mock
m = Mock()
m.return_value = 10
10.13 PATCHING (MOST IMPORTANT)
from unittest.mock import patch
@patch("module.func")
def test_call(mock_func):
mock_func.return_value = 5
📌 Patch where used, not where defined.
10.14 COMMON PATCHING BUG (INTERVIEW FAVORITE)
❌ Wrong:
@patch("utils.api_call")
✔ Correct:
@patch("service.utils.api_call")
Because:
- Python binds names at import time
10.15 MOCK VS STUB VS FAKE
| Type | Purpose |
|---|---|
| Mock | Verify calls |
| Stub | Return data |
| Fake | Working impl (in-memory DB) |
📌 Senior-level vocabulary.
🐞 DEBUGGING (SYSTEMATIC, NOT RANDOM PRINTS)
10.16 PRINT DEBUGGING — WHY IT FAILS
❌ Changes timing
❌ Pollutes output
❌ Misses state
10.17 USING pdb (INTERVIEW SAFE)
import pdb; pdb.set_trace()
Commands:
n→ nexts→ step intol→ listp x→ printc→ continue
📌 Demonstrates methodical debugging.
10.18 TRACEBACKS — READ THEM PROPERLY
Rule:
Read tracebacks bottom-up
Bottom:
- Actual error
Top: - Call chain
📌 Many candidates read it wrong.
10.19 DEBUGGING CONCURRENCY BUGS
Hardest bugs:
- Race conditions
- Deadlocks
- Order-dependent failures
Techniques:
- Logging timestamps
- Thread names
- Deterministic tests
📜 LOGGING (PRODUCTION-GRADE)
10.20 WHY NOT print()
| logging | |
|---|---|
| No levels | Levels |
| No timestamps | Yes |
| No routing | Handlers |
| No control | Configurable |
📌 Interview line
Print is for development; logging is for systems.
10.21 LOGGING LEVELS (MUST KNOW)
| Level | Use |
|---|---|
| DEBUG | Internals |
| INFO | Normal ops |
| WARNING | Recoverable issues |
| ERROR | Failures |
| CRITICAL | System failure |
10.22 BASIC LOGGING SETUP
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s"
)
10.23 LOGGER PER MODULE (BEST PRACTICE)
logger = logging.getLogger(__name__)
Why?
- Hierarchical control
- Selective verbosity
10.24 LOGGING EXCEPTIONS CORRECTLY
try:
risky()
except Exception:
logger.exception("failed")
✔ Automatically logs traceback
❌ Don’t do logger.error(e)
10.25 LOGGING AT BOUNDARIES
Rule:
- Raise internally
- Log at system boundary
📌 Prevents duplicate logs.
10.26 STRUCTURED LOGGING (MENTION-LEVEL)
Instead of:
logger.info("user %s failed", user)
Use:
logger.info("login_failed", extra={"user": user})
Used in:
- Microservices
- Observability stacks
🧪 TEST PYRAMID (DESIGN QUESTION)
E2E
Integration
Unit Tests
Rule:
- Many unit tests
- Few slow tests
- Stable foundation
📌 Interview favorite.
🧪 PRACTICE (INTERVIEW-LEVEL)
Q1
Why should you mock external APIs?
Q2
Explain why patching must be done “where used”.
Q3
Difference between unit and integration tests?
Q4
Why is logging better than print?
Q5
How would you debug a flaky test?
🎯 INTERVIEW CHECKPOINT (CRITICAL)
You must now confidently explain:
✅ pytest fundamentals
✅ Parametrization & fixtures
✅ Mocking & patching rules
✅ Exception testing
✅ Debugging with pdb
✅ Traceback analysis
✅ Logging best practices
✅ Logging exceptions
✅ Test pyramid
If you can do this → you are production-ready in Python.
🏁 PHASE 10 COMPLETE ✅
You now understand how Python code is tested, debugged, and trusted in production.
🚀 NEXT PHASE (FINAL FOUNDATIONS)
Reply with ONLY ONE number:
1 → PHASE 11: Performance, Profiling & Optimization (time, memory, CPU — deep)
2 → 30-question Testing & Logging interview drill
We continue A→Z without deviation.