İLERİ
Testing & Benchmarking
Gerçek API endpoint'ini Redis ile birlikte test et — en güvenilir integration test pattern'ı.
Kod örneği görünümü
Bu sayfadaki eşleşen örnekleri seçilen istemciye göre gösterir.
Integration Test (Testcontainers)
dotnet add package Testcontainers.Redis
dotnet add package xunit
# redis-benchmark — yerleşik performans aracı
redis-benchmark -h localhost -p 6379 -c 50 -n 100000 -q
# SET: 125000.00 requests per second
# GET: 142857.14 requests per second
# Belirli komut benchmark
redis-benchmark -t set,get -n 100000 -d 256 -q
# -d 256: 256 byte value
# Pipeline ile (daha gerçekçi)
redis-benchmark -t set,get -n 100000 -P 16 -q
# SET: 680000+ requests per second (pipeline=16)
# Lua script benchmark
redis-benchmark -n 100000 EVAL "return redis.call('SET',KEYS[1],ARGV[1])" 1 bench:key bench:val
# Latency baseline
redis-cli --latency -i 1 # her 1 saniye örnek
redis-cli --intrinsic-latency 5 # 5sn kernel latency ölç
// Testcontainers ile integration test
using Testcontainers.Redis;
using StackExchange.Redis;
using Xunit;
public class RedisIntegrationTests : IAsyncLifetime
{
private readonly RedisContainer _redis = new RedisBuilder()
.WithImage("redis:8-alpine")
.Build();
private IConnectionMultiplexer _mux = null!;
private IDatabase _db = null!;
public async Task InitializeAsync()
{
await _redis.StartAsync();
_mux = await ConnectionMultiplexer.ConnectAsync(
_redis.GetConnectionString());
_db = _mux.GetDatabase();
}
public async Task DisposeAsync()
{
_mux.Dispose();
await _redis.DisposeAsync();
}
[Fact]
public async Task CacheService_SetAndGet_ReturnsValue()
{
// Arrange
var service = new CachedProductRepository(_mux);
// Act
await _db.StringSetAsync("product:1",
JsonSerializer.Serialize(new { Id = 1, Name = "Test" }),
TimeSpan.FromMinutes(1));
var result = await _db.StringGetAsync("product:1");
// Assert
Assert.True(result.HasValue);
Assert.Contains("Test", result.ToString());
}
[Fact]
public async Task DistributedLock_AcquireAndRelease()
{
// Arrange
var lockService = new DistributedLockService(_mux);
// Act
await using var handle = await lockService.AcquireAsync(
"test-resource", TimeSpan.FromSeconds(5));
// Assert
Assert.NotNull(handle);
// Lock aktifken ikinci acquire başarısız olmalı
var second = await lockService.AcquireAsync(
"test-resource", TimeSpan.FromSeconds(5),
waitTimeout: TimeSpan.FromMilliseconds(100));
Assert.Null(second);
}
[Fact]
public async Task RateLimiter_ExceedsLimit_Returns429Equivalent()
{
var limiter = new RateLimiterService(_mux);
// 5 request, limit=3
for (int i = 0; i < 3; i++)
{
var r = await limiter.CheckAsync("client1", 3, TimeSpan.FromMinutes(1));
Assert.True(r.IsAllowed);
}
var blocked = await limiter.CheckAsync("client1", 3, TimeSpan.FromMinutes(1));
Assert.False(blocked.IsAllowed);
}
}
// BenchmarkDotNet ile client-side performans ölçümü
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
[SimpleJob(iterationCount: 20)]
public class RedisBenchmarks
{
private IDatabase _db = null!;
private IConnectionMultiplexer _mux = null!;
[GlobalSetup]
public void Setup()
{
_mux = ConnectionMultiplexer.Connect("localhost:6379");
_db = _mux.GetDatabase();
_db.StringSet("bench:key", new string('x', 256));
}
[Benchmark(Baseline = true)]
public async Task<RedisValue> StringGet()
=> await _db.StringGetAsync("bench:key");
[Benchmark]
public async Task StringSet()
=> await _db.StringSetAsync("bench:key", "value");
[Benchmark]
public async Task PipelinedGet10()
{
var tasks = Enumerable.Range(0, 10)
.Select(_ => _db.StringGetAsync("bench:key")).ToArray();
await Task.WhenAll(tasks);
}
[Benchmark]
public void FireAndForget()
=> _db.StringIncrement("bench:counter", flags: CommandFlags.FireAndForget);
[GlobalCleanup]
public void Cleanup() => _mux.Dispose();
}
// Çalıştır: dotnet run -c Release -- --filter *RedisBenchmarks*
Test kuralları: Her test kendi key namespace'ini kullan (
test:{testId}:*). Test sonundaFLUSHDBveya key cleanup. Production Redis'e asla test bağlantısı açma.
WebApplicationFactory + Testcontainers (End-to-End)
// NuGet: Microsoft.AspNetCore.Mvc.Testing + Testcontainers.Redis + xunit
public class ApiIntegrationTests : IAsyncLifetime
{
private readonly RedisContainer _redis = new RedisBuilder()
.WithImage("redis:8-alpine")
.Build();
private WebApplicationFactory<Program> _factory = null!;
private HttpClient _client = null!;
public async Task InitializeAsync()
{
await _redis.StartAsync();
_factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Production Redis bağlantısını test container ile değiştir
services.Remove(services.Single(
d => d.ServiceType == typeof(IConnectionMultiplexer)));
services.AddSingleton<IConnectionMultiplexer>(
ConnectionMultiplexer.Connect(_redis.GetConnectionString()));
});
});
_client = _factory.CreateClient();
}
public async Task DisposeAsync()
{
_client.Dispose();
await _factory.DisposeAsync();
await _redis.DisposeAsync();
}
[Fact]
public async Task GetProduct_Cached_ReturnsFromRedis()
{
// İlk istek — DB'den okur, cache'e yazar
var response1 = await _client.GetAsync("/api/products/1");
response1.EnsureSuccessStatusCode();
// İkinci istek — cache'den dönmeli (daha hızlı)
var sw = Stopwatch.StartNew();
var response2 = await _client.GetAsync("/api/products/1");
sw.Stop();
response2.EnsureSuccessStatusCode();
var content = await response2.Content.ReadAsStringAsync();
Assert.Contains(""id":1", content);
// Cache'den geldiğini doğrula: Redis'te key var mı?
var mux = _factory.Services.GetRequiredService<IConnectionMultiplexer>();
var db = mux.GetDatabase();
var cached = await db.StringGetAsync("product:1");
Assert.True(cached.HasValue);
}
[Fact]
public async Task RateLimit_ExceedsLimit_Returns429()
{
// Limit: 5 requests/minute
for (int i = 0; i < 5; i++)
{
var r = await _client.GetAsync("/api/limited-endpoint");
Assert.Equal(HttpStatusCode.OK, r.StatusCode);
}
var blocked = await _client.GetAsync("/api/limited-endpoint");
Assert.Equal(HttpStatusCode.TooManyRequests, blocked.StatusCode);
}
}
CI/CD'de: Testcontainers Docker gerektirir. GitHub Actions'da
services: dockeraktif olmalı. Container startup ~2-3s, test suite'in toplam süresine minimal etki eder.