Source profileQuality 69/100

affaan-m/ECC/docs/zh-CN/skills/csharp-testing/SKILL.md

csharp-testing

Use it for testing tasks; the detail page covers purpose, installation, and practical steps.

Source repository stars
234,327
Declared platforms
0
Static risk flags
0
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

使用 xUnit、FluentAssertions 和现代测试实践为 .NET 应用程序提供的全面测试模式。

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "docs/zh-CN/skills/csharp-testing"
    Safe inspection promptEditorial

    Inspect the Agent Skill "csharp-testing" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/docs/zh-CN/skills/csharp-testing/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      何时使用

      为 C 代码编写新测试 审查测试质量和覆盖率 为 .NET 项目搭建测试基础设施 调试不稳定或缓慢的测试

      为 C 代码编写新测试审查测试质量和覆盖率为 .NET 项目搭建测试基础设施
    2. 02

      测试框架栈

      Review the “测试框架栈” section in the pinned source before continuing.

      Review and apply the “测试框架栈” source section.
    3. 03

      单元测试结构

      Review the “单元测试结构” section in the pinned source before continuing.

      Review and apply the “单元测试结构” source section.
    4. 04

      安排-操作-断言

      Review the “安排-操作-断言” section in the pinned source before continuing.

      Review and apply the “安排-操作-断言” source section.
    5. 05

      使用 Theory 的参数化测试

      Review the “使用 Theory 的参数化测试” section in the pinned source before continuing.

      Review and apply the “使用 Theory 的参数化测试” source section.

    Permission review

    Static risk signals and limitations

    No configured static risk pattern was detected

    This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score69/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    docs/zh-CN/skills/csharp-testing/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    C# 测试模式

    使用 xUnit、FluentAssertions 和现代测试实践为 .NET 应用程序提供的全面测试模式。

    何时使用

    • 为 C# 代码编写新测试
    • 审查测试质量和覆盖率
    • 为 .NET 项目搭建测试基础设施
    • 调试不稳定或缓慢的测试

    测试框架栈

    工具用途
    xUnit测试框架(.NET 首选)
    FluentAssertions可读的断言语法
    NSubstituteMoq模拟依赖项
    Testcontainers集成测试中的真实基础设施
    WebApplicationFactoryASP.NET Core 集成测试
    Bogus生成逼真的测试数据

    单元测试结构

    安排-操作-断言

    public sealed class OrderServiceTests
    {
        private readonly IOrderRepository _repository = Substitute.For<IOrderRepository>();
        private readonly ILogger<OrderService> _logger = Substitute.For<ILogger<OrderService>>();
        private readonly OrderService _sut;
    
        public OrderServiceTests()
        {
            _sut = new OrderService(_repository, _logger);
        }
    
        [Fact]
        public async Task PlaceOrderAsync_ReturnsSuccess_WhenRequestIsValid()
        {
            // Arrange
            var request = new CreateOrderRequest
            {
                CustomerId = "cust-123",
                Items = [new OrderItem("SKU-001", 2, 29.99m)]
            };
    
            // Act
            var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
    
            // Assert
            result.IsSuccess.Should().BeTrue();
            result.Value.Should().NotBeNull();
            result.Value!.CustomerId.Should().Be("cust-123");
        }
    
        [Fact]
        public async Task PlaceOrderAsync_ReturnsFailure_WhenNoItems()
        {
            // Arrange
            var request = new CreateOrderRequest
            {
                CustomerId = "cust-123",
                Items = []
            };
    
            // Act
            var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
    
            // Assert
            result.IsSuccess.Should().BeFalse();
            result.Error.Should().Contain("at least one item");
        }
    }
    

    使用 Theory 的参数化测试

    [Theory]
    [InlineData("", false)]
    [InlineData("a", false)]
    [InlineData("ab@c.d", false)]
    [InlineData("user@example.com", true)]
    [InlineData("user+tag@example.co.uk", true)]
    public void IsValidEmail_ReturnsExpected(string email, bool expected)
    {
        EmailValidator.IsValid(email).Should().Be(expected);
    }
    
    [Theory]
    [MemberData(nameof(InvalidOrderCases))]
    public async Task PlaceOrderAsync_RejectsInvalidOrders(CreateOrderRequest request, string expectedError)
    {
        var result = await _sut.PlaceOrderAsync(request, CancellationToken.None);
    
        result.IsSuccess.Should().BeFalse();
        result.Error.Should().Contain(expectedError);
    }
    
    public static TheoryData<CreateOrderRequest, string> InvalidOrderCases => new()
    {
        { new() { CustomerId = "", Items = [ValidItem()] }, "CustomerId" },
        { new() { CustomerId = "c1", Items = [] }, "at least one item" },
        { new() { CustomerId = "c1", Items = [new("", 1, 10m)] }, "SKU" },
    };
    

    使用 NSubstitute 进行模拟

    [Fact]
    public async Task GetOrderAsync_ReturnsNull_WhenNotFound()
    {
        // Arrange
        var orderId = Guid.NewGuid();
        _repository.FindByIdAsync(orderId, Arg.Any<CancellationToken>())
            .Returns((Order?)null);
    
        // Act
        var result = await _sut.GetOrderAsync(orderId, CancellationToken.None);
    
        // Assert
        result.Should().BeNull();
    }
    
    [Fact]
    public async Task PlaceOrderAsync_PersistsOrder()
    {
        // Arrange
        var request = ValidOrderRequest();
    
        // Act
        await _sut.PlaceOrderAsync(request, CancellationToken.None);
    
        // Assert — verify the repository was called
        await _repository.Received(1).AddAsync(
            Arg.Is<Order>(o => o.CustomerId == request.CustomerId),
            Arg.Any<CancellationToken>());
    }
    

    ASP.NET Core 集成测试

    WebApplicationFactory 设置

    public sealed class OrderApiTests : IClassFixture<WebApplicationFactory<Program>>
    {
        private readonly HttpClient _client;
    
        public OrderApiTests(WebApplicationFactory<Program> factory)
        {
            _client = factory.WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(services =>
                {
                    // Replace real DB with in-memory for tests
                    services.RemoveAll<DbContextOptions<AppDbContext>>();
                    services.AddDbContext<AppDbContext>(options =>
                        options.UseInMemoryDatabase("TestDb"));
                });
            }).CreateClient();
        }
    
        [Fact]
        public async Task GetOrder_Returns404_WhenNotFound()
        {
            var response = await _client.GetAsync($"/api/orders/{Guid.NewGuid()}");
    
            response.StatusCode.Should().Be(HttpStatusCode.NotFound);
        }
    
        [Fact]
        public async Task CreateOrder_Returns201_WithValidRequest()
        {
            var request = new CreateOrderRequest
            {
                CustomerId = "cust-1",
                Items = [new("SKU-001", 1, 19.99m)]
            };
    
            var response = await _client.PostAsJsonAsync("/api/orders", request);
    
            response.StatusCode.Should().Be(HttpStatusCode.Created);
            response.Headers.Location.Should().NotBeNull();
        }
    }
    

    使用 Testcontainers 进行测试

    public sealed class PostgresOrderRepositoryTests : IAsyncLifetime
    {
        private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
            .WithImage("postgres:16-alpine")
            .Build();
    
        private AppDbContext _db = null!;
    
        public async Task InitializeAsync()
        {
            await _postgres.StartAsync();
            var options = new DbContextOptionsBuilder<AppDbContext>()
                .UseNpgsql(_postgres.GetConnectionString())
                .Options;
            _db = new AppDbContext(options);
            await _db.Database.MigrateAsync();
        }
    
        public async Task DisposeAsync()
        {
            await _db.DisposeAsync();
            await _postgres.DisposeAsync();
        }
    
        [Fact]
        public async Task AddAsync_PersistsOrder()
        {
            var repo = new SqlOrderRepository(_db);
            var order = Order.Create("cust-1", [new OrderItem("SKU-001", 2, 10m)]);
    
            await repo.AddAsync(order, CancellationToken.None);
    
            var found = await repo.FindByIdAsync(order.Id, CancellationToken.None);
            found.Should().NotBeNull();
            found!.Items.Should().HaveCount(1);
        }
    }
    

    测试组织

    tests/
      MyApp.UnitTests/
        Services/
          OrderServiceTests.cs
          PaymentServiceTests.cs
        Validators/
          EmailValidatorTests.cs
      MyApp.IntegrationTests/
        Api/
          OrderApiTests.cs
        Repositories/
          OrderRepositoryTests.cs
      MyApp.TestHelpers/
        Builders/
          OrderBuilder.cs
        Fixtures/
          DatabaseFixture.cs
    

    测试数据构建器

    public sealed class OrderBuilder
    {
        private string _customerId = "cust-default";
        private readonly List<OrderItem> _items = [new("SKU-001", 1, 10m)];
    
        public OrderBuilder WithCustomer(string customerId)
        {
            _customerId = customerId;
            return this;
        }
    
        public OrderBuilder WithItem(string sku, int quantity, decimal price)
        {
            _items.Add(new OrderItem(sku, quantity, price));
            return this;
        }
    
        public Order Build() => Order.Create(_customerId, _items);
    }
    
    // Usage in tests
    var order = new OrderBuilder()
        .WithCustomer("cust-vip")
        .WithItem("SKU-PREMIUM", 3, 99.99m)
        .Build();
    

    常见反模式

    反模式修复方法
    测试实现细节测试行为和结果
    共享的可变测试状态每个测试使用新实例(xUnit 通过构造函数实现)
    在异步测试中使用 Thread.Sleep使用带超时的 Task.Delay 或轮询辅助方法
    ToString() 输出进行断言对类型化属性进行断言
    每个测试一个巨型断言每个测试一个逻辑断言
    测试名称描述实现按行为命名:Method_ExpectedResult_WhenCondition
    忽略 CancellationToken始终传递并验证取消

    运行测试

    # Run all tests
    dotnet test
    
    # Run with coverage
    dotnet test --collect:"XPlat Code Coverage"
    
    # Run specific project
    dotnet test tests/MyApp.UnitTests/
    
    # Filter by test name
    dotnet test --filter "FullyQualifiedName~OrderService"
    
    # Watch mode during development
    dotnet watch test --project tests/MyApp.UnitTests/
    

    Alternatives

    Compare before choosing