用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/SAM42-Lab/everything-claude-code-kr --skill csharp-testing命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
캡처, 진단, 억제된 복구 및 인트로스펙션 보고서를 사용하여 AI 에이전트 실패에 대한 구조화된 셀프 디버깅 워크플로우를 수행합니다.
기술, 명령어, 규칙, 훅 및 기타 요소를 DAILY 대 LIBRARY 버킷으로 분류하여 특정 저장소에 대한 증거 기반의 ECC 설치 계획을 수립합니다. 전체 번들을 로드하는 대신 프로젝트에 실제로 필요한 것만 ECC를 트리밍해야 할 때 사용하세요.
리소스 명명, 상태 코드, 페이지네이션, 필터링, 에러 응답, 버전 관리 및 프로덕션 API를 위한 속도 제한을 포함한 REST API 설계 패턴.
基于 SOC 职业分类
| name | csharp-testing |
| description | xUnit, FluentAssertions, 모킹, 통합 테스트, 테스트 구조 모범 사례를 포함한 C# 및 .NET 테스트 패턴. |
| origin | ECC |
xUnit, FluentAssertions 및 현대적인 테스트 관행을 사용하는 .NET 애플리케이션을 위한 포괄적인 테스트 패턴입니다.
| 도구 | 용도 |
|---|---|
| xUnit | 테스트 프레임워크 (.NET 선호) |
| FluentAssertions | 가독성 높은 단언(assertion) 구문 |
| NSubstitute 또는 Moq | 의존성 모킹 |
| Testcontainers | 통합 테스트에서 실제 인프라 사용 |
| WebApplicationFactory | ASP.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]
[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" },
};
[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 — 저장소가 호출되었는지 확인
await _repository.Received(1).AddAsync(
Arg.Is<Order>(o => o.CustomerId == request.CustomerId),
Arg.Any<CancellationToken>());
}
public sealed class OrderApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrderApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// 테스트를 위해 실제 DB를 인메모리로 교체
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();
}
}
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);
}
// 테스트에서 사용 예시
var order = new OrderBuilder()
.WithCustomer("cust-vip")
.WithItem("SKU-PREMIUM", 3, 99.99m)
.Build();
| 안티패턴 | 해결책 |
|---|---|
| 구현 세부 사항 테스트 | 행동과 결과 테스트 |
| 공유된 가변 테스트 상태 | 테스트마다 새로운 인스턴스 사용 (xUnit은 생성자를 통해 이를 수행) |
비동기 테스트의 Thread.Sleep | 타임아웃이 있는 Task.Delay 또는 폴링 헬퍼 사용 |
ToString() 출력에 대한 단언 | 타입이 지정된 속성에 대한 단언 |
| 테스트당 거대한 단언 하나 | 테스트당 하나의 논리적 단언 |
| 구현을 설명하는 테스트 이름 | 행동에 따른 이름 지정: Method_ExpectedResult_WhenCondition |
CancellationToken 무시 | 항상 전달하고 취소 여부 확인 |
# 모든 테스트 실행
dotnet test
# 커버리지와 함께 실행
dotnet test --collect:"XPlat Code Coverage"
# 특정 프로젝트 실행
dotnet test tests/MyApp.UnitTests/
# 테스트 이름으로 필터링
dotnet test --filter "FullyQualifiedName~OrderService"
# 개발 중 와치 모드
dotnet watch test --project tests/MyApp.UnitTests/