Deriving Test Conditions and Scenarios from Requirements Documents

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()
Note: Notice how a single five-sentence requirement produced 10 test conditions โ€” and two of them (TC-201-08 and TC-201-09 about case sensitivity and whitespace trimming) are implicit conditions not stated in the requirement. These are the conditions that separate an experienced tester from a beginner. An experienced tester's domain knowledge allows them to identify implicit expectations that the specification author took for granted. Building this intuition takes practice, but the derivation framework โ€” systematically asking "what else could go wrong?" for every requirement โ€” accelerates the learning process.
Tip: Use a structured template for derivation: for each requirement, list test conditions in three columns โ€” Positive (valid inputs, expected behaviour), Negative (invalid inputs, error conditions), and Boundary (edge values, limits, transitions). This three-column approach ensures you do not over-focus on happy-path testing and neglect the error and edge-case scenarios where most production defects hide.
Warning: Do not confuse test conditions with test cases. A test condition is a high-level aspect to verify ("discount cannot reduce total below zero"). A test case is a specific scenario with concrete data and expected results ("subtotal $10 with 150% discount โ†’ total clamped at $0.00"). Deriving conditions first gives you a coverage map; expanding them into cases gives you executable tests. Jumping directly to test cases without the conditions step often results in gaps because you skip the systematic thinking about what needs to be covered.

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.

🧠 Test Yourself

A requirement states: "Each discount code can be used once per user." Which of the following is the MOST comprehensive set of test conditions derived from this single statement?