3.9 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	
			3.9 KiB
		
	
	
	
	
	
	
	
Test-Driven Development Workflow
Core TDD Cycle
- Red - Write failing test
 - Green - Make test pass
 - Refactor - Improve code while keeping tests passing
 
Writing Effective Tests
Test Structure
# Arrange - Set up test data and conditions
# Act - Execute the code being tested
# Assert - Verify expected behavior
Good Test Characteristics
- Isolated - Tests don't depend on each other
 - Repeatable - Same input always produces same output
 - Fast - Tests run quickly
 - Clear - Test name describes what's being tested
 - Focused - One concept per test
 
Test Naming
test_<function>_<scenario>_<expected_result>
Examples:
test_rename_folder_as_owner_succeedstest_rename_folder_without_permission_returns_403test_rename_folder_with_empty_name_returns_400
Common Patterns
Testing Error Conditions
// Test expected errors
test('rename_folder_without_permission_returns_403', async () => {
  // Arrange: Set up user without permissions
  const user = createUserWithoutPermissions();
  
  // Act: Attempt rename
  const response = await renameFolder(user, folderId, newName);
  
  // Assert: Verify 403 error
  expect(response.status).toBe(403);
  expect(response.error).toContain('forbidden');
});
Testing Happy Path
test('rename_folder_as_owner_succeeds', async () => {
  // Arrange: Set up folder with owner
  const owner = createOwner();
  const folder = createFolder(owner);
  
  // Act: Rename folder
  const response = await renameFolder(owner, folder.id, 'NewName');
  
  // Assert: Verify success
  expect(response.status).toBe(200);
  expect(response.data.name).toBe('NewName');
});
Testing Edge Cases
test('rename_folder_with_special_characters_sanitizes_name', async () => {
  const owner = createOwner();
  const folder = createFolder(owner);
  
  const response = await renameFolder(owner, folder.id, '<script>alert(1)</script>');
  
  expect(response.status).toBe(200);
  expect(response.data.name).not.toContain('<script>');
});
TDD Best Practices
Start Simple
Write simplest test first, then add complexity:
- Happy path with minimal data
 - Add edge cases
 - Add error conditions
 - Add integration scenarios
 
One Test at a Time
- Write one test
 - Watch it fail
 - Make it pass
 - Refactor if needed
 - Repeat
 
Keep Tests Independent
// ❌ Bad - Tests depend on order
test('create_folder', () => { /* creates folder with id=1 */ });
test('rename_folder', () => { /* assumes folder id=1 exists */ });
// ✅ Good - Each test sets up own data
test('create_folder', () => {
  const folder = createFolder();
  expect(folder.id).toBeDefined();
});
test('rename_folder', () => {
  const folder = createFolder(); // Own setup
  const result = renameFolder(folder.id, 'NewName');
  expect(result.name).toBe('NewName');
});
Test Behavior, Not Implementation
// ❌ Bad - Tests internal implementation
test('uses_postgres_query', () => {
  expect(mockDb.query).toHaveBeenCalledWith('UPDATE folders...');
});
// ✅ Good - Tests behavior
test('rename_updates_folder_name', () => {
  renameFolder(folderId, 'NewName');
  const folder = getFolder(folderId);
  expect(folder.name).toBe('NewName');
});
When to Refactor
Refactor when tests are green and you notice:
- Duplication
 - Long functions
 - Unclear variable names
 - Complex conditionals
 - Hard-coded values
 
Always keep tests passing during refactoring
Handling Legacy Code
When adding tests to existing code without tests:
- Write characterization tests (document current behavior)
 - Refactor to make code testable
 - Add tests for new functionality
 - Gradually improve test coverage
 
Test Coverage
Aim for coverage of:
- All new code paths
 - Bug fixes (test the bug scenario)
 - Edge cases
 - Error conditions
 
Not aiming for 100% - focus on meaningful coverage.