skills/testing/performance-testing/SKILL.md
Use when benchmarking with BenchmarkDotNet or running load/throughput tests.
npx skillsauth add faysilalshareef/dotnet-ai-kit performance-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
namespace {Company}.{Domain}.Benchmarks;
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
public class EventSerializationBenchmarks
{
private Event<OrderCreatedData> _event = null!;
private string _json = null!;
[GlobalSetup]
public void Setup()
{
_event = new Event<OrderCreatedData>
{
AggregateId = Guid.NewGuid(),
Sequence = 1,
Type = EventTypes.OrderCreated,
DateTime = DateTime.UtcNow,
Data = new OrderCreatedData("Test", 100m, [])
};
_json = JsonConvert.SerializeObject(_event);
}
[Benchmark(Baseline = true)]
public string Serialize_Newtonsoft()
=> JsonConvert.SerializeObject(_event);
[Benchmark]
public Event<OrderCreatedData> Deserialize_Newtonsoft()
=> JsonConvert.DeserializeObject<Event<OrderCreatedData>>(_json)!;
}
// Run: dotnet run -c Release --project src/Benchmarks/
namespace {Company}.{Domain}.Tests.Live;
/// <summary>
/// Sends batches of events to Service Bus and measures throughput.
/// Requires real Service Bus connection string in environment.
/// </summary>
public sealed class ServiceBusThroughputTest
{
[Fact(Skip = "Manual execution only — requires live Service Bus")]
public async Task MeasurePublishThroughput()
{
// Arrange
var connectionString = Environment.GetEnvironmentVariable(
"SERVICEBUS_CONNECTION_STRING")!;
var client = new ServiceBusClient(connectionString);
var sender = client.CreateSender("{company}-{domain}-commands");
var events = Enumerable.Range(1, 1000).Select(i =>
new ServiceBusMessage($"{{\"AggregateId\":\"{Guid.NewGuid()}\",\"Sequence\":{i}}}")
{
Subject = EventTypes.OrderCreated,
SessionId = Guid.NewGuid().ToString()
}).ToList();
// Act
var sw = Stopwatch.StartNew();
foreach (var batch in events.Chunk(100))
{
using var messageBatch = await sender.CreateMessageBatchAsync();
foreach (var msg in batch)
messageBatch.TryAddMessage(msg);
await sender.SendMessagesAsync(messageBatch);
}
sw.Stop();
// Assert
var throughput = events.Count / sw.Elapsed.TotalSeconds;
Console.WriteLine($"Throughput: {throughput:F0} messages/sec");
throughput.Should().BeGreaterThan(100, "Minimum throughput not met");
}
}
[MemoryDiagnoser]
public class QueryBenchmarks
{
private ApplicationDbContext _db = null!;
[GlobalSetup]
public async Task Setup()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer("Server=.;Database=BenchmarkDb;Trusted_Connection=true;")
.Options;
_db = new ApplicationDbContext(options);
// Seed 10,000 orders for realistic benchmarks
await SeedData.SeedOrders(_db, 10000);
}
[Benchmark]
public async Task<List<OrderOutput>> Query_WithPagination()
{
return await _db.Orders.AsNoTracking()
.OrderByDescending(o => o.Sequence)
.Skip(0).Take(20)
.Select(o => new OrderOutput(o.Id, o.CustomerName, o.Total))
.ToListAsync();
}
[Benchmark]
public async Task<List<OrderOutput>> Query_WithFilter()
{
return await _db.Orders.AsNoTracking()
.Where(o => o.CustomerName.Contains("Test"))
.OrderByDescending(o => o.Sequence)
.Skip(0).Take(20)
.Select(o => new OrderOutput(o.Id, o.CustomerName, o.Total))
.ToListAsync();
}
}
tests/
{Domain}.Tests.Unit/ # Unit tests (CI)
{Domain}.Tests.Integration/ # Integration tests (CI)
{Domain}.Tests.Live/ # Live infrastructure tests (manual)
{Domain}.Benchmarks/ # BenchmarkDotNet projects (manual)
| Anti-Pattern | Correct Approach |
|---|---|
| Optimizing without benchmarks | Measure first, optimize second |
| Benchmarks in CI pipeline | Run benchmarks manually or on schedule |
| Testing with Debug build | Always benchmark in Release mode |
| Live tests without Skip attribute | Mark with Skip to prevent accidental CI runs |
# Find BenchmarkDotNet usage
grep -r "BenchmarkDotNet\|\[Benchmark\]" --include="*.cs" tests/ benchmarks/
# Find Test.Live projects
find . -name "*Tests.Live*" -type d
# Find throughput tests
grep -r "Throughput\|Stopwatch" --include="*.cs" tests/
Benchmarks/ project with Release configurationTest.Live project for real infrastructure validationSkip attribute or [Trait("Category", "Live")]data-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.