tasks-test-generation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Skill Variant: Use this skill for autonomous test generation with structured templates. For interactive test writing with user feedback, use
test-generation
instead.
技能变体: 此技能适用于通过结构化模板自动生成测试的场景。如果需要用户反馈的交互式测试编写,请使用
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).
StepActionKey Notes
1Pre-flight checklistIdentify code to test, find existing patterns, determine test type
2Identify dependenciesMap mocks needed for repositories, services, context
3Write testsFollow platform patterns (Command, Query, Entity, Component)
4Verify coverageEnsure 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
    test-generation
    instead
  • 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.cs
tests/
└── {Service}.Tests/
    ├── UnitTests/
    │   ├── Commands/
    │   │   └── Save{Entity}CommandTests.cs
    │   ├── Queries/
    │   │   └── Get{Entity}ListQueryTests.cs
    │   └── Entities/
    │       └── {Entity}Tests.cs
    └── IntegrationTests/
        └── {Feature}IntegrationTests.cs

Frontend Tests

前端测试

src/Frontend/apps/{app}/src/app/
└── features/
    └── {feature}/
        ├── {feature}.component.spec.ts
        └── {feature}.store.spec.ts
src/Frontend/apps/{app}/src/app/
└── features/
    └── {feature}/
        ├── {feature}.component.spec.ts
        └── {feature}.store.spec.ts

Pattern 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_SetsErrorState

Anti-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
  • 始终将任务拆分为多个小的待办项
  • 最后必须添加审核待办项,回顾已完成的工作,排查需要修复或优化的问题