03 Quiz: Testing Fundamentals - Unit Testing and Clean Code
November 2025 (3067 Words, 18 Minutes)
Instructions
This quiz tests your understanding of key concepts from Chapter 03 (Testing Basics): Testing Fundamentals.
Why Take This Quiz?
For Your Learning:
- Self-Assessment: Identify areas where you have strong understanding and topics that need review
- Exam Preparation: The quiz format and difficulty mirror what you can expect in the final exam
- Immediate Feedback: Get instant feedback on your answers to reinforce correct understanding
- Active Recall: Testing yourself is one of the most effective learning techniques
For Course Improvement:
- Anonymous Feedback: Your responses help me understand which concepts need more explanation
- Content Adjustment: If many students struggle with a topic, we’ll spend more time on it in class
- Pacing Indicator: Shows me if we’re moving too fast or too slow
Guidelines:
- Each question has 4 options with exactly one correct answer
- Try to answer without referring to the lecture materials first
- Focus on understanding concepts, not memorizing specific numbers
- Click “Show Answer” to see the explanation after attempting each question
Question 1: Code Quality vs. Code Correctness
Your CI pipeline shows all checks passing (Ruff, Pyright). What does this tell you about your code?
A) The code is production-ready and bug-free
B) The code follows style guidelines and has correct types, but may still contain logic bugs
C) The code will handle all edge cases correctly
D) The code has been tested thoroughly
Show Answer
Correct Answer: B
Code quality tools (Ruff, Pyright) check style and types, not correctness. They verify:
- ✅ Formatting (PEP8 compliance)
- ✅ Type hints are present and consistent
- ✅ Unused imports are removed
They do NOT verify:
- ❌ Logic is correct
- ❌ Edge cases are handled
- ❌ Functions return correct results
This is why we need tests - to verify that code actually works as intended, not just that it looks good.
Question 2: The Testing Pyramid
What is the recommended distribution of tests in the testing pyramid?
A) Equal distribution: 33% unit, 33% module, 33% end-to-end
B) Top-heavy: 10% unit, 20% module, 70% end-to-end
C) Bottom-heavy: 70% unit, 20% module, 10% end-to-end
D) Middle-heavy: 10% unit, 80% module, 10% end-to-end
Show Answer
Correct Answer: C
The testing pyramid recommends:
- 70% Unit tests (base of pyramid) - Fast, cheap, easy to debug
- 20% Module/Integration tests (middle) - Moderate speed and cost
- 10% End-to-End tests (top) - Slow, expensive, hard to debug
Why this distribution?
- Fast feedback loops: Unit tests run in milliseconds, enabling rapid iteration
- Easy debugging: When a unit test fails, you know exactly which function is broken
- Maintainability: Unit tests are easier to write and maintain than E2E tests
- Cost-effective: 50 unit tests run faster than 5 E2E tests
The pyramid shape reflects both the speed and quantity - more tests at the fast, cheap level.
Question 3: The Test Cone Anti-Pattern
What is the “test cone” (inverted pyramid) and why is it problematic?
A) A testing strategy that focuses on integration tests, which is the most efficient approach
B) A pattern where most tests are slow end-to-end tests, resulting in slow feedback and brittle tests
C) A modern testing approach that replaces the outdated testing pyramid
D) A way to organize test files in directories
Show Answer
Correct Answer: B
The test cone is an anti-pattern where the testing pyramid is inverted:
- Many E2E tests (top of pyramid becomes wide)
- Few or no unit tests (base of pyramid becomes narrow)
Problems with the test cone:
- Slow feedback: Every change requires running slow E2E tests (minutes, not seconds)
- Hard to debug: When E2E test fails, which of the 10 modules caused it?
- Brittle: UI changes break many E2E tests
- Vicious cycle: Tests are slow → developers avoid running them → bugs pile up
How it happens:
- “I don’t know how to write unit tests”
- “The UI is easy to click through manually”
- “Unit tests are too much work”
Solution: Learn to write unit tests properly (this lecture!) and build the pyramid correctly.
Question 4: Unit Test Characteristics
Which of the following is NOT a characteristic of a good unit test?
A) Tests a single function or method in isolation
B) Requires starting the entire application (Dash, database, etc.)
C) Runs in milliseconds
D) Has clear expectations (arrange-act-assert pattern)
Show Answer
Correct Answer: B
Good unit tests should:
- ✅ Test in isolation: One function/method, no dependencies on other systems
- ✅ Be fast: Run in milliseconds (not seconds or minutes)
- ✅ Be clear: Easy to understand what’s being tested
- ✅ Be independent: Can run in any order
Unit tests should NOT:
- ❌ Start the entire application
- ❌ Connect to databases, APIs, or external services
- ❌ Require manual setup (like clicking through UI)
- ❌ Depend on other tests running first
Example:
# ✅ Good unit test - tests find_intersection() in isolation
def test_find_intersection_normal_angle():
x, y, dist = find_intersection(test_data)
assert dist > 0
# ❌ Not a unit test - requires starting Dash app
def test_full_application_flow():
app = start_dash_app() # Too slow!
response = app.click_button()
assert response.status == 200
Question 5: The AAA Pattern
What does the AAA pattern stand for in unit testing?
A) Always Assert Accurately
B) Arrange-Act-Assert
C) Analyze-Apply-Approve
D) Automatic-Assertion-Analysis
Show Answer
Correct Answer: B
The AAA pattern is a standard structure for unit tests:
- Arrange: Set up test data and conditions
- Act: Call the function being tested
- Assert: Verify the result matches expectations
Example:
def test_find_intersection_normal_angle():
# ARRANGE: Set up test data
x_road = np.array([0, 10, 20, 30])
y_road = np.array([0, 2, 4, 6])
angle = -10.0
# ACT: Call the function
x, y, dist = find_intersection(x_road, y_road, angle)
# ASSERT: Verify the result
assert x is not None
assert dist > 0
Why this pattern?
- Readability: Clear structure makes tests easy to understand
- Debugging: Easy to see what’s being tested and what failed
- Consistency: All tests follow the same pattern
Question 6: Exhaustive Testing
Why is exhaustive testing (testing all possible inputs) impossible for most functions?
A) Because computers are too slow to run that many tests
B) Because the number of possible input combinations grows exponentially, making it impractical
C) Because we don’t have enough disk space to store all the tests
D) Because testing frameworks have limits on the number of tests
Show Answer
Correct Answer: B
Exhaustive testing is impossible because:
- Combinatorial explosion: Each parameter multiplies the number of test cases
- Infinite or near-infinite input spaces: Floats, strings, arrays have too many possible values
- Time constraints: Testing all combinations would take longer than the age of the universe
Example: A function with just 3 float parameters:
- Each parameter: ~1,000,000 possible values (conservative estimate)
- Total combinations: 1,000,000³ = 1 quintillion tests
- Time at 1ms per test: ~32 billion years
What we do instead:
- Smart testing strategies: Equivalence classes, boundary value analysis (Chapter 03 (Boundary Analysis))
- Risk-based testing: Focus on critical functionality
- Representative samples: Choose inputs that represent different scenarios
Key insight: You can never test everything, so you must be strategic about what you test.
Question 7: pytest Benefits
What is the PRIMARY advantage of using pytest over manual testing (e.g., print statements)?
A) pytest makes tests run faster
B) pytest automatically discovers, organizes, and reports test results with detailed error information
C) pytest reduces the number of tests you need to write
D) pytest eliminates the need to write assertions
Show Answer
Correct Answer: B
pytest provides automation and organization for testing:
With pytest:
- ✅ Auto-discovery: Finds all
test_*.pyfiles automatically - ✅ Rich output: Shows exactly which test failed, line number, error type
- ✅ Organized reports: Clear pass/fail counts, execution time
- ✅ Test isolation: Each test runs independently
- ✅ CI/CD integration: Works seamlessly with GitHub Actions
- ✅ Simple syntax: Just use
assert(no special methods needed)
Without pytest (manual testing):
- ❌ Have to call each test function manually
- ❌ Just get print statements (no structured output)
- ❌ No clear summary of what passed/failed
- ❌ Have to write your own error handling
- ❌ Tests might interfere with each other
Example comparison:
# Manual - primitive and tedious
def manual_test():
if result != expected:
print("FAILED")
return False
return True
# pytest - clean and powerful
def test_something():
assert result == expected # pytest handles the rest!
Question 8: One Concept Per Test
Why is it important to test only ONE concept per test function?
A) To make tests run faster by reducing the amount of code
B) To save memory by having fewer variables
C) To make it immediately clear what’s broken when a test fails
D) To follow Python naming conventions
Show Answer
Correct Answer: C
One concept per test means each test verifies a single behavior or scenario.
Benefits:
- ✅ Clear failures: Test name tells you exactly what broke
- ✅ Easy debugging: No need to read entire test to find the issue
- ✅ Better organization: Related tests are grouped but separate
- ✅ Easier maintenance: Can modify/remove one concept without affecting others
Example of violation:
# ❌ Bad: Multiple concepts in one test
def test_find_intersection_everything():
# Concept 1: downward angle
assert find_intersection(..., -10.0)[0] is not None
# Concept 2: vertical angle
assert find_intersection(..., 90.0)[0] is None
# Concept 3: empty arrays
assert find_intersection([], [], -10.0)[0] is None
If this test fails, which concept is broken? You have to investigate.
Better approach:
# ✅ Good: One concept per test
def test_find_intersection_finds_intersection_for_downward_angle():
assert find_intersection(..., -10.0)[0] is not None
def test_find_intersection_returns_none_for_vertical_angle():
assert find_intersection(..., 90.0)[0] is None
def test_find_intersection_returns_none_for_empty_arrays():
assert find_intersection([], [], -10.0)[0] is None
Now when a test fails: Test name immediately tells you which scenario is broken!
Question 9: Descriptive Test Names
Which test name follows the recommended pattern: test_[function]_[scenario]_[expected_behavior]()?
A) test_1()
B) test_intersection()
C) test_find_intersection_returns_none_for_empty_arrays()
D) test_edge_case()
Show Answer
Correct Answer: C
Good test names are descriptive and follow a pattern:
test_[function]_[scenario]_[expected_behavior]()
Analysis of each option:
A) test_1() - ❌ Tells you nothing
- What function? What scenario? What’s expected?
- Useless when test fails: “test_1 failed” means nothing
B) test_intersection() - ❌ Too vague
- Which function? Which scenario?
- Not specific enough to understand what failed
C) test_find_intersection_returns_none_for_empty_arrays() - ✅ Perfect!
- Function:
find_intersection - Scenario: empty arrays
- Expected behavior: returns None
- When it fails: “Empty array handling is broken” - immediately clear!
D) test_edge_case() - ❌ Not specific
- Which edge case?
- Which function?
Real-world impact:
# Unhelpful failure message:
❌ FAILED test_1
# Helpful failure message:
✅ FAILED test_find_intersection_returns_none_for_empty_arrays
# You immediately know: "Oh, empty array handling is broken!"
Question 10: Feedback Loop Speed
Why does the speed of tests matter for the development feedback loop?
A) Faster tests use less electricity
B) Fast tests can be run frequently during development, catching bugs immediately
C) Fast tests require less powerful computers
D) Speed only matters in CI/CD, not local development
Show Answer
Correct Answer: B
Fast tests enable rapid iteration:
With fast unit tests (milliseconds):
- ✅ Run tests after every code change
- ✅ Catch bugs immediately (within seconds)
- ✅ Stay in flow state (no waiting)
- ✅ Fix bugs while context is fresh
- ✅ Developers actually run tests (not avoided)
With slow E2E tests (minutes):
- ❌ Run tests infrequently (too slow)
- ❌ Bugs pile up between test runs
- ❌ Context switch while waiting (get coffee, check email)
- ❌ Fix bugs hours later (forgot what you changed)
- ❌ Developers avoid running tests (too slow)
Real-world comparison:
Change one line in find_intersection()
Fast feedback loop:
Write code → Run 50 unit tests (0.5s) → Get immediate result
→ Fix if needed → Iterate rapidly
Slow feedback loop:
Write code → Run 50 E2E tests (250s) → Wait 4+ minutes
→ Get distracted → Lose context → Harder to debug
Key principle: The faster the feedback, the more productive the developer.
Question 11: Multiple Asserts in One Test
When is it acceptable to have multiple assert statements in a single test?
A) Never - always split into separate tests
B) When they test different aspects of the same concept
C) When you want to save time by combining tests
D) Only when testing more than three things
Show Answer
Correct Answer: B
Multiple asserts are acceptable when they test the SAME concept:
✅ Good: Multiple asserts for one concept (coordinate validity)
def test_find_intersection_returns_valid_coordinates():
"""Test that coordinates are within expected bounds."""
x, y, dist = find_intersection(...)
# All verify SAME concept: valid coordinate bounds
assert x is not None
assert y is not None
assert 0 <= x <= 30
assert 0 <= y <= 6
Why this is OK: All asserts check “coordinate validity” - one concept with multiple aspects.
❌ Bad: Multiple asserts for different concepts
def test_find_intersection_coordinates_and_distance():
x, y, dist = find_intersection(...)
assert x > 0 # Concept 1: x position
assert dist > 0 # Concept 2: distance (different!)
Why this is bad: Testing two independent concepts (position and distance).
Guideline:
- Same concept (coordinate validity, error handling) → Multiple asserts OK
- Different concepts (position vs. distance, input vs. output) → Split tests
Test yourself: If you can’t describe what the test checks in one sentence, split it.
Question 12: Test Isolation
What does “test isolation” mean, and why is it important?
A) Tests should run in separate folders
B) Each test should run independently without depending on other tests or external systems
C) Tests should be isolated from the main codebase
D) Tests should only run on isolated CI servers
Show Answer
Correct Answer: B
Test isolation means each test:
- ✅ Runs independently (doesn’t depend on other tests)
- ✅ Can run in any order
- ✅ Has no side effects (doesn’t modify global state)
- ✅ Doesn’t depend on external systems (databases, APIs, UI)
Why isolation matters:
1. Parallel execution:
# Isolated tests can run in parallel
$ pytest -n auto # Runs tests simultaneously
2. Reliable debugging:
# ✅ Isolated: Can run just this one test
$ pytest tests/test_geometry.py::test_find_intersection_normal_angle
# ❌ Not isolated: Have to run test_setup() first
# Can't debug in isolation
3. No flaky tests:
# ❌ Not isolated - depends on test order
def test_a():
global_state = 5 # Sets global state
def test_b():
assert global_state == 5 # Fails if test_a doesn't run first!
# ✅ Isolated - each test sets up its own data
def test_a():
local_state = 5
assert local_state == 5
def test_b():
local_state = 10
assert local_state == 10
How pytest helps:
- Each test function runs in its own scope
- No shared state between tests
- Tests can run in any order (or in parallel)
Scoring Guide
- 10-12 correct: Excellent! You have a strong grasp of testing fundamentals and clean code principles for tests
- 7-9 correct: Good understanding! Review the areas you missed to solidify your knowledge
- 4-6 correct: Fair understanding. Revisit the lecture materials and focus on the testing pyramid, AAA pattern, and clean code principles
- Below 4: Please review the lecture carefully and reach out during office hours if you need clarification
Key Takeaways to Remember
- Code quality ≠ code correctness - CI checks style, tests check logic
- Testing pyramid: 70% unit, 20% module, 10% E2E - Fast tests at the base
- Test cone is an anti-pattern - Inverted pyramid leads to slow, brittle tests
- AAA pattern: Arrange-Act-Assert - Standard structure for readable tests
- Exhaustive testing is impossible - Must use smart testing strategies
- pytest provides automation - Auto-discovery, rich output, CI/CD integration
- One concept per test - Clear failures, easy debugging
- Descriptive test names -
test_[function]_[scenario]_[expected]() - Fast feedback loops matter - Unit tests enable rapid iteration
- Test isolation is critical - Independent tests can run in any order
- Multiple asserts OK for same concept - But split different concepts
- Unit tests test in isolation - No dependencies on external systems
What’s Next?
Chapter 03 (Boundary Analysis) will cover:
- ✅ Equivalence class partitioning (smart testing strategies)
- ✅ Boundary value analysis (finding edge cases)
- ✅ LLM-assisted test writing (using AI to accelerate testing)
- ✅ Human-AI collaboration for testing
For now, practice:
- Write unit tests for simple functions
- Follow the AAA pattern
- Use descriptive test names
- Keep tests isolated and focused
Try this: Take a function you’ve written and write 3 unit tests for it using what you learned today!