Write a failing test. Make it pass with the smallest change you can. Clean up. Repeat. The loop is small enough to feel silly — and that's the point. The discipline forces you to specify what you want before you build it, and leaves a regression suite as a side effect.
← Back to Testing & QualityPick the smallest behavior you don't have yet. Write a test that asserts it. Run it. It must fail — and fail for the right reason (the function doesn't exist, the result is wrong) rather than a typo or import error. A red test you didn't expect to fail is a bug in your test, not a feature.
Write the dumbest code that turns the test green. Hardcode the answer if you have to. The point isn't elegance yet — it's proving the test wires up to real code and the loop is closed. Speed matters here; minutes, not hours.
Now that the bar is green, improve the code without changing behavior. Rename, extract, deduplicate. Run the tests after every small step. If a refactor turns the bar red, undo it — don't try to fix it forward.
Refactor the test code too. Tests are first-class code; they rot like everything else.
Building the short-code encoder for the URL shortener track, one red-green-refactor cycle at a time.
test('encode(0) returns "0"', () => {
expect(encode(0)).toBe('0');
});
Red — encode doesn't exist. Green — const encode = () => '0';. That's it. Resist the urge to write the real algorithm.
test('encode(1) returns "1"', () => expect(encode(1)).toBe('1'));
test('encode(10) returns "a"', () => expect(encode(10)).toBe('a'));
test('encode(61) returns "Z"', () => expect(encode(61)).toBe('Z'));
Now the hardcoded version fails. Implement a real lookup against the alphabet.
test('encode(62) returns "10"', () => expect(encode(62)).toBe('10'));
test('encode(123456) round-trips', () => {
expect(decode(encode(123456))).toBe(123456);
});
Red drives you to the divmod loop. Once green, refactor: extract the alphabet to a constant, share it between encode and decode, add a property test for round-trips on random integers.
Notice what you didn't write: error handling for negatives, performance optimizations, edge cases for huge integers. They'll arrive when a test demands them — not before.
| Flavor | What's different | When it shines |
|---|---|---|
| Classic / Chicago | Test the result. Use real collaborators where possible. | Algorithms, pure logic, libraries. |
| Mockist / London | Test interactions with collaborators via mocks. | Object-heavy code with clear roles and responsibilities. |
| BDD (Behavior-Driven) | Tests phrased as given/when/then scenarios. | Cross-functional teams; product-readable specs. |
| ATDD (Acceptance-TDD) | Start at the user-visible behavior, drill in. | Feature work with clear acceptance criteria. |
| Property-based | Assert invariants over generated inputs, not specific cases. | Encoders, parsers, anything with round-trip or algebraic properties. |
After a few features, you'll notice you're designing differently — smaller pieces, clearer seams, fewer big-bang debug sessions. That's the real win, not the green bar.