Testing in Practice — Putting Fundamentals to Work on a Real Project

You have learned what testing is, why it matters, the seven principles, the testing mindset and the core vocabulary. Now it is time to put it all together. In this lesson you will walk through a realistic mini-project — a simple e-commerce checkout feature — applying the fundamentals from this chapter exactly as you would on day one of a QA role. This practical exercise bridges the gap between theory and real-world testing.

Applying Testing Fundamentals to a Real Checkout Feature

Imagine you have just joined a QA team. The developers have built a checkout function and your task is to test it before release. You need to write test cases, think about edge cases, classify any defects you find and report them clearly. Let us walk through this process step by step.

# The function under test — a simplified checkout calculator
def calculate_order_total(items, discount_code=None):
    """
    Calculate the total price of an order.
    items: list of dicts with 'name', 'price', 'quantity'
    discount_code: optional string for a percentage discount
    """
    DISCOUNT_CODES = {
        "SAVE10": 10,
        "SAVE20": 20,
    }

    subtotal = sum(item["price"] * item["quantity"] for item in items)

    if discount_code:
        if discount_code in DISCOUNT_CODES:
            pct = DISCOUNT_CODES[discount_code]
            subtotal = subtotal - (subtotal * pct / 100)
        # BUG: invalid codes are silently ignored — no error raised

    tax = round(subtotal * 0.08, 2)   # 8% tax
    total = round(subtotal + tax, 2)
    return {"subtotal": round(subtotal, 2), "tax": tax, "total": total}


# ── Test cases applying Chapter 1 fundamentals ──

# TC-001 Happy path: single item, no discount
result = calculate_order_total([{"name": "Shirt", "price": 25.00, "quantity": 2}])
assert result == {"subtotal": 50.00, "tax": 4.00, "total": 54.00}, f"TC-001 FAIL: {result}"

# TC-002 Valid discount code
result = calculate_order_total(
    [{"name": "Shoe", "price": 100.00, "quantity": 1}],
    discount_code="SAVE10"
)
assert result == {"subtotal": 90.00, "tax": 7.20, "total": 97.20}, f"TC-002 FAIL: {result}"

# TC-003 Invalid discount code — tester mindset: what SHOULD happen?
result = calculate_order_total(
    [{"name": "Hat", "price": 30.00, "quantity": 1}],
    discount_code="FAKE99"
)
# The function silently ignores invalid codes. Is that a defect?
# A tester would file this: "Invalid discount code accepted silently —
# expected an error or user-facing message."
print(f"TC-003 (invalid code): {result}")

# TC-004 Edge case: empty cart
result = calculate_order_total([])
assert result == {"subtotal": 0.00, "tax": 0.00, "total": 0.00}, f"TC-004 FAIL: {result}"

# TC-005 Edge case: zero-price item
result = calculate_order_total([{"name": "Freebie", "price": 0, "quantity": 1}])
assert result == {"subtotal": 0.00, "tax": 0.00, "total": 0.00}, f"TC-005 FAIL: {result}"

print("All checkout tests completed ✓")
Note: Notice how TC-003 reveals a design decision that could be a defect — invalid discount codes are silently ignored. Whether this is a bug depends on the requirements. If the spec says “show an error for invalid codes,” it is a defect. If the spec is silent on the topic, the tester should raise it as a question or risk item. This is a perfect example of why testers need requirements analysis skills, not just the ability to run scripts.
Tip: Organise your test cases using a simple numbering convention from day one: TC-001 through TC-NNN, grouped by feature area. Add a short description to each ID. This habit scales from your very first project to enterprise-level test management in tools like TestRail or Zephyr.
Warning: Avoid the temptation to only test the code paths you can see. In a real project, you will not have access to source code during black-box testing. Practice deriving test cases from requirements and user stories, not from reading the implementation. This skill is essential for UAT, exploratory testing and most manual testing roles.

Common Mistakes

Mistake 1 — Skipping edge cases because the happy path works

❌ Wrong: “The checkout works with one item and a valid discount — ship it.”

✅ Correct: Testing empty carts, zero-quantity items, maximum quantities, expired discount codes, case-sensitive codes, and concurrent checkouts before declaring the feature ready.

Mistake 2 — Not documenting the expected result before running a test

❌ Wrong: Running a test, seeing output, and deciding after the fact whether it “looks right.”

✅ Correct: Writing the expected result first (from requirements or specifications), then comparing actual results against that documented expectation. This prevents confirmation bias.

🧠 Test Yourself

During testing of a checkout feature, you discover that entering an invalid discount code does not show an error — the code is silently ignored. The requirements document does not mention this scenario. What should you do?