Privacy Notice: When you sign in with GitHub, your quiz attempts are stored in Google Firebase so you can track your progress. This data is only accessible to the course instructor, is not otherwise shared, and will be deleted at the end of the semester. For earlier deletion, contact the instructor. Prefer to stay anonymous? Use guest mode or a GitHub account without your real name.
Loading...

03 Exercise: CFG Tracing

Score: 0 / 19

CFG Tracing Exercise: Visualizing Statement and Branch Coverage

Introduction

This exercise helps you understand code coverage by tracing test cases through Control Flow Graphs (CFGs). By visualizing which nodes (statements) and edges (branches) are covered, you’ll build intuition for how coverage works.

Preparation: Read First

Before attempting this exercise, study the following lecture sections:

From Chapter 03 (Testing Theory and Coverage): Testing Theory & Coverage:

Learning Objectives

After completing these exercises, you should be able to:

  • Read and interpret Control Flow Graphs
  • Trace test case execution through code paths
  • Calculate Statement Coverage (C0) from node coverage
  • Calculate Branch Coverage (C1) from edge coverage
  • Design tests to achieve specific coverage goals
  • Understand why C1 subsumes C0

Instructions

  1. Study the code and CFG descriptions in each question
  2. Trace the given test case(s) through the graph
  3. Identify which nodes and edges are covered
  4. Calculate the coverage percentages
  5. Click to reveal explanations after answering

Time: 35-45 minutes for all exercises

Question1 Basic CFG Tracing

Exercise 1: Basic CFG - classify_number()

Consider this code:

def classify_number(x):
    if x > 0:           # Node 1 (decision)
        return "pos"    # Node 2
    elif x < 0:         # Node 3 (decision)
        return "neg"    # Node 4
    else:
        return "zero"   # Node 5

Test Case:

def test_positive():
    assert classify_number(5) == "pos"

With input x = 5:

  • Path: START → N1 (True) → N2 → END
  • Nodes covered: N1, N2 (2 out of 5 nodes)

What is the Statement Coverage (C0)? Enter a number (no % sign).

Question2 Basic CFG Tracing

Exercise 1: Basic CFG - classify_number() (continued)

Same code and test:

def test_positive():
    assert classify_number(5) == "pos"

Branch edges (decision outcomes) in the CFG:

  • N1 → N2 (True branch of x > 0)
  • N1 → N3 (False branch of x > 0)
  • N3 → N4 (True branch of x < 0)
  • N3 → N5 (False branch of x < 0)

Total branch edges: 4

The test covers only N1 → N2 (True branch).

What is the Branch Coverage (C1)? Enter a number (no % sign).

Question3 Multiple Test Cases

Exercise 2: Multiple Test Cases

Same classify_number() function, now with TWO tests:

def test_positive():
    assert classify_number(5) == "pos"

def test_negative():
    assert classify_number(-3) == "neg"

Traces:

  • test_positive: START → N1 (True) → N2 → END (covers N1, N2)
  • test_negative: START → N1 (False) → N3 (True) → N4 → END (covers N1, N3, N4)

Combined nodes covered: N1, N2, N3, N4 (4 out of 5)

What is the combined Statement Coverage (C0)? Enter a number (no % sign).

Question4 Multiple Test Cases

Exercise 2: Multiple Test Cases (continued)

Branch edges covered by both tests:

  • N1 → N2 (True): ✓ from test_positive
  • N1 → N3 (False): ✓ from test_negative
  • N3 → N4 (True): ✓ from test_negative
  • N3 → N5 (False): ✗ not covered

What is the combined Branch Coverage (C1)? Enter a number (no % sign).

Question5 Multiple Test Cases

Exercise 2: What test is needed for 100% coverage?

Given the two tests (test_positive and test_negative), which additional test would achieve 100% branch coverage?

Question6 Multiple Branches

Exercise 3: calculate_grade() - Multiple Branches

def calculate_grade(score):
    if score >= 90:         # N1
        return "A"          # N2
    elif score >= 80:       # N3
        return "B"          # N4
    elif score >= 70:       # N5
        return "C"          # N6
    elif score >= 60:       # N7
        return "D"          # N8
    else:
        return "F"          # N9

Two tests:

def test_grade_A():
    assert calculate_grade(95) == "A"  # Path: N1(T) → N2

def test_grade_B():
    assert calculate_grade(85) == "B"  # Path: N1(F) → N3(T) → N4

This function has 4 decision points (N1, N3, N5, N7), each with True/False outcomes. Total branches: 8

Branches covered:

  • N1 True: ✓, N1 False: ✓, N3 True: ✓, N3 False: ✗
  • N5 True: ✗, N5 False: ✗, N7 True: ✗, N7 False: ✗

What is the Branch Coverage (C1)? Enter the percentage as a decimal (e.g., 37.5).

Question7 Multiple Branches

Exercise 3: How many more tests needed?

To achieve 100% Branch Coverage (C1) for calculate_grade(), how many ADDITIONAL tests are needed beyond the two already provided (test_grade_A and test_grade_B)?

Enter a number.

Question8 C0 vs C1 Relationship

Exercise 4: C0 vs C1 Comparison

def check_eligibility(age, has_license):
    if age >= 18:               # N1
        if has_license:         # N2
            return "approved"   # N3
        return "need license"   # N4
    return "too young"          # N5

Two tests:

def test_approved():
    assert check_eligibility(25, True) == "approved"
    # Path: N1(T) → N2(T) → N3

def test_need_license():
    assert check_eligibility(20, False) == "need license"
    # Path: N1(T) → N2(F) → N4

Coverage results:

  • C0: 4/5 nodes = 80% (N1, N2, N3, N4 covered; N5 not covered)
  • C1: 3/4 branches = 75% (N1 True, N2 True, N2 False covered; N1 False not covered)

Which statement is correct about C0 and C1?

Question9 Nested Conditions

Exercise 5: Nested Conditions - validate_password()

def validate_password(password):
    if len(password) < 8:           # N1
        return "too short"          # N2
    has_upper = any(c.isupper() for c in password)  # N3
    has_digit = any(c.isdigit() for c in password)  # N4
    if not has_upper:               # N5
        return "need uppercase"     # N6
    if not has_digit:               # N7
        return "need digit"         # N8
    return "valid"                  # N9

Test:

def test_valid_password():
    assert validate_password("SecurePass1") == "valid"

Trace: “SecurePass1” has length 11, uppercase letters, and a digit. Path: N1(F) → N3 → N4 → N5(F) → N7(F) → N9

Nodes covered: N1, N3, N4, N5, N7, N9 (6 out of 9)

What is C0? Enter a number (no % sign, round to nearest integer).

Question10 Nested Conditions

Exercise 5: Nested Conditions (continued)

Same test for validate_password():

Decision outcomes:

  • N1 (len < 8): True = NOT covered, False = Covered ✓
  • N5 (not has_upper): True = NOT covered, False = Covered ✓
  • N7 (not has_digit): True = NOT covered, False = Covered ✓

What is C1? Enter a number (no % sign).

Question11 Nested Conditions

Exercise 5: Which tests are needed for 100% C1?

Select ALL tests that would help achieve 100% branch coverage for validate_password():

Select all that apply:

Question12 Early Returns

Exercise 6: Early Returns - calculate_discount()

def calculate_discount(amount, is_member, coupon_code):
    if amount <= 0:                     # N1
        return 0                        # N2
    discount = 0                        # N3
    if is_member:                       # N4
        discount += 10                  # N5
    if coupon_code == "SAVE20":         # N6
        discount += 20                  # N7
    elif coupon_code == "SAVE10":       # N8
        discount += 10                  # N9
    return amount * (discount / 100)   # N10

Two tests:

def test_member_with_save20():
    result = calculate_discount(100, True, "SAVE20")
    # Path: N1(F) → N3 → N4(T) → N5 → N6(T) → N7 → N10

def test_non_member_no_coupon():
    result = calculate_discount(100, False, None)
    # Path: N1(F) → N3 → N4(F) → N6(F) → N8(F) → N10

Nodes covered: N1, N3, N4, N5, N6, N7, N8, N10 (8 out of 10)

What is C0? Enter a number (no % sign).

Question13 Early Returns

Exercise 6: Which branches are NOT covered?

Same tests as above. Select ALL uncovered branches:

Select all that apply:

Question14 Complex Conditions

Exercise 7: Complex Conditions - approve_loan()

def approve_loan(income, credit_score, has_collateral):
    if income < 30000:                          # N1
        return "rejected: low income"           # N2
    if credit_score < 600:                      # N3
        return "rejected: poor credit"          # N4
    if income >= 100000 and credit_score >= 750:  # N5
        return "approved: premium"              # N6
    if credit_score >= 700 or has_collateral:   # N7
        return "approved: standard"             # N8
    return "manual review required"             # N9

Three tests:

def test_premium_approval():
    assert approve_loan(150000, 800, False) == "approved: premium"
    # Path: N1(F) → N3(F) → N5(T) → N6

def test_standard_with_good_credit():
    assert approve_loan(50000, 720, False) == "approved: standard"
    # Path: N1(F) → N3(F) → N5(F) → N7(T) → N8

def test_standard_with_collateral():
    assert approve_loan(50000, 650, True) == "approved: standard"
    # Path: N1(F) → N3(F) → N5(F) → N7(T) → N8

Nodes covered: N1, N3, N5, N6, N7, N8 (6 out of 9)

What is C0? Enter a number (no % sign, round to nearest integer).

Question15 Complex Conditions

Exercise 7: Complex Conditions (continued)

Branch outcomes:

  • N1 (income < 30k): True = ✗, False = ✓
  • N3 (credit < 600): True = ✗, False = ✓
  • N5 (income>=100k AND credit>=750): True = ✓, False = ✓
  • N7 (credit>=700 OR collateral): True = ✓, False = ✗

What is C1? Enter the percentage as a decimal (e.g., 62.5).

Question16 Test Design

Exercise 8: Design Your Own Tests

def process_order(total, user_type, promo_code):
    if total <= 0:                          # N1
        return "invalid order"              # N2
    base_discount = 0                       # N3
    if user_type == "premium":              # N4
        base_discount = 15                  # N5
    elif user_type == "member":             # N6
        base_discount = 5                   # N7
    if promo_code == "FLASH50":             # N8
        if total >= 100:                    # N9
            return f"discount: 50%"         # N10
        return "promo requires $100+"       # N11
    if base_discount > 0:                   # N12
        return f"discount: {base_discount}%"  # N13
    return "no discount"                    # N14

How many decision points (if/elif statements) does this code have?

Enter a number.

Question17 Test Design

Exercise 8: Minimum Tests for 100% C1

The process_order() function has 6 decision points with 12 total branches.

What is the minimum number of tests needed for 100% Branch Coverage (C1)?

Enter a number.

Question18 Coverage Relationships

Summary: C1 Subsumes C0

Based on the exercises, which statement about coverage relationships is TRUE?

Question19 CFG Insights

Summary: Key Insight from CFG Tracing

What is the most important insight from tracing tests through Control Flow Graphs?

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk