kb.erickguedes.com
C#: Matching Engine — Projeto Mercado Financeiro

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.