Roll Your Own Test Framework

Level: Advanced 60–90 min

Concepts: Design PatternsIncremental DesignBoundaries


Build a minimal unit testing framework and command-line runner. The twist: use TDD to build a tool for TDD.

Inspired by Jason Gorman’s Codemanship kata.

Requirements

Step 1: Test Discovery

Define a convention for marking test methods. Options:

  • Naming convention: methods starting with test_ or test
  • Annotations/decorators: @Test or [Test]
  • Registration: suite.addTest("name", function)

The framework should find all test methods in a given class or module.

Step 2: Test Execution

Run each discovered test and capture the result:

  • PASS — test completed without errors
  • FAIL — an assertion was not met
  • ERROR — an unhandled exception occurred

Each test runs independently — a failure in one test does not prevent others from running.

Step 3: Assertions

Provide at minimum:

  • assertTrue(condition) — fails if condition is false
  • assertEqual(expected, actual) — fails if values don’t match
  • assertThrows(fn) — fails if the function doesn’t throw

Failed assertions should include a message describing what went wrong:

assertEqual failed: expected 5 but got 3

Step 4: Result Reporting

After all tests run, print a summary:

Tests run: 5, Passed: 3, Failed: 1, Errors: 1

FAIL: test_addition - assertEqual failed: expected 5 but got 3
ERROR: test_division - ZeroDivisionError: division by zero

Test Cases

ScenarioExpected
Discover 0 testsReports “0 tests run”
Discover 3 test methodsFinds and runs all 3
Passing testReported as PASS
Failing assertionReported as FAIL with message
Unhandled exceptionReported as ERROR with exception info
Multiple tests, mixed resultsSummary counts correct
assertEqual(5, 5)Passes silently
assertEqual(5, 3)Fails with “expected 5 but got 3”
assertTrue(false)Fails with “expected true”
assertThrows with no throwFails with “expected exception”

Bonus

  • Add setUp() and tearDown() methods that run before/after each test
  • Add parameterized tests — run the same test with multiple input sets
  • Add test filtering — run only tests matching a pattern
  • Add timing — report how long each test took
  • Add color output — green for pass, red for fail

Hint

Start with assertEqual. You need it to test everything else. Then build assertTrue on top of it. Once assertions work, build the runner that catches assertion failures vs unhandled exceptions. Test discovery comes last — it’s language-specific plumbing.