TDD in Practice
How test-driven development is applied in Vivolar: red-green-refactor with real examples from the codebase.
Why TDD
TDD isn’t just about testing — it’s a design tool. Writing the test first forces you to think about the API from the consumer’s perspective before writing any implementation. This leads to cleaner interfaces, smaller methods, and better separation of concerns.
In Vivolar, TDD is a non-negotiable part of the workflow. Every feature, every bug fix, every refactoring session starts with a test.
The Loop
The implementation loop is codified in the project’s CLAUDE.md:
- Read & Study Patterns — before writing any code, read the task spec AND at least one existing example of the same kind
- Implement — red → green → refactor
- Test — run
./mvnw verifyandnpm run build && npm test - Fix — if anything fails, analyze and fix. Repeat until green
- Doc — update documentation if behavior changed
- Commit — stage all files, conventional commit format
Red Phase
The red phase is about defining the expected behavior:
@Test
void shouldReturnItemsFilteredByHousehold() {
// Given items in two different households
// When requesting items for household A
// Then only household A's items are returned
}
The test should fail for the right reason — not because of a compile error, but because the expected behavior isn’t implemented yet. If the test passes immediately, either the feature already exists or the test is wrong.
Green Phase
Write the minimum code to make the test pass. This means:
- No premature abstractions
- No “while I’m here” improvements
- No handling edge cases not covered by the current test
If the minimum implementation feels too simple, that’s a signal to write more tests, not to write more code.
Refactor Phase
With green tests as a safety net, clean up:
- Extract methods that do too much
- Rename variables for clarity
- Remove duplication introduced during the green phase
- Ensure the code follows project conventions
The key rule: tests must stay green throughout refactoring. If a refactoring breaks a test, it wasn’t a refactoring — it was a behavior change.
Practical Tips from the Trenches
Start with the happy path. Get the basic scenario working before handling edge cases. Each edge case gets its own test.
Name tests like sentences. shouldReturnItemsFilteredByHousehold is better than testGetItems. The test name is documentation.
One assertion per test (usually). Multiple assertions in one test make it hard to pinpoint what broke. Exception: when the assertions are verifying different aspects of the same logical result.
Use TestContainers for integration tests. Don’t mock the database. A test that passes against mocks but fails against real PostgreSQL is worse than no test.