tasks-test-generation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSkill Variant: Use this skill for autonomous test generation with structured templates. For interactive test writing with user feedback, useinstead.test-generation
技能变体: 此技能适用于通过结构化模板自动生成测试的场景。如果需要用户反馈的交互式测试编写,请使用。test-generation
Test Generation Workflow
测试生成工作流
Summary
摘要
Goal: Generate comprehensive unit and integration tests following EasyPlatform patterns for both backend (C#/xUnit) and frontend (TypeScript/Jest).
| Step | Action | Key Notes |
|---|---|---|
| 1 | Pre-flight checklist | Identify code to test, find existing patterns, determine test type |
| 2 | Identify dependencies | Map mocks needed for repositories, services, context |
| 3 | Write tests | Follow platform patterns (Command, Query, Entity, Component) |
| 4 | Verify coverage | Ensure happy path, validation, edge cases covered |
Key Principles:
- Follow existing test patterns — grep for similar tests before writing new ones
- Autonomous variant: use structured templates; for interactive, use instead
test-generation - Backend tests use Mock repositories; frontend tests use TestBed with platform utilities
目标: 遵循EasyPlatform规范,为后端(C#/xUnit)和前端(TypeScript/Jest)生成完善的单元测试和集成测试。
| 步骤 | 操作 | 关键说明 |
|---|---|---|
| 1 | 前置检查 | 确定要测试的代码,查找现有规范,明确测试类型 |
| 2 | 识别依赖 | 梳理存储库、服务、上下文所需的mock |
| 3 | 编写测试 | 遵循平台规范(命令、查询、实体、组件) |
| 4 | 验证覆盖率 | 确保覆盖正常路径、校验逻辑、边界场景 |
关键原则:
- 遵循现有测试规范 —— 编写新测试前先搜索相似的测试参考
- 自动生成变体:使用结构化模板;如需交互式生成,请使用
test-generation - 后端测试使用Mock存储库;前端测试使用搭载平台工具的TestBed
When to Use This Skill
适用场景
- Creating unit tests for new code
- Adding tests for bug fixes
- Integration test development
- Test coverage improvement
- 为新代码编写单元测试
- 为Bug修复补充测试
- 集成测试开发
- 提升测试覆盖率
Pre-Flight Checklist
前置检查清单
- Identify code to test (command, query, entity, component)
- Find existing test patterns:
grep "Test.*{Feature}" --include="*.cs" - Determine test type (unit, integration, e2e)
- Identify dependencies to mock
- 确定要测试的代码(命令、查询、实体、组件)
- 查找现有测试规范:
grep "Test.*{Feature}" --include="*.cs" - 明确测试类型(单元、集成、端到端)
- 识别需要Mock的依赖
File Locations
文件位置
Backend Tests
后端测试
tests/
└── {Service}.Tests/
├── UnitTests/
│ ├── Commands/
│ │ └── Save{Entity}CommandTests.cs
│ ├── Queries/
│ │ └── Get{Entity}ListQueryTests.cs
│ └── Entities/
│ └── {Entity}Tests.cs
└── IntegrationTests/
└── {Feature}IntegrationTests.cstests/
└── {Service}.Tests/
├── UnitTests/
│ ├── Commands/
│ │ └── Save{Entity}CommandTests.cs
│ ├── Queries/
│ │ └── Get{Entity}ListQueryTests.cs
│ └── Entities/
│ └── {Entity}Tests.cs
└── IntegrationTests/
└── {Feature}IntegrationTests.csFrontend Tests
前端测试
src/Frontend/apps/{app}/src/app/
└── features/
└── {feature}/
├── {feature}.component.spec.ts
└── {feature}.store.spec.tssrc/Frontend/apps/{app}/src/app/
└── features/
└── {feature}/
├── {feature}.component.spec.ts
└── {feature}.store.spec.tsPattern 1: Command Handler Unit Test
模式1:命令处理程序单元测试
csharp
public class SaveEmployeeCommandTests
{
private readonly Mock<IPlatformQueryableRootRepository<Employee>> _employeeRepoMock;
private readonly Mock<IPlatformApplicationRequestContextAccessor> _contextMock;
private readonly SaveEmployeeCommandHandler _handler;
public SaveEmployeeCommandTests()
{
_employeeRepoMock = new Mock<IPlatformQueryableRootRepository<Employee>>();
_contextMock = new Mock<IPlatformApplicationRequestContextAccessor>();
// Setup default context
var requestContext = new Mock<IPlatformApplicationRequestContext>();
requestContext.Setup(x => x.UserId()).Returns("test-user-id");
requestContext.Setup(x => x.CurrentCompanyId()).Returns("test-company-id");
_contextMock.Setup(x => x.Current).Returns(requestContext.Object);
_handler = new SaveEmployeeCommandHandler(
Mock.Of<ILoggerFactory>(),
Mock.Of<IPlatformUnitOfWorkManager>(),
Mock.Of<IServiceProvider>(),
Mock.Of<IPlatformRootServiceProvider>(),
_employeeRepoMock.Object
);
}
[Fact]
public async Task HandleAsync_CreateEmployee_ReturnsNewEmployee()
{
// Arrange
var command = new SaveEmployeeCommand
{
Name = "John Doe",
Email = "john@example.com"
};
_employeeRepoMock
.Setup(x => x.CreateAsync(It.IsAny<Employee>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Employee e, CancellationToken _) => e);
// Act
var result = await _handler.HandleAsync(command, CancellationToken.None);
// Assert
Assert.NotNull(result.Employee);
Assert.Equal("John Doe", result.Employee.Name);
_employeeRepoMock.Verify(x => x.CreateAsync(
It.Is<Employee>(e => e.Name == "John Doe"),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task HandleAsync_UpdateEmployee_UpdatesExisting()
{
// Arrange
var existingEmployee = new Employee { Id = "emp-1", Name = "Old Name" };
var command = new SaveEmployeeCommand
{
Id = "emp-1",
Name = "New Name"
};
_employeeRepoMock
.Setup(x => x.GetByIdAsync("emp-1", It.IsAny<CancellationToken>()))
.ReturnsAsync(existingEmployee);
_employeeRepoMock
.Setup(x => x.UpdateAsync(It.IsAny<Employee>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Employee e, CancellationToken _) => e);
// Act
var result = await _handler.HandleAsync(command, CancellationToken.None);
// Assert
Assert.Equal("New Name", result.Employee.Name);
_employeeRepoMock.Verify(x => x.UpdateAsync(
It.Is<Employee>(e => e.Name == "New Name"),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task HandleAsync_InvalidCommand_ReturnsValidationError()
{
// Arrange
var command = new SaveEmployeeCommand
{
Name = "" // Invalid: empty name
};
// Act & Assert
var result = command.Validate();
Assert.False(result.IsValid);
Assert.Contains(result.Errors, e => e.Contains("Name"));
}
}csharp
public class SaveEmployeeCommandTests
{
private readonly Mock<IPlatformQueryableRootRepository<Employee>> _employeeRepoMock;
private readonly Mock<IPlatformApplicationRequestContextAccessor> _contextMock;
private readonly SaveEmployeeCommandHandler _handler;
public SaveEmployeeCommandTests()
{
_employeeRepoMock = new Mock<IPlatformQueryableRootRepository<Employee>>();
_contextMock = new Mock<IPlatformApplicationRequestContextAccessor>();
// Setup default context
var requestContext = new Mock<IPlatformApplicationRequestContext>();
requestContext.Setup(x => x.UserId()).Returns("test-user-id");
requestContext.Setup(x => x.CurrentCompanyId()).Returns("test-company-id");
_contextMock.Setup(x => x.Current).Returns(requestContext.Object);
_handler = new SaveEmployeeCommandHandler(
Mock.Of<ILoggerFactory>(),
Mock.Of<IPlatformUnitOfWorkManager>(),
Mock.Of<IServiceProvider>(),
Mock.Of<IPlatformRootServiceProvider>(),
_employeeRepoMock.Object
);
}
[Fact]
public async Task HandleAsync_CreateEmployee_ReturnsNewEmployee()
{
// Arrange
var command = new SaveEmployeeCommand
{
Name = "John Doe",
Email = "john@example.com"
};
_employeeRepoMock
.Setup(x => x.CreateAsync(It.IsAny<Employee>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Employee e, CancellationToken _) => e);
// Act
var result = await _handler.HandleAsync(command, CancellationToken.None);
// Assert
Assert.NotNull(result.Employee);
Assert.Equal("John Doe", result.Employee.Name);
_employeeRepoMock.Verify(x => x.CreateAsync(
It.Is<Employee>(e => e.Name == "John Doe"),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task HandleAsync_UpdateEmployee_UpdatesExisting()
{
// Arrange
var existingEmployee = new Employee { Id = "emp-1", Name = "Old Name" };
var command = new SaveEmployeeCommand
{
Id = "emp-1",
Name = "New Name"
};
_employeeRepoMock
.Setup(x => x.GetByIdAsync("emp-1", It.IsAny<CancellationToken>()))
.ReturnsAsync(existingEmployee);
_employeeRepoMock
.Setup(x => x.UpdateAsync(It.IsAny<Employee>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((Employee e, CancellationToken _) => e);
// Act
var result = await _handler.HandleAsync(command, CancellationToken.None);
// Assert
Assert.Equal("New Name", result.Employee.Name);
_employeeRepoMock.Verify(x => x.UpdateAsync(
It.Is<Employee>(e => e.Name == "New Name"),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task HandleAsync_InvalidCommand_ReturnsValidationError()
{
// Arrange
var command = new SaveEmployeeCommand
{
Name = "" // Invalid: empty name
};
// Act & Assert
var result = command.Validate();
Assert.False(result.IsValid);
Assert.Contains(result.Errors, e => e.Contains("Name"));
}
}Pattern 2: Query Handler Unit Test
模式2:查询处理程序单元测试
csharp
public class GetEmployeeListQueryTests
{
private readonly Mock<IPlatformQueryableRootRepository<Employee>> _repoMock;
private readonly GetEmployeeListQueryHandler _handler;
[Fact]
public async Task HandleAsync_WithFilters_ReturnsFilteredResults()
{
// Arrange
var employees = new List<Employee>
{
new() { Id = "1", Name = "Active", Status = EmployeeStatus.Active },
new() { Id = "2", Name = "Inactive", Status = EmployeeStatus.Inactive }
};
_repoMock.Setup(x => x.CountAsync(It.IsAny<Expression<Func<Employee, bool>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(1);
_repoMock.Setup(x => x.GetAllAsync(It.IsAny<Func<IPlatformUnitOfWork, IQueryable<Employee>, IQueryable<Employee>>>(), It.IsAny<CancellationToken>(), It.IsAny<Expression<Func<Employee, object>>[]>()))
.ReturnsAsync(employees.Where(e => e.Status == EmployeeStatus.Active).ToList());
var query = new GetEmployeeListQuery
{
Statuses = [EmployeeStatus.Active],
SkipCount = 0,
MaxResultCount = 10
};
// Act
var result = await _handler.HandleAsync(query, CancellationToken.None);
// Assert
Assert.Single(result.Items);
Assert.Equal("Active", result.Items[0].Name);
}
}csharp
public class GetEmployeeListQueryTests
{
private readonly Mock<IPlatformQueryableRootRepository<Employee>> _repoMock;
private readonly GetEmployeeListQueryHandler _handler;
[Fact]
public async Task HandleAsync_WithFilters_ReturnsFilteredResults()
{
// Arrange
var employees = new List<Employee>
{
new() { Id = "1", Name = "Active", Status = EmployeeStatus.Active },
new() { Id = "2", Name = "Inactive", Status = EmployeeStatus.Inactive }
};
_repoMock.Setup(x => x.CountAsync(It.IsAny<Expression<Func<Employee, bool>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(1);
_repoMock.Setup(x => x.GetAllAsync(It.IsAny<Func<IPlatformUnitOfWork, IQueryable<Employee>, IQueryable<Employee>>>(), It.IsAny<CancellationToken>(), It.IsAny<Expression<Func<Employee, object>>[]>()))
.ReturnsAsync(employees.Where(e => e.Status == EmployeeStatus.Active).ToList());
var query = new GetEmployeeListQuery
{
Statuses = [EmployeeStatus.Active],
SkipCount = 0,
MaxResultCount = 10
};
// Act
var result = await _handler.HandleAsync(query, CancellationToken.None);
// Assert
Assert.Single(result.Items);
Assert.Equal("Active", result.Items[0].Name);
}
}Pattern 3: Entity Validation Test
模式3:实体校验测试
csharp
public class EmployeeEntityTests
{
[Fact]
public void UniqueExpr_ReturnsCorrectExpression()
{
// Arrange
var employees = new List<Employee>
{
new() { CompanyId = "c1", UserId = "u1" },
new() { CompanyId = "c1", UserId = "u2" },
new() { CompanyId = "c2", UserId = "u1" }
}.AsQueryable();
// Act
var expr = Employee.UniqueExpr("c1", "u1");
var result = employees.Where(expr).ToList();
// Assert
Assert.Single(result);
Assert.Equal("u1", result[0].UserId);
}
[Fact]
public async Task ValidateAsync_DuplicateCode_ReturnsError()
{
// Arrange
var repoMock = new Mock<IPlatformQueryableRootRepository<Employee>>();
repoMock.Setup(x => x.AnyAsync(It.IsAny<Expression<Func<Employee, bool>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true); // Duplicate exists
var employee = new Employee { Id = "new", Code = "EMP001", CompanyId = "c1" };
// Act
var result = await employee.ValidateAsync(repoMock.Object, CancellationToken.None);
// Assert
Assert.False(result.IsValid);
Assert.Contains(result.Errors, e => e.Contains("already exists"));
}
[Fact]
public void ComputedProperty_IsActive_CalculatesCorrectly()
{
// Arrange
var activeEmployee = new Employee { Status = EmployeeStatus.Active, IsDeleted = false };
var inactiveEmployee = new Employee { Status = EmployeeStatus.Inactive, IsDeleted = false };
var deletedEmployee = new Employee { Status = EmployeeStatus.Active, IsDeleted = true };
// Assert
Assert.True(activeEmployee.IsActive);
Assert.False(inactiveEmployee.IsActive);
Assert.False(deletedEmployee.IsActive);
}
}csharp
public class EmployeeEntityTests
{
[Fact]
public void UniqueExpr_ReturnsCorrectExpression()
{
// Arrange
var employees = new List<Employee>
{
new() { CompanyId = "c1", UserId = "u1" },
new() { CompanyId = "c1", UserId = "u2" },
new() { CompanyId = "c2", UserId = "u1" }
}.AsQueryable();
// Act
var expr = Employee.UniqueExpr("c1", "u1");
var result = employees.Where(expr).ToList();
// Assert
Assert.Single(result);
Assert.Equal("u1", result[0].UserId);
}
[Fact]
public async Task ValidateAsync_DuplicateCode_ReturnsError()
{
// Arrange
var repoMock = new Mock<IPlatformQueryableRootRepository<Employee>>();
repoMock.Setup(x => x.AnyAsync(It.IsAny<Expression<Func<Employee, bool>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true); // Duplicate exists
var employee = new Employee { Id = "new", Code = "EMP001", CompanyId = "c1" };
// Act
var result = await employee.ValidateAsync(repoMock.Object, CancellationToken.None);
// Assert
Assert.False(result.IsValid);
Assert.Contains(result.Errors, e => e.Contains("already exists"));
}
[Fact]
public void ComputedProperty_IsActive_CalculatesCorrectly()
{
// Arrange
var activeEmployee = new Employee { Status = EmployeeStatus.Active, IsDeleted = false };
var inactiveEmployee = new Employee { Status = EmployeeStatus.Inactive, IsDeleted = false };
var deletedEmployee = new Employee { Status = EmployeeStatus.Active, IsDeleted = true };
// Assert
Assert.True(activeEmployee.IsActive);
Assert.False(inactiveEmployee.IsActive);
Assert.False(deletedEmployee.IsActive);
}
}Pattern 4: Angular Component Test
模式4:Angular组件测试
typescript
describe('FeatureListComponent', () => {
let component: FeatureListComponent;
let fixture: ComponentFixture<FeatureListComponent>;
let store: FeatureListStore;
let apiMock: jasmine.SpyObj<FeatureApiService>;
beforeEach(async () => {
apiMock = jasmine.createSpyObj('FeatureApiService', ['getList', 'delete']);
await TestBed.configureTestingModule({
imports: [FeatureListComponent],
providers: [FeatureListStore, { provide: FeatureApiService, useValue: apiMock }]
}).compileComponents();
fixture = TestBed.createComponent(FeatureListComponent);
component = fixture.componentInstance;
store = TestBed.inject(FeatureListStore);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should load items on init', () => {
// Arrange
const items = [{ id: '1', name: 'Test' }];
apiMock.getList.and.returnValue(of({ items, totalCount: 1 }));
// Act
fixture.detectChanges();
// Assert
expect(apiMock.getList).toHaveBeenCalled();
expect(component.vm()?.items).toEqual(items);
});
it('should delete item', fakeAsync(() => {
// Arrange
store.updateState({ items: [{ id: '1', name: 'Test' }] });
apiMock.delete.and.returnValue(of(void 0));
// Act
component.onDelete({ id: '1', name: 'Test' });
tick();
// Assert
expect(apiMock.delete).toHaveBeenCalledWith('1');
expect(component.vm()?.items.length).toBe(0);
}));
it('should show loading state', () => {
// Arrange
apiMock.getList.and.returnValue(new Subject()); // Never completes
// Act
fixture.detectChanges();
// Assert
expect(store.isLoading$('loadItems')()).toBe(true);
});
});typescript
describe('FeatureListComponent', () => {
let component: FeatureListComponent;
let fixture: ComponentFixture<FeatureListComponent>;
let store: FeatureListStore;
let apiMock: jasmine.SpyObj<FeatureApiService>;
beforeEach(async () => {
apiMock = jasmine.createSpyObj('FeatureApiService', ['getList', 'delete']);
await TestBed.configureTestingModule({
imports: [FeatureListComponent],
providers: [FeatureListStore, { provide: FeatureApiService, useValue: apiMock }]
}).compileComponents();
fixture = TestBed.createComponent(FeatureListComponent);
component = fixture.componentInstance;
store = TestBed.inject(FeatureListStore);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should load items on init', () => {
// Arrange
const items = [{ id: '1', name: 'Test' }];
apiMock.getList.and.returnValue(of({ items, totalCount: 1 }));
// Act
fixture.detectChanges();
// Assert
expect(apiMock.getList).toHaveBeenCalled();
expect(component.vm()?.items).toEqual(items);
});
it('should delete item', fakeAsync(() => {
// Arrange
store.updateState({ items: [{ id: '1', name: 'Test' }] });
apiMock.delete.and.returnValue(of(void 0));
// Act
component.onDelete({ id: '1', name: 'Test' });
tick();
// Assert
expect(apiMock.delete).toHaveBeenCalledWith('1');
expect(component.vm()?.items.length).toBe(0);
}));
it('should show loading state', () => {
// Arrange
apiMock.getList.and.returnValue(new Subject()); // Never completes
// Act
fixture.detectChanges();
// Assert
expect(store.isLoading$('loadItems')()).toBe(true);
});
});Pattern 5: Angular Store Test
模式5:Angular Store测试
typescript
describe('FeatureListStore', () => {
let store: FeatureListStore;
let apiMock: jasmine.SpyObj<FeatureApiService>;
beforeEach(() => {
apiMock = jasmine.createSpyObj('FeatureApiService', ['getList', 'save', 'delete']);
TestBed.configureTestingModule({
providers: [FeatureListStore, { provide: FeatureApiService, useValue: apiMock }]
});
store = TestBed.inject(FeatureListStore);
});
it('should initialize with default state', () => {
expect(store.currentVm().items).toEqual([]);
expect(store.currentVm().pagination.pageIndex).toBe(0);
});
it('should load items', fakeAsync(() => {
// Arrange
const items = [{ id: '1', name: 'Test' }];
apiMock.getList.and.returnValue(of({ items, totalCount: 1 }));
// Act
store.loadItems();
tick();
// Assert
expect(store.currentVm().items).toEqual(items);
expect(store.currentVm().pagination.totalCount).toBe(1);
}));
it('should update state immutably', () => {
// Arrange
const initialItems = store.currentVm().items;
// Act
store.updateState({ items: [{ id: '1', name: 'New' }] });
// Assert
expect(store.currentVm().items).not.toBe(initialItems);
});
it('should handle API error', fakeAsync(() => {
// Arrange
apiMock.getList.and.returnValue(throwError(() => new Error('API Error')));
// Act
store.loadItems();
tick();
// Assert
expect(store.getErrorMsg$('loadItems')()).toContain('Error');
}));
});typescript
describe('FeatureListStore', () => {
let store: FeatureListStore;
let apiMock: jasmine.SpyObj<FeatureApiService>;
beforeEach(() => {
apiMock = jasmine.createSpyObj('FeatureApiService', ['getList', 'save', 'delete']);
TestBed.configureTestingModule({
providers: [FeatureListStore, { provide: FeatureApiService, useValue: apiMock }]
});
store = TestBed.inject(FeatureListStore);
});
it('should initialize with default state', () => {
expect(store.currentVm().items).toEqual([]);
expect(store.currentVm().pagination.pageIndex).toBe(0);
});
it('should load items', fakeAsync(() => {
// Arrange
const items = [{ id: '1', name: 'Test' }];
apiMock.getList.and.returnValue(of({ items, totalCount: 1 }));
// Act
store.loadItems();
tick();
// Assert
expect(store.currentVm().items).toEqual(items);
expect(store.currentVm().pagination.totalCount).toBe(1);
}));
it('should update state immutably', () => {
// Arrange
const initialItems = store.currentVm().items;
// Act
store.updateState({ items: [{ id: '1', name: 'New' }] });
// Assert
expect(store.currentVm().items).not.toBe(initialItems);
});
it('should handle API error', fakeAsync(() => {
// Arrange
apiMock.getList.and.returnValue(throwError(() => new Error('API Error')));
// Act
store.loadItems();
tick();
// Assert
expect(store.getErrorMsg$('loadItems')()).toContain('Error');
}));
});Test Naming Convention
测试命名规范
[MethodName]_[Scenario]_[ExpectedBehavior]
Examples:
- HandleAsync_ValidCommand_ReturnsSuccess
- HandleAsync_InvalidId_ThrowsNotFound
- UniqueExpr_MatchingValues_ReturnsTrue
- LoadItems_ApiError_SetsErrorState[方法名]_[场景]_[预期行为]
示例:
- HandleAsync_ValidCommand_ReturnsSuccess
- HandleAsync_InvalidId_ThrowsNotFound
- UniqueExpr_MatchingValues_ReturnsTrue
- LoadItems_ApiError_SetsErrorStateAnti-Patterns to AVOID
需要避免的反模式
:x: Testing implementation, not behavior
csharp
// WRONG - testing internal method calls
Assert.True(handler.WasValidateCalled);
// CORRECT - testing observable behavior
Assert.Equal("Expected", result.Value);:x: Not mocking dependencies
csharp
// WRONG - using real repository
var handler = new Handler(new RealRepository());
// CORRECT - using mock
var repoMock = new Mock<IRepository>();
var handler = new Handler(repoMock.Object);:x: Missing edge cases
csharp
// WRONG - only happy path
[Fact] public void Save_ValidData_Succeeds() { }
// CORRECT - include edge cases
[Fact] public void Save_EmptyName_ReturnsError() { }
[Fact] public void Save_DuplicateCode_ReturnsError() { }
[Fact] public void Save_NullInput_ThrowsException() { }:x: 测试实现而非行为
csharp
// WRONG - testing internal method calls
Assert.True(handler.WasValidateCalled);
// CORRECT - testing observable behavior
Assert.Equal("Expected", result.Value);:x: 未Mock依赖
csharp
// WRONG - using real repository
var handler = new Handler(new RealRepository());
// CORRECT - using mock
var repoMock = new Mock<IRepository>();
var handler = new Handler(repoMock.Object);:x: 缺失边界场景
csharp
// WRONG - only happy path
[Fact] public void Save_ValidData_Succeeds() { }
// CORRECT - include edge cases
[Fact] public void Save_EmptyName_ReturnsError() { }
[Fact] public void Save_DuplicateCode_ReturnsError() { }
[Fact] public void Save_NullInput_ThrowsException() { }Verification Checklist
验证清单
- Unit tests cover happy path
- Edge cases and error conditions tested
- Dependencies properly mocked
- Test naming follows convention
- Assertions are specific and meaningful
- No test interdependencies
- Tests are deterministic (no random, no time-dependent)
- 单元测试覆盖正常路径
- 覆盖边界场景和错误条件
- 依赖已正确Mock
- 测试命名遵循规范
- 断言具体且有意义
- 无测试间依赖
- 测试是确定性的(无随机值、无时间依赖)
IMPORTANT Task Planning Notes
重要任务规划说明
- Always plan and break many small todo tasks
- Always add a final review todo task to review the works done at the end to find any fix or enhancement needed
- 始终将任务拆分为多个小的待办项
- 最后必须添加审核待办项,回顾已完成的工作,排查需要修复或优化的问题