tdd-workflow
4
总安装量
2
周安装量
#51750
全站排名
安装命令
npx skills add https://github.com/doubleslashse/claude-marketplace --skill tdd-workflow
Agent 安装分布
opencode
2
claude-code
2
windsurf
1
antigravity
1
gemini-cli
1
Skill 文档
Test-Driven Development Workflow
The TDD Cycle: RED-GREEN-REFACTOR
âââââââââââââââââââââââââââââââââââââââ
â â
â âââââââ âââââââââ âââââââ â
â â RED âââââ¶â GREEN âââââ¶âREFACââââ
â âââââââ âââââââââ âââââââ
â â â
â â Write failing test â
â â â
â â¼ â
â Make it pass (minimal) â
â â
ââââââââââââââââââââââââââââââââ
Improve design
Phase 1: RED – Write a Failing Test
Rules for RED Phase
- Write ONE test that fails
- Test must fail for the RIGHT reason
- Test must be meaningful and specific
- Run the test to confirm it fails
Test Naming Convention
{MethodUnderTest}_{Scenario}_{ExpectedBehavior}
Examples:
CreateOrder_WithValidItems_ReturnsOrderGetUser_WhenNotFound_ThrowsNotFoundExceptionCalculateTotal_WithDiscount_AppliesCorrectPercentage
AAA Pattern (Arrange-Act-Assert)
[Fact]
public void MethodName_Scenario_ExpectedResult()
{
// Arrange - Set up preconditions
var sut = new SystemUnderTest();
var input = CreateValidInput();
// Act - Execute the behavior
var result = sut.Execute(input);
// Assert - Verify outcome
Assert.Equal(expected, result);
}
Test Categories
// Unit Test - Tests single unit in isolation
[Fact]
public void Calculator_Add_ReturnsSumOfNumbers() { }
// Integration Test - Tests component interaction
[Fact]
public void OrderService_CreateOrder_PersistsToDatabase() { }
// Acceptance Test - Tests user scenarios
[Fact]
public void User_CanCompleteCheckout_WithValidCart() { }
Phase 2: GREEN – Make It Pass
Rules for GREEN Phase
- Write MINIMAL code to pass the test
- Do NOT add extra features
- Do NOT optimize yet
- It’s okay to be “ugly” – we’ll fix it in REFACTOR
- Run tests to confirm they pass
The Simplest Thing That Works
// BAD - Over-engineering in GREEN phase
public decimal CalculateDiscount(Order order)
{
var strategy = _discountStrategyFactory.Create(order.CustomerType);
return strategy.Calculate(order, _configService.GetDiscountRules());
}
// GOOD - Minimal implementation for GREEN
public decimal CalculateDiscount(Order order)
{
return order.Total * 0.1m; // 10% discount
}
Fake It Till You Make It
// Test expects specific value
[Fact]
public void GetGreeting_ReturnsHello()
{
var result = greeter.GetGreeting();
Assert.Equal("Hello", result);
}
// GREEN: Just return what the test expects
public string GetGreeting() => "Hello";
Phase 3: REFACTOR – Improve Design
Rules for REFACTOR Phase
- Tests MUST stay green
- Improve structure, not behavior
- Apply SOLID principles
- Remove duplication (DRY)
- Simplify (KISS)
- Remove unused code (YAGNI)
Refactoring Checklist
- Extract methods for clarity
- Rename for intent
- Remove duplication
- Apply design patterns if needed
- Check for SOLID violations
- Run tests after each change
Common Refactorings
// Before: Long method
public void ProcessOrder(Order order)
{
// 50 lines of mixed concerns
}
// After: Single responsibility
public void ProcessOrder(Order order)
{
ValidateOrder(order);
CalculateTotals(order);
ApplyDiscounts(order);
PersistOrder(order);
NotifyCustomer(order);
}
Test Doubles
Types of Test Doubles
// Dummy - Passed but never used
var dummyLogger = new Mock<ILogger>().Object;
// Stub - Provides canned answers
var stubRepo = new Mock<IUserRepository>();
stubRepo.Setup(r => r.GetById(1)).Returns(new User { Id = 1 });
// Spy - Records interactions
var spyNotifier = new SpyNotifier();
service.Execute();
Assert.True(spyNotifier.WasCalled);
// Mock - Verifies interactions
var mockNotifier = new Mock<INotifier>();
service.Execute();
mockNotifier.Verify(n => n.Send(It.IsAny<Message>()), Times.Once);
// Fake - Working implementation (in-memory)
var fakeRepo = new InMemoryUserRepository();
When to Use What
| Double | Use When |
|---|---|
| Dummy | Parameter required but unused |
| Stub | Need controlled return values |
| Spy | Need to verify calls were made |
| Mock | Need to verify specific interactions |
| Fake | Need realistic behavior without dependencies |
Test Organization
Project Structure
src/
âââ MyApp.Domain/
â âââ Entities/
âââ MyApp.Application/
â âââ Services/
âââ MyApp.Infrastructure/
âââ Repositories/
tests/
âââ MyApp.Domain.Tests/
â âââ Entities/
âââ MyApp.Application.Tests/
â âââ Services/
âââ MyApp.Integration.Tests/
âââ Repositories/
Test Class Structure
public class OrderServiceTests
{
private readonly Mock<IOrderRepository> _mockRepository;
private readonly Mock<INotificationService> _mockNotifier;
private readonly OrderService _sut;
public OrderServiceTests()
{
_mockRepository = new Mock<IOrderRepository>();
_mockNotifier = new Mock<INotificationService>();
_sut = new OrderService(_mockRepository.Object, _mockNotifier.Object);
}
[Fact]
public void CreateOrder_WithValidData_PersistsOrder() { }
[Fact]
public void CreateOrder_WithInvalidData_ThrowsValidationException() { }
}
Anti-Patterns to Avoid
Test Smells
// BAD: Testing implementation details
Assert.Equal(3, order.Items.Count);
// GOOD: Testing behavior
Assert.True(order.HasItems);
// BAD: Multiple assertions testing different behaviors
[Fact]
public void Order_Tests()
{
Assert.NotNull(order.Id);
Assert.Equal("Pending", order.Status);
Assert.True(order.Total > 0);
}
// GOOD: One logical assertion per test
[Fact]
public void NewOrder_HasPendingStatus()
{
Assert.Equal(OrderStatus.Pending, order.Status);
}
// BAD: Tests depending on order
[Fact]
public void Test1_CreateUser() { } // Creates user
[Fact]
public void Test2_GetUser() { } // Assumes user exists
// GOOD: Independent tests
[Fact]
public void GetUser_WhenExists_ReturnsUser()
{
var user = CreateUser(); // Arrange includes setup
var result = _sut.GetUser(user.Id);
Assert.NotNull(result);
}
Quick Reference
TDD Commands
# Run all tests
dotnet test
# Run with coverage
dotnet test /p:CollectCoverage=true
# Run specific test
dotnet test --filter "FullyQualifiedName~OrderServiceTests"
# Watch mode
dotnet watch test
Test Attributes
[Fact] // Single test case
[Theory] // Parameterized test
[InlineData(1, 2)] // Test data
[Trait("Category", "Unit")] // Categorization
[Skip("Reason")] // Skip test
See patterns.md for advanced testing patterns.