Classification Tree Method: Structured Test Design with CTE
The Classification Tree Method (CTM), developed by Matthias Grochtmann and Klaus Grimm at Daimler-Benz Research in the early 1990s, is a structured approach to black-box test design that makes the input space visible. Unlike equivalence partitioning — which most testers apply informally and inconsistently — CTM gives you a formal tree structure that shows exactly what you're covering and what you're missing.
The name "Classification Tree" refers to the hierarchical breakdown of the test object's input space into mutually exclusive, collectively exhaustive classes. Each leaf in the tree represents a test class. Combinations of leaves — one from each branch — produce test cases.
The Core Concept
A classification tree decomposes the input domain of a system under test along multiple independent dimensions, called classifications. Each classification is broken into classes that partition that dimension completely.
The rules:
- Classes within a classification must be mutually exclusive — no input belongs to more than one class
- Classes within a classification must be collectively exhaustive — every possible input belongs to exactly one class
- Classifications must be independent — the choice in one classification doesn't (in principle) depend on choices in others
These constraints are what make the method formal rather than ad hoc.
Building a Classification Tree: Step by Step
Let's build one for a flight search feature. Inputs:
- Origin and destination airports
- Travel dates
- Passenger count and types (adults, children, infants)
- Cabin class (economy, business, first)
- Trip type (one-way, round-trip, multi-city)
Step 1: Identify Classifications
The root of the tree is the test object. Branch off into independent input dimensions:
Flight Search
├── Trip Type
├── Date Selection
├── Passenger Count
├── Passenger Types
└── Cabin ClassStep 2: Define Classes for Each Classification
Now partition each dimension:
Flight Search
├── Trip Type
│ ├── One-way
│ ├── Round-trip
│ └── Multi-city (3+ legs)
│
├── Date Selection
│ ├── Today (same day)
│ ├── Near future (1-30 days)
│ ├── Far future (31-365 days)
│ └── Beyond one year
│
├── Passenger Count
│ ├── 1 passenger
│ ├── 2-8 passengers
│ └── 9+ passengers (group booking)
│
├── Passenger Types
│ ├── Adults only
│ ├── Adults + children
│ ├── Adults + infants
│ └── Mixed (adults, children, infants)
│
└── Cabin Class
├── Economy
├── Premium Economy
├── Business
└── FirstThis is the classification tree. It has 5 classifications with 3-4 classes each.
Step 3: Derive Test Cases
Each test case selects exactly one class from each classification. The total number of combinations: 3 × 4 × 3 × 4 × 4 = 576. You obviously won't test all 576.
The point of CTM is to reduce this intelligently while maintaining meaningful coverage. The minimum number of test cases is the maximum number of classes in any single classification — in this case, 4.
Four test cases, one leaf covered per row:
| Test | Trip Type | Date | Passengers | Types | Cabin |
|---|---|---|---|---|---|
| T1 | One-way | Today | 1 | Adults only | Economy |
| T2 | Round-trip | Near future | 2-8 | Adults+children | Premium Economy |
| T3 | Multi-city | Far future | 9+ | Adults+infants | Business |
| T4 | One-way* | Beyond 1 year | 1* | Mixed | First |
*Reused from earlier — the goal is that every class appears at least once.
But 4 tests is the floor, not the target. For a flight search feature, you'd want to add more: the boundary of 9 passengers (what happens at 9 vs 8?), the date boundary at 365 days, round-trip with mixed passengers, etc.
Step 4: Add Refinements
Classifications can themselves be hierarchical. The "Passenger Types" classification could be refined:
Passenger Types
├── Adults only
│ ├── Single adult
│ └── Multiple adults
├── With minors
│ ├── Children (2-11) without lap infants
│ ├── Lap infants (< 2) without older children
│ └── Both children and lap infants
└── Business travelers (adults, frequent flyers)Refinement is the key difference between CTM and a flat equivalence partition table. The tree structure lets you add detail incrementally where it matters without exploding the complexity everywhere.
The CTE (Classification Tree Editor) Tool
Grochtmann and Grimm built CTM with tooling in mind. The Classification Tree Editor (CTE) is software that:
- Lets you draw the tree interactively
- Automatically generates test cases from selected leaves
- Shows coverage — which classes are in no test case yet
- Exports to test management tools
CTE-XL (the commercial successor) and TESTONA are the main tools. The open-source ACTS tool from NIST covers similar ground. For teams without dedicated tools, a spreadsheet with one row per test case and one column per classification works.
A More Complex Example: API Endpoint Testing
Classification trees aren't just for UI features. Consider an API endpoint POST /api/v2/payment:
Request body fields:
amount(required, number)currency(required, string)payment_method(required, enum)customer_id(required, string)idempotency_key(optional, string)metadata(optional, object)
Classifications:
POST /payment
├── Amount
│ ├── Zero (invalid)
│ ├── Positive, within limit ($0.01 - $9,999.99)
│ ├── At limit boundary ($10,000.00)
│ ├── Above limit (> $10,000.00)
│ └── Negative (invalid)
│
├── Currency
│ ├── Supported (USD, EUR, GBP)
│ ├── Unsupported but valid ISO code (JPY if not enabled)
│ └── Invalid string (not ISO 4217)
│
├── Payment Method
│ ├── card
│ ├── bank_transfer
│ ├── wallet
│ └── Unknown/invalid value
│
├── Customer ID
│ ├── Valid, existing customer
│ ├── Valid format, non-existent customer
│ └── Invalid format
│
├── Idempotency Key
│ ├── Absent
│ ├── Present, unique
│ └── Present, duplicate (replay scenario)
│
└── Request State
├── First request
├── Retry (same idempotency key)
└── Concurrent duplicate requestsMinimum test cases = 5 (the maximum depth across all classifications). Realistic test suite = 12-15 cases covering important interactions.
Key combinations to cover:
- All valid inputs → success (T1)
- Invalid amount with all others valid → 400 error (T2)
- Duplicate idempotency key → 200 with cached response, not duplicate charge (T3)
- Concurrent duplicates → one succeeds, one gets conflict response (T4)
- Unknown payment method → descriptive error, not 500 (T5)
The tree makes it obvious that "concurrent duplicate requests" is its own test class. Without the tree, most teams don't think to test it.
Constraints: When Classes Interact
Like cause-effect graphing, CTM needs to handle impossible combinations. The tool approach is to mark certain class combinations as forbidden.
Example: if Trip Type = "Multi-city", then Cabin Class cannot = "First" (most airlines don't offer first class on multi-city bookings). Mark the combination [Multi-city, First] as forbidden. Your test generation skips it.
In a spreadsheet, just leave those cells empty or mark them N/A. Don't waste a test case on an impossible state.
Some combinations are not just impossible but interesting: what happens when a user tries to book first class on a multi-city route? The system should block it gracefully. That's a separate test class: "UI prevents invalid combination" — which belongs in a separate classification tree for the UI validation behavior.
CTM vs Equivalence Partitioning
These are related techniques. CTM is essentially structured equivalence partitioning with formalism:
| Aspect | Equivalence Partitioning | Classification Tree Method |
|---|---|---|
| Structure | Flat list of partitions | Hierarchical tree |
| Multiple inputs | Handle separately, recombine manually | Tree branches represent independent dimensions |
| Documentation | Usually informal | Explicit visual artifact |
| Tooling | Spreadsheet | Dedicated tools (CTE, TESTONA) |
| Coverage visibility | Low — easy to miss classes | High — tree shows what's uncovered |
The practical difference: equivalence partitioning applied informally by a developer who's tired will miss the "9+ passengers" class. The classification tree, once built, makes the gap obvious.
CTM vs Pairwise Testing
Both techniques deal with the combinatorial explosion of input combinations. They attack it differently:
- CTM: Explicit about each class, allows domain knowledge to choose important combinations, handles hierarchical input structures
- Pairwise: Algorithmic reduction to pairs, doesn't require explicit class definition, scales to many parameters
For feature testing with 5-8 independent inputs, CTM gives better control. For configuration testing with 15+ parameters (OS versions, browser versions, database versions, etc.), pairwise is more practical. They're not mutually exclusive — you can use CTM to define the classes, then apply pairwise to select test combinations.
When to Build a Classification Tree
Build one when:
Requirements exist but aren't yet broken down into test cases. The tree-building process is the requirements analysis. Walk through the tree with a business analyst: "Is 'far future' correct, or should that be two classes: 31-180 days and 181-365 days?" You'll catch ambiguities before implementation.
You need to justify test coverage to a stakeholder. A tree is auditable. "We test all 17 classes across 6 input dimensions using 12 test cases" is a statement you can back up with the artifact.
You're testing a component that will be tested repeatedly — across releases, across team members, across regression cycles. The tree is a persistent test design artifact, not a one-time note.
You're onboarding new testers to a complex domain. The tree communicates what the input space looks like. A new QA engineer looking at the classification tree for a tax calculation engine understands the feature better in 10 minutes than from reading the requirements for an hour.
Common Mistakes
Classes that aren't mutually exclusive. "Passenger Count: few passengers" and "Passenger Count: small group" overlap. Force yourself to define clear boundaries: 1, 2-4, 5-8, 9+.
Missing the "invalid" classes. Every classification should include at least one invalid or error class. "Amount: negative" is a class. Omitting it means you don't have a test for negative payment amounts.
Tree too deep, too early. Build the first pass of the tree at two levels (classification → classes). Add sub-classifications only where the risk justifies it. A tree with 8 levels is not better than one with 3 levels — it's just harder to use.
Treating the minimum test count as the target. The minimum (= max classes in any one classification) is a mathematical floor. Real-world testing targets important combinations, not mathematical minimums. Add tests for high-risk combinations even if every class is already covered.
Integration with Test Automation
Classification trees translate directly to parameterized test frameworks:
import pytest
TRIP_TYPES = ["one-way", "round-trip", "multi-city"]
DATE_RANGES = ["today", "near-future", "far-future", "beyond-one-year"]
PASSENGER_COUNTS = [1, 5, 9]
@pytest.mark.parametrize("trip_type,date_range,passengers", [
("one-way", "today", 1),
("round-trip", "near-future", 5),
("multi-city", "far-future", 9),
("one-way", "beyond-one-year", 1),
])
def test_flight_search(trip_type, date_range, passengers):
result = search_flights(trip_type, date_range, passengers)
assert result.status == "ok"The parametrize decorator maps directly to the rows in your test design table. When the classification tree changes, updating the parameter list is a 2-minute task.
The Deliverable
A completed classification tree gives you three things:
- The tree itself — a visual or tabular record of how the input space is partitioned. This is the test design document.
- The test case table — which class combinations are tested, with concrete values assigned.
- The coverage matrix — which classes appear in which test cases. Any class not covered by at least one test case is a gap.
For a well-tested feature, every leaf in the tree appears in at least one test case. For a risk-based approach, high-risk paths appear in multiple test cases with different combinations.
The tree outlives any single test cycle. When a new requirement adds a payment method or a new cabin class, you update the tree, identify which existing test cases cover the new class (none), and add test cases for the gap. This is what structured test design looks like in practice.