Equivalence Partitioning for Non-Numeric Types: Strings, Dates, Enums, and Collections

Equivalence Partitioning for Non-Numeric Types: Strings, Dates, Enums, and Collections

Most equivalence partitioning (EP) tutorials use age ranges and temperature thresholds. Clean, numeric, easy to understand. The real world is messier: username validation, date ranges with timezone edge cases, enum state machines, and variable-length collections.

This post covers EP for non-numeric input types where the partition boundaries aren't as obvious as "1 to 100."

Review: What EP Is Solving

EP assumes that if a system handles one value in a partition correctly, it handles all values in that partition correctly. This lets you test one representative from each partition instead of every possible value.

The skill is identifying the right partitions. For numeric ranges, partition boundaries are explicit. For other types, you have to derive them from system behavior.

Equivalence Partitioning for Strings

Strings are the most complex input type for EP because they have multiple dimensions: length, character content, encoding, and format.

Length partitions

For a username field (3–20 characters):

Partition Range Representative
Empty 0 chars ""
Too short 1–2 chars "ab"
Valid minimum 3 chars "abc"
Valid typical 4–19 chars "johndoe"
Valid maximum 20 chars "abcdefghijklmnopqrst"
Too long 21+ chars "abcdefghijklmnopqrstu"

These are the same length partitions you'd use for any input — but they only cover one dimension of string validation.

Character type partitions

Most string fields have character restrictions. A username might allow alphanumeric + underscore but reject everything else:

Partition Content Representative
All lowercase a-z only "johndoe"
All uppercase A-Z only "JOHNDOE"
Mixed case Both "JohnDoe"
Numbers only 0-9 "12345678"
Special chars (valid) Underscore "john_doe"
Special chars (invalid) Other symbols "john@doe"
Unicode letters Latin extended "Jöhn"
Unicode symbols Emoji, CJK "john😀"
Whitespace Space, tab, newline "john doe"
Null byte \x00 "john\x00doe"

Each character category can behave differently. Null bytes and control characters often bypass string length checks and break downstream processing. Unicode characters may bypass character-allowlist checks.

Format-specific partitions

For email addresses:

Partition Representative Notes
Valid simple "user@example.com" Baseline valid
Valid complex "user.name+tag@sub.example.co.uk" Subdomains, plus-addressing
No @ symbol "userexample.com" Missing required character
Multiple @ "user@@example.com" Invalid format
No domain "user@" Missing domain
No local part "@example.com" Missing local part
Internationalized "üser@example.com" IDN email
Very long local "a" × 65 + "@example.com" RFC 5321 limit is 64 chars
Very long domain "user@" + "a" × 256 + ".com" DNS name limit
IP address domain "user@[192.168.1.1]" Valid per RFC, often blocked

For phone numbers, passwords, URLs, credit card numbers — each format has its own partition structure derived from the format specification and common validation failure modes.

SQL injection and XSS partitions

For any string that reaches a database or HTML output:

Partition Representative
SQL keywords "'; DROP TABLE users; --"
SQL quote escaping "O'Brien"
XSS payload ""
HTML entities "<script>"
Unicode bypasses "<script>" (fullwidth)

These partitions aren't about normal valid/invalid — they're security-relevant partitions that verify the system handles potentially malicious input without executing it.

Equivalence Partitioning for Dates and Times

Date EP is complex because of calendar irregularities and time zones.

Date value partitions

For an event date field (must be future date):

Partition Representative
Well in the past "2000-01-01"
Yesterday today - 1 day
Today today
Tomorrow today + 1 day
Far future "2099-12-31"

The partitions relative to "today" require test setup that pins the current date, or test design that accommodates a moving boundary.

Calendar edge cases as partitions

Partition Representative Why It Matters
February 28 non-leap year "2023-02-28" Last day of month
February 29 leap year "2024-02-29" Only valid in leap years
February 30 "2023-02-30" Never valid
Last day of month "2023-01-31" 31-day month boundary
Last day of year "2023-12-31" Year rollover
First day of year "2024-01-01" Year rollover

Many date parsing bugs concentrate at month/year boundaries. February 29 is the classic example: code that fails to account for leap years produces wrong results once every four years.

Timezone partitions

For applications with timezone-aware dates:

Partition Representative Why It Matters
UTC "2023-01-15T12:00:00Z" Baseline
Positive offset "2023-01-15T12:00:00+05:30" IST
Negative offset "2023-01-15T12:00:00-08:00" PST
DST spring-forward "2023-03-12T02:30:00-05:00" Doesn't exist in US Eastern
DST fall-back "2023-11-05T01:30:00-05:00" Exists twice in US Eastern
Midnight UTC boundary "2023-01-15T00:00:00Z" Date depends on timezone

Daylight saving time transitions are notorious for revealing timezone bugs. A time that "doesn't exist" (spring forward) or "exists twice" (fall back) triggers edge cases in most time handling libraries.

Equivalence Partitioning for Enumerations

Enums have a different partition structure: there are no continuous ranges, only discrete valid and invalid values.

State machine partitions

For an order status enum with values: PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED:

The partitions aren't just "valid value" and "invalid value" — they're state-dependent. An order in SHIPPED state can transition to DELIVERED or CANCELLED, but not back to PENDING.

Current State Valid Transitions Invalid Transitions
PENDING CONFIRMED, CANCELLED SHIPPED, DELIVERED
CONFIRMED SHIPPED, CANCELLED PENDING, DELIVERED
SHIPPED DELIVERED, CANCELLED PENDING, CONFIRMED
DELIVERED (none) All others
CANCELLED (none) All others

EP for state machines: each valid transition is one partition (test one example). Each invalid transition from each state is another partition (test one example of each illegal transition).

For an order, that's at minimum 7 valid transition tests and ~15 invalid transition tests (varies by state machine shape).

Unknown/garbage enum values

Beyond valid enum values, test:

  • Null/undefined
  • Empty string
  • Unrecognized string values ("REFUNDED", "VOID")
  • Numeric values if the enum maps to integers
  • Out-of-range numeric values

APIs that serialize enums as strings often fail when receiving unrecognized values. The partition "unrecognized enum value" should be explicitly tested.

Equivalence Partitioning for Collections (Lists, Arrays)

Collections have size as a primary partition dimension and element validation as a secondary dimension.

Size partitions

For a cart with 0–50 items:

Partition Count Representative
Empty 0 []
Single item 1 [item]
Multiple items (typical) 2–49 [item, item, item]
Maximum 50 [item × 50]
Over maximum 51+ [item × 51]

Empty and single-element collections frequently reveal bugs because:

  • Empty: code assumes at least one element (null pointer, division by zero when computing average)
  • Single: code assumes multiple elements (min/max comparison logic fails with one element)

Element validity partitions

For a list that should contain only positive integers:

Partition Representative
All valid [1, 2, 3]
Contains zero [1, 0, 3]
Contains negative [1, -1, 3]
Contains non-integer [1, 1.5, 3]
Contains null [1, null, 3]
Contains duplicate [1, 1, 2]
All same value [5, 5, 5]

Mixed validity (some valid, some invalid) reveals how the system handles partial validation: does it reject the whole collection, process only valid items, or return an error per invalid item?

Ordering partitions

If the collection order matters (sorted list):

Partition Representative
Already sorted ascending [1, 2, 3, 4, 5]
Already sorted descending [5, 4, 3, 2, 1]
Random order [3, 1, 4, 1, 5]
Already sorted (no change) [1, 2, 3] — test idempotency
Single element [5]

Sorting bugs often manifest at the partitions where no actual sorting is needed (already sorted, single element). These edge cases are underrepresented in tests.

Identifying Non-Obvious Partitions

For complex types, extract partitions systematically:

Step 1: Read the specification What are the explicit rules? Each rule creates at least one partition (satisfies rule) and one (violates rule).

Step 2: Model the behavior What does the system do with this input? Map the input to system behavior. Different behaviors = different partitions.

Step 3: Look for implicit boundaries What values have "interesting" behavior not documented in the spec? String length at database column limits (255, 65535), numeric values at integer overflow points, dates at calendar boundaries.

Step 4: Apply security intuition What values has the OWASP Top 10 shown to cause vulnerabilities? SQL injection strings, XSS payloads, format string attacks for string inputs.

Step 5: Consider the consumer What does the next system do with this input? A string that's valid for your validation but causes a problem in your database, your email sender, or your report generator represents a boundary you need to partition.

Summary

Equivalence partitioning works for any input type that has distinct behavioral regions. For strings: length, character type, format, and security partitions. For dates: value relative to boundaries, calendar edge cases, and timezone. For enums: state machine transitions and invalid values. For collections: size (especially empty and maximum) and element validity.

The discipline: derive partitions from behavior, not from type. Two inputs belong in the same partition if and only if the system behaves identically for both. Different behavior = different partition = needs its own test.

Read more

Start now free