Home

03 Quiz: Testing Fundamentals - Unit Testing and Clean Code

quiz chapter-03 testing pytest unit-testing test-pyramid clean-code

Instructions

This quiz tests your understanding of key concepts from Chapter 03 (Testing Basics): Testing Fundamentals.

Why Take This Quiz?

For Your Learning:

For Course Improvement:

Guidelines:


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:

  1. Slow feedback: Every change requires running slow E2E tests (minutes, not seconds)
  2. Hard to debug: When E2E test fails, which of the 10 modules caused it?
  3. Brittle: UI changes break many E2E tests
  4. 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:

  1. Arrange: Set up test data and conditions
  2. Act: Call the function being tested
  3. 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_*.py files 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


Key Takeaways to Remember

  1. Code quality ≠ code correctness - CI checks style, tests check logic
  2. Testing pyramid: 70% unit, 20% module, 10% E2E - Fast tests at the base
  3. Test cone is an anti-pattern - Inverted pyramid leads to slow, brittle tests
  4. AAA pattern: Arrange-Act-Assert - Standard structure for readable tests
  5. Exhaustive testing is impossible - Must use smart testing strategies
  6. pytest provides automation - Auto-discovery, rich output, CI/CD integration
  7. One concept per test - Clear failures, easy debugging
  8. Descriptive test names - test_[function]_[scenario]_[expected]()
  9. Fast feedback loops matter - Unit tests enable rapid iteration
  10. Test isolation is critical - Independent tests can run in any order
  11. Multiple asserts OK for same concept - But split different concepts
  12. Unit tests test in isolation - No dependencies on external systems

What’s Next?

Chapter 03 (Boundary Analysis) will cover:

For now, practice:

Try this: Take a function you’ve written and write 3 unit tests for it using what you learned today!

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk