# Weight dimension (fix distance=50, express=False)
test_light_weight() # 2.5 kg → €5
test_medium_weight() # 10 kg → €10
test_heavy_weight() # 30 kg → €20
# Distance dimension (fix weight=2.5, express=False)
test_local_distance() # 50 km → €5 × 1.0
test_regional_distance() # 250 km → €5 × 1.5
test_long_distance() # 1000 km → €5 × 2.0
# Express dimension (fix weight=2.5, distance=50)
test_standard_shipping() # €5 × 1.0
test_express_shipping() # €5 × 1.5
# Boundary tests + error tests
Result: ~10 strategic tests instead of 18!
When discrete is EASIER:
Output categories are obvious (just list return values!)
No granularity debates (function signature defines classes)
Complete coverage achievable (6 grades? Write 6 tests!)
When discrete is HARDER:
Many possible outputs (8 validation errors = 8 tests minimum)
Combinatorial explosion (3 × 3 × 2 = 18 classes)
Hierarchical validation (order matters - early returns)
Boundary values still critical (89 vs 90 can hide bugs)
Takeaway: Discrete simplifies output partitioning, not input analysis!
Use discrete analysis when:
Use continuous analysis when:
Use hybrid analysis when:
Looks continuous:
tuple[float, float] | None (coordinates)But outcomes are discrete:
NoneKey insight: Even though inputs are continuous (floats), the outcomes are discrete!
Testing strategy:
Identify discrete outcome categories, then pick representative inputs for each!
This is exactly what we did with calculate_shipping_cost!
Pattern:
From your Road Profile Viewer project:
def find_intersection(
x_road: np.ndarray, # Road profile arrays
y_road: np.ndarray,
angle_degrees: float, # Ray direction
camera_x: float = 0, # Camera position
camera_y: float = 1.5
) -> tuple[float, float] | None:
# ... 70 lines of geometric logic ...
Think about it: How would you test this?
Important: This example is so complex that we've created a separate detailed walkthrough with 22 visual illustrations!
See the "Deep Dive - Testing find_intersection()" slide deck for the complete analysis.
Full details:
Interactive Poll:
How many tests do you think we need for
find_intersection()?A) 5-10 tests
B) 20-30 tests
C) 50-100 tests
D) More than 100 tests
Discuss with your neighbor! (30 seconds)
x_road and y_road arrays:
len = 0) → error handlinglen = 1) → degenerate caselen = 2) → one segment, minimal validlen = 3) → two segmentslen > 3) → multiple segments, typical caseangle_degrees:
−90° < angle < 0°)angle = 0°)0° < angle < 90°)angle = ±90°) → no slope, special handling|angle| > 90°) → invalid inputcamera_x and camera_y:
Why these matter:
Based on what the function returns:
(x, y)NoneNoneWhy these matter:
Geometric properties:
Why these matter:
For find_intersection(), we identified:
Let's do the math...
(We simplified road shape to avoid going over 1000!)
Reality check: Can you write and maintain 600 tests?
Spoiler: No! That's where human insight comes in...
You might think: "Just ask ChatGPT/Claude to generate all 600 tests!"
Problems with AI-generated tests:
Bottom line: LLMs can help, but can't replace human insight!
Human insight:
Result: Humans can prune 600 → 20-30 strategic tests based on domain knowledge.
This is your value as an engineer!
Strategy: Cover each dimension with 2-3 representatives
Math: 3 + 3 + 2 + 4 + 2 = 14 base tests
Plus edge cases: Vertical ray, parallel segment, out-of-range → ~10 more
Total: ~25-30 tests
Want to see the actual tests? Check out the "Deep Dive - Testing find_intersection()" slide deck for all 22 tests with visual diagrams!
class TestFindIntersection:
# Structural dimension
def test_empty_arrays(self): ...
def test_two_point_road(self): ...
def test_many_point_road(self): ...
# Angle dimension
def test_downward_ray(self): ...
def test_horizontal_ray(self): ...
def test_upward_ray(self): ...
def test_vertical_ray(self): ... # Edge case
# Outcome dimension
def test_intersection_found(self): ...
def test_no_intersection(self): ...
def test_ray_parallel_to_segment(self): ...
class TestFindIntersection:
# ... (previous tests)
# Smart combinations (domain-driven)
def test_camera_on_road_horizontal_ray(self): ...
def test_camera_above_road_downward_ray_hits(self): ...
def test_camera_below_road_upward_ray_hits(self): ...
def test_camera_far_right_upward_ray_misses(self): ...
def test_flat_road_any_angle(self): ...
def test_peaked_road_multiple_intersections(self): ...
# Edge cases
def test_ray_through_road_endpoint(self): ...
def test_angle_out_of_range(self): ...
Human: Identify classes, choose granularity, prioritize risks, review
LLM: Generate boilerplate, create test data, write assertions, suggest edges
Workflow: You design → LLM implements → You review!
Good prompts:
find_intersection with a two-point flat road and horizontal ray"None when the ray misses the road" Bad prompts:
find_intersection" → produces low-quality noiseKey: Be specific about equivalence classes and expected behavior!
Scenario: You're testing find_intersection() for a self-driving car.
Questions for discussion:
Think-pair-share: Discuss with your neighbor! (2 minutes)
1. Equivalence classes reduce infinite inputs to finite tests
2. Output categories determine input classes
reciprocal_sum(x,y,z): 3 output behaviors → 3 classes3. Granularity is a design choice
4. White-box testing reveals hidden equivalence classes
reciprocal_sum code reveals sum is what matters5. Real-world functions have combinatorial complexity
find_intersection: 5 dimensions × many classes = 600 tests6. Human insight is irreplaceable
Testing Pyramid (Reminder):
/ \
/ E2E \ 10% - End-to-End (full system)
/_______\
/ \
/Integration\ 20% - Integration (modules together)
/_____________\
/ \
/ Unit Tests \ 70% - Unit Tests (individual functions)
/___________________\
Today's focus: How to choose which unit tests to write efficiently!
Next topic: Boundary value analysis (finding bugs at edges)
Equivalence class testing is powerful, but it's not enough!
Problem: Bugs often hide at boundaries between classes.
Example for reciprocal(x):
x = 5.0, x = -5.0, x = 0.0 x = 0.0000001? (very close to zero) x = 1e308? (very large, near float max)Boundary value analysis says: Test at the edges of equivalence classes!
Next lecture: How to find and test boundaries systematically.
Exercise: Choose equivalence classes for this function:
def classify_temperature(temp_celsius: float) -> str:
"""Classify temperature for weather app"""
if temp_celsius < 0:
return "freezing"
elif temp_celsius < 10:
return "cold"
elif temp_celsius < 20:
return "mild"
elif temp_celsius < 30:
return "warm"
else:
return "hot"
Take 2 minutes: Work individually or with a partner
For classify_temperature(temp_celsius):
How many equivalence classes?
What are they?
What representative values would you choose?
Bonus: What boundary values should you test?
Full lecture: Chapter 03 (Testing Basics) - Testing Fundamentals
For any function, ask:
Result: Finite, manageable test suite that covers infinite input space!
Remember: You're not testing all inputs — you're testing all behaviors!