Once you have analysed requirements for testability and resolved ambiguities, the next step is to derive test conditions โ the specific aspects of a requirement that need to be verified. A single requirement can generate multiple test conditions, and each test condition can produce multiple test cases. This derivation process is the bridge between requirements analysis and test case design. Doing it systematically ensures nothing falls through the cracks.
From Requirements to Test Conditions to Test Cases
Think of the hierarchy as a funnel: one requirement generates several test conditions, and each condition generates several test cases with specific data and expected results.
# Deriving test conditions from a single requirement
requirement = {
"id": "REQ-201",
"text": (
"Registered users can apply a discount code at checkout. "
"Valid codes reduce the subtotal by the specified percentage. "
"Each code can be used once per user. "
"Expired codes display an error message. "
"The discount cannot reduce the total below zero."
),
}
# Step 1: Break the requirement into atomic test conditions
test_conditions = [
{"tc_id": "TC-201-01", "condition": "Valid discount code reduces subtotal correctly",
"type": "Positive"},
{"tc_id": "TC-201-02", "condition": "Discount percentage is applied to subtotal, not total with tax",
"type": "Positive"},
{"tc_id": "TC-201-03", "condition": "Code can be used once per user โ second use is rejected",
"type": "Negative"},
{"tc_id": "TC-201-04", "condition": "Expired code displays specific error message",
"type": "Negative"},
{"tc_id": "TC-201-05", "condition": "Discount cannot reduce total below zero",
"type": "Boundary"},
{"tc_id": "TC-201-06", "condition": "Invalid (non-existent) code is rejected with error",
"type": "Negative"},
{"tc_id": "TC-201-07", "condition": "Empty code field โ submit without entering a code",
"type": "Negative"},
{"tc_id": "TC-201-08", "condition": "Code is case-insensitive (SAVE20 = save20 = Save20)",
"type": "Positive โ implicit"},
{"tc_id": "TC-201-09", "condition": "Code with leading/trailing spaces is trimmed and accepted",
"type": "Positive โ implicit"},
{"tc_id": "TC-201-10", "condition": "Unregistered (guest) user cannot apply discount codes",
"type": "Negative โ from 'registered users' constraint"},
]
# Step 2: Expand one test condition into detailed test cases
expanded_cases = [
{
"condition": "TC-201-05: Discount cannot reduce total below zero",
"test_cases": [
{"data": "Subtotal $10, Discount 100%", "expected": "Total = $0.00 (not negative)"},
{"data": "Subtotal $10, Discount 150%", "expected": "Total = $0.00 (clamped at zero)"},
{"data": "Subtotal $10, Discount 99%", "expected": "Total = $0.10 + tax"},
{"data": "Subtotal $0, Discount 50%", "expected": "Total = $0.00"},
],
},
]
print(f"Requirement: {requirement['id']}")
print(f" {requirement['text']}")
print(f"\nDerived Test Conditions: {len(test_conditions)}")
print("=" * 65)
for tc in test_conditions:
print(f" [{tc['type']:<22}] {tc['tc_id']}: {tc['condition']}")
print(f"\n\nExpanded Test Cases for {expanded_cases[0]['condition']}:")
print("-" * 65)
for case in expanded_cases[0]['test_cases']:
print(f" Data: {case['data']}")
print(f" Expected: {case['expected']}")
print()
Common Mistakes
Mistake 1 โ Deriving only happy-path test conditions from requirements
โ Wrong: REQ-201 produces 2 test conditions: "apply valid code" and "see discount applied."
โ Correct: REQ-201 produces 10+ conditions covering positive, negative, boundary, and implicit scenarios โ expired codes, duplicate use, zero-total clamping, case sensitivity, guest user restriction.
Mistake 2 โ Not deriving test conditions for implicit requirements
โ Wrong: Only creating conditions for what the spec explicitly states and ignoring universally expected behaviours.
โ Correct: Adding conditions for implicit expectations like case sensitivity, whitespace handling, concurrent access, and session timeout โ behaviours that users expect even when the spec is silent.