Q1 of 38 · Test design

What is equivalence partitioning?

Test designJuniortest-designequivalence-partitioningbvafundamentals

Short answer

Short answer: A test design technique that groups inputs into classes which are expected to behave the same, then picks one representative from each class. It cuts hundreds of input cases down to a handful without losing coverage of distinct behaviours.

Detail

Equivalence partitioning rests on a simple observation: not every input deserves its own test. If a function accepts integers from 1 to 100 and rejects everything else, you don't need 100 tests for valid inputs. Pick one — say, 50 — and one each from the invalid classes (≤ 0 and ≥ 101). The function should treat every member of an equivalence class the same way; representative coverage of the class is enough.

Concretely, for an "age" field accepting 18–120:

  • Valid class: 18 ≤ age ≤ 120 → one test (age = 35).
  • Below valid: age < 18 → one test (age = 12).
  • Above valid: age > 120 → one test (age = 150).
  • Wrong type: non-numeric → one test ("abc").
  • Missing: empty → one test (null).

Five tests instead of hundreds, and each tests a distinct behavioural class. The companion technique is boundary value analysis, which adds tests at the edges of each class (17, 18, 120, 121) because off-by-one errors are common at boundaries. Most practitioners use both together.

The catch: the partitions are only valid if your assumption that "the class behaves uniformly" is right. If the function has special handling for, say, age 65 (senior discount), 65 deserves its own class. Equivalence partitioning is design done up front, with the spec — not a substitute for thinking.

// EXAMPLE

age-validator.test.ts

import { describe, test, expect } from 'vitest';
import { validateAge } from './age-validator';

describe('validateAge — equivalence partitioning', () => {
  // Valid class: representative
  test('accepts a typical adult age', () => {
    expect(validateAge(35)).toEqual({ ok: true });
  });

  // Below-valid class: representative
  test('rejects an under-18 age', () => {
    expect(validateAge(12)).toEqual({
      ok: false, error: 'TOO_YOUNG',
    });
  });

  // Above-valid class: representative
  test('rejects an above-120 age', () => {
    expect(validateAge(150)).toEqual({
      ok: false, error: 'TOO_OLD',
    });
  });

  // Wrong type
  test('rejects a non-numeric value', () => {
    expect(validateAge('abc' as unknown as number)).toEqual({
      ok: false, error: 'INVALID_TYPE',
    });
  });

  // Boundary tests (BVA, complementary)
  test.each([
    [17, false], [18, true], [120, true], [121, false],
  ])('boundary: age %i → ok=%s', (age, expected) => {
    expect(validateAge(age).ok).toBe(expected);
  });
});

// WHAT INTERVIEWERS LOOK FOR

Clear definition with a real example, awareness that BVA pairs naturally with EP, and recognition that equivalence partitions depend on the spec being honest about what's uniform.

// COMMON PITFALL

Treating EP as 'pick one valid input and one invalid input' without identifying the actual classes. Or skipping BVA — the boundaries are where most bugs live.