Testes na Matching Engine
Aula 6 de 7
Testes com xUnit
// Instalar: dotnet add package xunit
// dotnet add package FluentAssertions
public class OrderBookTests
{
[Fact]
public void AdicionarOrdem_Limite_DeveAdicionarAoBook()
{
var book = new OrderBook("AAPL");
var ordem = new Ordem("AAPL", OrdemLado.COMPRA, 100,
OrdemTipo.LIMITE, 150.50m);
book.AdicionarOrdem(ordem);
var snapshot = book.ObterSnapshot();
snapshot.Bids.Should().HaveCount(1);
snapshot.Bids.First().Quantidade.Should().Be(100);
}
[Theory]
[InlineData(100, 150.50, 50, 150.50)] // match total
[InlineData(100, 150.50, 150, 150.50)] // match parcial
public void ExecutarMatch_DeveGerarTrade(int qtdCompra, decimal precoCompra,
int qtdVenda, decimal precoVenda)
{
var book = new OrderBook("AAPL");
var compra = new Ordem("AAPL", OrdemLado.COMPRA, qtdCompra,
OrdemTipo.LIMITE, precoCompra);
var venda = new Ordem("AAPL", OrdemLado.VENDA, qtdVenda,
OrdemTipo.LIMITE, precoVenda);
book.AdicionarOrdem(compra);
book.AdicionarOrdem(venda);
var trades = book.ExecutarMatches();
trades.Should().HaveCount(1);
trades[0].Quantidade.Should().Be(Math.Min(qtdCompra, qtdVenda));
}
}
Testes de Concorrência
public class MatchingEngineConcurrencyTests
{
[Fact]
public async Task MultiplosThreads_NaoDeveCorromperEstado()
{
var engine = new MatchingEngine();
var tasks = new List<Task>();
var random = new Random();
for (int i = 0; i < 100; i++)
{
tasks.Add(Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
var lado = random.Next(2) == 0
? OrdemLado.COMPRA : OrdemLado.VENDA;
var ordem = new Ordem(
"AAPL", lado, random.Next(1, 100),
OrdemTipo.LIMITE,
Math.Round((decimal)(random.NextDouble() * 200 + 100), 2));
var resultado = engine.EnviarOrdem(ordem);
}
}));
}
await Task.WhenAll(tasks);
// Verificar consistência: total comprado = total vendido
var estado = engine.ObterEstado("AAPL");
estado.QuantidadeComprada.Should().Be(estado.QuantidadeVendida);
}
[Fact]
public void EnviarOrdem_DeveSerThreadSafe()
{
var engine = new MatchingEngine();
var cts = new CancellationTokenSource();
var errors = 0;
var producer = Task.Run(() =>
{
Parallel.For(0, 10000, new ParallelOptions
{
MaxDegreeOfParallelism = 8
}, i =>
{
try
{
var ordem = new Ordem("AAPL", OrdemLado.COMPRA, 10,
OrdemTipo.LIMITE, 150m);
engine.EnviarOrdem(ordem);
}
catch
{
Interlocked.Increment(ref errors);
}
});
});
producer.Wait();
errors.Should().Be(0);
}
}
Testes de Desempenho (BenchmarkDotNet)
// Instalar: dotnet add package BenchmarkDotNet
[MemoryDiagnoser]
public class OrderBookBenchmark
{
private OrderBook _book = null!;
private List<Ordem> _ordens = null!;
[Params(1000, 10000, 100000)]
public int NumeroOrdens { get; set; }
[GlobalSetup]
public void Setup()
{
_book = new OrderBook("AAPL");
_ordens = new List<Ordem>();
var rng = new Random(42);
for (int i = 0; i < NumeroOrdens; i++)
{
_ordens.Add(new Ordem(
"AAPL",
rng.Next(2) == 0 ? OrdemLado.COMPRA : OrdemLado.VENDA,
rng.Next(1, 1000),
OrdemTipo.LIMITE,
Math.Round((decimal)(rng.NextDouble() * 100 + 100), 2)));
}
}
[Benchmark]
public void InserirOrdens()
{
foreach (var ordem in _ordens)
_book.AdicionarOrdem(ordem);
}
}
// Executar: BenchmarkRunner.Run<OrderBookBenchmark>();
Testes de concorrência são essenciais em sistemas financeiros. Use Parallel.For para simular carga real. BenchmarkDotNet para métricas de performance.