Patterns and techniques for evaluating and improving AI agent outputs.
日本語の概要は準備中です。原文の説明を表示しています。
Specialized skill for containerized database testing using Testcontainers. Use when testing real database behavior, using SQL Server/PostgreSQL/MySQL containers, testing EF Core/Dapper. Covers container startup, database migrations, test isolation, container sharing. Keywords: testcontainers, container testing, database testing, MsSqlContainer, PostgreSqlContainer, MySqlContainer, EF Core testing, Dapper testing, Testcontainers.MsSql, Testcontainers.PostgreSql, GetConnectionString, IAsyncLifetime, CollectionFixture
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.
Use this skill when asked to perform the following tasks:
Before choosing a testing strategy, must understand the significant limitations of EF Core InMemory database:
SaveChanges() immediately saves data, cannot RollbackInMemory mode cannot test:
Conclusion: When needing to validate complex transaction logic, concurrency handling, or database-specific behavior, should use Testcontainers for integration testing.
Testcontainers is a testing library providing lightweight, easy-to-use API to start Docker containers, specifically for integration testing.
<ItemGroup>
<!-- Test frameworks -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.9.3" />
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
<!-- Testcontainers core packages -->
<PackageReference Include="Testcontainers" Version="3.10.0" />
<!-- Database containers -->
<PackageReference Include="Testcontainers.PostgreSql" Version="3.10.0" />
<PackageReference Include="Testcontainers.MsSql" Version="3.10.0" />
<!-- Entity Framework Core -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0" />
<!-- Dapper (optional) -->
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
</ItemGroup>
```text
> **Important**: Use `Microsoft.Data.SqlClient` instead of the old `System.Data.SqlClient` for better performance and security.
## Environment Requirements
### Docker Desktop Configuration
- Windows 10 version 2004 or later
- WSL 2 feature enabled
- 8GB RAM (16GB+ recommended)
- 64GB available disk space
### Recommended Docker Desktop Resources Settings
- Memory: 6GB (50-75% of system memory)
- CPUs: 4 cores
- Swap: 2GB
- Disk image size: 64GB
## Basic Container Operation Patterns
### PostgreSQL Container
```csharp
public class PostgreSqlTests : IAsyncLifetime
{
private readonly PostgreSqlContainer _postgres;
private UserDbContext _dbContext = null!;
public PostgreSqlTests()
{
_postgres = new PostgreSqlBuilder()
.WithImage("postgres:15-alpine")
.WithDatabase("testdb")
.WithUsername("testuser")
.WithPassword("testpass")
.WithPortBinding(5432, true) // Auto-assign host port
.Build();
}
public async Task InitializeAsync()
{
await _postgres.StartAsync();
var options = new DbContextOptionsBuilder<UserDbContext>()
.UseNpgsql(_postgres.GetConnectionString())
.Options;
_dbContext = new UserDbContext(options);
await _dbContext.Database.EnsureCreatedAsync();
}
public async Task DisposeAsync()
{
await _dbContext.DisposeAsync();
await _postgres.DisposeAsync();
}
}
```text
### SQL Server Container
```csharp
public class SqlServerTests : IAsyncLifetime
{
private readonly MsSqlContainer _container;
private UserDbContext _dbContext = null!;
public SqlServerTests()
{
_container = new MsSqlBuilder()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithPassword("YourStrong@Passw0rd")
.WithCleanUp(true)
.Build();
}
public async Task InitializeAsync()
{
await _container.StartAsync();
var options = new DbContextOptionsBuilder<UserDbContext>()
.UseSqlServer(_container.GetConnectionString())
.Options;
_dbContext = new UserDbContext(options);
await _dbContext.Database.EnsureCreatedAsync();
}
public async Task DisposeAsync()
{
await _dbContext.DisposeAsync();
await _container.DisposeAsync();
}
}
```text
## Collection Fixture Pattern: Container Sharing
### Why Container Sharing?
In large projects, creating new containers for each test class encounters serious performance bottlenecks:
- **Traditional approach**: Each test class starts a container. If there are 3 test classes, total time is approximately `3 × 10 seconds = 30 seconds`
- **Collection Fixture**: All test classes share the same container. Total time is only approximately `1 × 10 seconds = 10 seconds`
### Test execution time reduced by approximately 67%
### Collection Fixture Implementation
```csharp
/// <summary>
/// MSSQL container Collection Fixture
/// </summary>
public class SqlServerContainerFixture : IAsyncLifetime
{
private readonly MsSqlContainer _container;
public SqlServerContainerFixture()
{
_container = new MsSqlBuilder()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.WithPassword("Test123456!")
.WithCleanUp(true)
.Build();
}
public static string ConnectionString { get; private set; } = string.Empty;
public async Task InitializeAsync()
{
await _container.StartAsync();
ConnectionString = _container.GetConnectionString();
// Wait for container to fully start
await Task.Delay(2000);
}
public async Task DisposeAsync()
{
await _container.DisposeAsync();
}
}
/// <summary>
/// Define test collection
/// </summary>
[CollectionDefinition(nameof(SqlServerCollectionFixture))]
public class SqlServerCollectionFixture : ICollectionFixture<SqlServerContainerFixture>
{
// This class is just for defining Collection, no implementation needed
}
```text
### Test Class Integration
```csharp
[Collection(nameof(SqlServerCollectionFixture))]
public class EfCoreTests : IDisposable
{
private readonly ECommerceDbContext _dbContext;
public EfCoreTests(ITestOutputHelper testOutputHelper)
{
var connectionString = SqlServerContainerFixture.ConnectionString;
var options = new DbContextOptionsBuilder<ECommerceDbContext>()
.UseSqlServer(connectionString)
.EnableSensitiveDataLogging()
.LogTo(testOutputHelper.WriteLine, LogLevel.Information)
.Options;
_dbContext = new ECommerceDbContext(options);
_dbContext.Database.EnsureCreated();
}
public void Dispose()
{
// Clean up data in foreign key constraint order
_dbContext.Database.ExecuteSqlRaw("DELETE FROM OrderItems");
_dbContext.Database.ExecuteSqlRaw("DELETE FROM Orders");
_dbContext.Database.ExecuteSqlRaw("DELETE FROM Products");
_dbContext.Database.ExecuteSqlRaw("DELETE FROM Categories");
_dbContext.Dispose();
}
}
```text
## SQL Script Externalization Strategy
### Why Externalize SQL Scripts?
- **Separation of Concerns**: C# code focuses on test logic, SQL scripts focus on database structure
- **Maintainability**: When modifying database structure, only edit `.sql` files
- **Readability**: C# code becomes cleaner
- **Tool Support**: SQL files get editor syntax highlighting and formatting support
- **Version Control Friendly**: SQL changes can be clearly tracked in version control
### Folder Structure
```text
tests/DatabaseTesting.Tests/
├── SqlScripts/
│ ├── Tables/
│ │ ├── CreateCategoriesTable.sql
│ │ ├── CreateProductsTable.sql
│ │ ├── CreateOrdersTable.sql
│ │ └── CreateOrderItemsTable.sql
│ └── StoredProcedures/
│ └── GetProductSalesReport.sql
```text
### .csproj Configuration
```xml
<ItemGroup>
<Content Include="SqlScripts\**\*.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
```text
### Script Loading Implementation
```csharp
private void EnsureTablesExist()
{
var scriptDirectory = Path.Combine(AppContext.BaseDirectory, "SqlScripts");
if (!Directory.Exists(scriptDirectory)) return;
// Execute table creation scripts in dependency order
var orderedScripts = new[]
{
"Tables/CreateCategoriesTable.sql",
"Tables/CreateProductsTable.sql",
"Tables/CreateOrdersTable.sql",
"Tables/CreateOrderItemsTable.sql"
};
foreach (var scriptPath in orderedScripts)
{
var fullPath = Path.Combine(scriptDirectory, scriptPath);
if (File.Exists(fullPath))
{
var script = File.ReadAllText(fullPath);
_dbContext.Database.ExecuteSqlRaw(script);
}
}
}
```text
## Wait Strategy Best Practices
### Built-in Wait Strategy
```csharp
// Wait for specific port to be available
var postgres = new PostgreSqlBuilder()
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilPortIsAvailable(5432))
.Build();
// Wait for log message to appear
var sqlServer = new MsSqlBuilder()
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilPortIsAvailable(1433)
.UntilMessageIsLogged("SQL Server is now ready for client connections"))
.Build();
```text
## EF Core Advanced Feature Testing
Covers Include/ThenInclude multi-level relationship queries, AsSplitQuery to avoid Cartesian product, N+1 query problem validation, AsNoTracking read-only query optimization, and other complete testing examples.
> 📖 Complete code examples please refer to [references/orm-advanced-testing.md](references/orm-advanced-testing.md#ef-core-advanced-feature-testing)
## Dapper Advanced Feature Testing
Covers basic CRUD test class setup, QueryMultiple one-to-many relationship handling, DynamicParameters dynamic query construction, and other complete testing examples.
> 📖 Complete code examples please refer to [references/orm-advanced-testing.md](references/orm-advanced-testing.md#dapper-advanced-feature-testing)
## Repository Pattern Design Principles
### Interface Segregation Principle (ISP) Application
```csharp
/// <summary>
/// Basic CRUD operation interface
/// </summary>
public interface IProductRepository
{
Task<IEnumerable<Product>> GetAllAsync();
Task<Product?> GetByIdAsync(int id);
Task AddAsync(Product product);
Task UpdateAsync(Product product);
Task DeleteAsync(int id);
}
/// <summary>
/// EF Core specific advanced features interface
/// </summary>
public interface IProductByEFCoreRepository
{
Task<Product?> GetProductWithCategoryAndTagsAsync(int productId);
Task<IEnumerable<Product>> GetProductsByCategoryWithSplitQueryAsync(int categoryId);
Task<int> BatchUpdateProductPricesAsync(int categoryId, decimal priceMultiplier);
Task<IEnumerable<Product>> GetProductsWithNoTrackingAsync(decimal minPrice);
}
/// <summary>
/// Dapper specific advanced features interface
/// </summary>
public interface IProductByDapperRepository
{
Task<Product?> GetProductWithTagsAsync(int productId);
Task<IEnumerable<Product>> SearchProductsAsync(int? categoryId, decimal? minPrice, bool? isActive);
Task<IEnumerable<ProductSalesReport>> GetProductSalesReportAsync(decimal minPrice);
}
```text
### Design Advantages
1. **Single Responsibility Principle (SRP)**: Each interface focuses on specific responsibilities
2. **Interface Segregation Principle (ISP)**: Consumers only depend on interfaces they need
3. **Dependency Inversion Principle (DIP)**: High-level modules depend on abstractions not concrete implementations
4. **Test Isolation**: Can perform precise testing for specific features
## Common Issues Handling
### Docker Container Startup Failure
```bash
# Check if port is occupied
netstat -an | findstr :5432
# Clean up unused images
docker system prune -a
```text
### Out of Memory Issues
- Adjust Docker Desktop memory configuration
- Limit number of simultaneously running containers
- Use Collection Fixture to share containers
### Test Data Isolation
```csharp
public void Dispose()
{
// Clean up data in foreign key constraint order
_dbContext.Database.ExecuteSqlRaw("DELETE FROM OrderItems");
_dbContext.Database.ExecuteSqlRaw("DELETE FROM Orders");
_dbContext.Database.ExecuteSqlRaw("DELETE FROM Products");
_dbContext.Database.ExecuteSqlRaw("DELETE FROM Categories");
_dbContext.Dispose();
}
```text
## Reference Resources
### Original Articles
This skill content is distilled from the "Old School Software Engineer's Testing Practice - 30 Day Challenge" article series:
- **Day 20 - Testcontainers Introduction: Using Docker to Set Up Testing Environment**
- Article: https://ithelp.ithome.com.tw/articles/10376401
- Sample code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day20
- **Day 21 - Testcontainers Integration Testing: MSSQL + EF Core and Dapper Basic Applications**
- Article: https://ithelp.ithome.com.tw/articles/10376524
- Sample code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day21
### Official Documentation
- [Testcontainers Official Website](https://testcontainers.com/)
- [Testcontainers for .NET](https://dotnet.testcontainers.org/)
- [Testcontainers for .NET / Modules](https://dotnet.testcontainers.org/modules/)
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Patterns and techniques for evaluating and improving AI agent outputs.
日本語の概要は準備中です。原文の説明を表示しています。
Comprehensive AI prompt engineering safety review and improvement prompt. Analyzes prompts for safety, bias, security vulnerabilities, and effectiveness while providing detailed improvement recommendations.
日本語の概要は準備中です。原文の説明を表示しています。
Use when user requests research requiring multiple sources, comprehensive analysis, or synthesis across topics - technical research, domain knowledge gathering, market analysis, or learning about complex subjects
日本語の概要は準備中です。原文の説明を表示しています。
AI-powered wiki generation for code repositories with commands, agents, and skills
日本語の概要は準備中です。原文の説明を表示しています。
Use when building .NET 10 or C# 14 applications; when using minimal APIs, modular monolith patterns, or feature folders; when implementing HTTP resilience, Options pattern, Channels, or validation; when seeing outdated patterns like old extension method syntax
日本語の概要は準備中です。原文の説明を表示しています。
Implements accessible .NET UI. SemanticProperties, ARIA, AutomationPeer, testing per platform.
日本語の概要は準備中です。原文の説明を表示しています。