Ir para o conteúdo do rodapé
Iron Academy Logo
Aprenda C#
Aprenda C#

Outras categorias

Asserções Fluentes em Testes Unitários em C#

[[academy-video-youtube({"vid": "TytferBCLOo", "start_time": "0", "title": "Fluent Assertions in Unit Testing in C#", "creator": "Tim Corey", "length": "10m 6s"})]]

Testes de unidade são escritos uma vez e lidos muitas vezes, o que torna a linha de asserção um dos detalhes mais consequentes em um suite de testes. Assert.Equal(expected, actual) funciona, mas uma mensagem de falha que apenas compara duas strings nem sempre explica o que o teste estava verificando. Fluent Assertions reformula a sintaxe de asserção para que a linha leia como a regra que ela impõe, e a mensagem de falha se torne uma sentença em vez de um diff.

Em seu vídeo "Fluent Assertions in Unit Testing in C#," Tim Corey pega um projeto xUnit que já exercita um SampleClass caminho feliz, depois introduz um caso extremo (Eddie Van Halen) que quebra a lógica simples de dividir no espaço. Ele instala o pacote Fluent Assertions, reescreve a asserção usando a cadeia .Should().Be(), demonstra como encadear múltiplas condições em um único valor e termina com AssertionScope para que um único teste reporte todas as condições falhas de uma vez em vez de desistir na primeira. Equipes cujo conjunto de testes cresceu além do ponto onde diffs de igualdade pura são claros encontrarão o caminho de atualização aqui.

O Ponto de Partida xUnit

[0:35 - 2:46] O projeto na tela é pequeno: uma biblioteca de classes com um único SampleClass que pega um nome completo em seu construtor e o divide no espaço em FirstName e LastName, além de um projeto de teste xUnit que exercita o caminho feliz. Os testes usam Theory com atributos InlineData para executar a mesma lógica contra duas entradas ("Tim Corey" e "Sue Storm"), e cada asserção é o formulário padrão Assert.Equal(expected, actual).

[Theory]
[InlineData("Tim Corey", "Tim")]
[InlineData("Sue Storm", "Sue")]
public void TestFirstNameProperty(string fullName, string expected)
{
    var sample = new SampleClass(fullName);
    Assert.Equal(expected, sample.FirstName);
}
[Theory]
[InlineData("Tim Corey", "Tim")]
[InlineData("Sue Storm", "Sue")]
public void TestFirstNameProperty(string fullName, string expected)
{
    var sample = new SampleClass(fullName);
    Assert.Equal(expected, sample.FirstName);
}

Executar o conjunto fornece seis testes aprovados. Nada disso está errado, e para os casos que se encaixam em "primeiro espaço último", as asserções são suficientemente claras. O território interessante começa quando um nome não se encaixa nessa forma.

Onde o Caminho Feliz Deixa de Funcionar

[2:46 - 4:32] O caso extremo que Tim escolhe é "Eddie Van Halen", um nome de três palavras onde o sobrenome é "Van Halen", não apenas o token após o primeiro espaço. A implementação atual pega o índice 1 da divisão e o chama de sobrenome, então a classe retorna "Van" para o sobrenome e descarta "Halen" completamente. O bug é real e escrever um teste falho para ele é o primeiro passo para corrigi-lo.

Escrever esse teste com Assert.Equal("Van Halen", sample.LastName) funciona, mas a mensagem de falha é lida como uma comparação de strings sem contexto. Mudar para Fluent Assertions faz o mesmo teste expressar a regra em palavras e produzir uma mensagem de falha que nomeia a propriedade sob teste. Para um pequeno conjunto a diferença parece cosmética; para um conjunto de algumas centenas de asserções, a diferença de legibilidade se compõe.

Instalando Asserções Fluent

[4:32 - 5:10] O pacote está disponível no NuGet sob FluentAssertions. Tim o destaca como um dos pacotes mais baixados no registro, o que é um contexto útil: qualquer novo colaborador provavelmente já o viu antes. O projeto de teste recebe duas diretivas de uso no topo:

using FluentAssertions;
using FluentAssertions.Execution;
using FluentAssertions;
using FluentAssertions.Execution;

A primeira traz os métodos de extensão de asserção, que é o que a maioria dos testes precisa. O segundo existe para o tipo AssertionScope abordado mais adiante no vídeo; testes que não agrupam asserções podem deixá-la de fora.

A Sintaxe Should.Be

[5:10 - 6:14] A reescrita mínima do Fluent Assertions do teste do Van Halen:

[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");
    sample.LastName.Should().Be("Van Halen");
}
[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");
    sample.LastName.Should().Be("Van Halen");
}

A cadeia lê próxima ao inglês falado. A extensão .Should() retorna um objeto de asserção cujos métodos descrevem a comparação; .Be(...) faz uma verificação de igualdade. Sensibilidade a maiúsculas e minúsculas, espaços em branco à frente e ao final são todos parte daquela verificação, que corresponde ao comportamento da maioria das comparações de string no código de produção.

Executar este teste contra a implementação quebrada produz uma mensagem de falha que nomeia a propriedade e explica a discrepância: "Esperado que sample.LastName fosse 'Van Halen' com um comprimento de 9, mas 'Van' tem um comprimento de 3." A mensagem identifica o valor em teste pela expressão que o produziu, que é o tipo de contexto que o formulário simples Assert.Equal não fornece.

Encadeando Múltiplas Condições

[6:14 - 8:10] Uma única propriedade frequentemente precisa satisfazer mais de uma regra. Fluent Assertions compõe condições com .And para que a cadeia permaneça em uma única instrução:

sample.LastName.Should()
    .StartWith("Van")
    .And.EndWith("len")
    .And.Contain(" ");
sample.LastName.Should()
    .StartWith("Van")
    .And.EndWith("len")
    .And.Contain(" ");

Cada elo na cadeia é uma condição separada. Por padrão, a cadeia para de reportar na primeira falha: se StartWith("Van") passar mas EndWith("len") falhar, a mensagem de falha nomeará EndWith e a asserção para ali. Para testes onde cada condição é independente e você quer que cada falha seja apresentada, o escopo da asserção (próxima seção) altera esse comportamento.

A sintaxe de encadeamento se inclina para expressar a intenção sobre enumerar a igualdade. Um teste que diz "o sobrenome deve começar com Van, terminar com len e conter um espaço" lê como uma especificação. A mesma lógica escrita com três chamadas Assert.True separadas seria lida como três verificações booleanas sem uma narrativa que as conecte.

Relatando Cada Falha com Escopos de Asserção

[8:10 - 9:34] O comportamento padrão de parar na primeira falha é bom para cadeias onde condições posteriores dependem de condições anteriores. Para cadeias onde cada condição importa independentemente, envolver as asserções em um AssertionScope faz com que o teste reporte todas as falhas em uma execução:

[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");

    using var _ = new AssertionScope();
    sample.LastName.Should().StartWith("Van");
    sample.LastName.Should().EndWith("len");
    sample.LastName.Should().Contain(" ");
}
[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");

    using var _ = new AssertionScope();
    sample.LastName.Should().StartWith("Van");
    sample.LastName.Should().EndWith("len");
    sample.LastName.Should().Contain(" ");
}

A linha de descarte usando-variável é o padrão: o escopo dura até que a variável saia de escopo no final do método, e o sublinhado de descarte sinaliza que a própria variável nunca é lida. Quando o teste falha, a mensagem contém cada condição falha como uma linha separada, o que significa que uma execução dá toda a história em vez de forçar três execuções para apresentar três problemas. Para testes que verificam a forma de um objeto retornado em várias propriedades, isso é a diferença entre corrigir um bug por ciclo de teste e corrigir todos eles de uma vez.

Concluindo: Testes Que Lêem Como Especificações

[9:34 - 10:06] Asserções Fluent não alteram o que os testes verificam; ele muda como a verificação é lida. A cadeia .Should(), a composição .And e o agrupamento AssertionScope todos empurram o código de teste mais para perto de uma especificação escrita do comportamento em teste. Combinado com o mesmo tipo de estilo fluente usado por FluentValidation para validação de entrada, o padrão produz suites de teste e regras de validação que os novatos podem ler sem precisar de um tour.

Conclusão

[9:34 - 10:06] Adicionar Fluent Assertions a um projeto de teste requer uma instalação NuGet, duas diretivas using e uma reescrita da linha de asserção de Assert.Equal(expected, actual) para actual.Should().Be(expected). Cadeias expressam regras de múltiplas condições em uma única instrução, e AssertionScope faz com que um único teste reporte todas as falhas em vez de parar na primeira. O benefício são mensagens de falhas que explicam a regra que quebrou, que é a diferença entre um rastreio da pilha e uma frase.

Dica de exemplo: Quando asserindo em coleções, prefira result.Should().BeEquivalentTo(expected) em vez de comparação propriedade a propriedade. Ele percorre o gráfico de objetos para você e reporta a primeira propriedade divergente pelo caminho (por exemplo, users[2].Address.City), o que é muito mais útil do que uma mensagem "coleções não são iguais" quando uma lista de 50 elementos não concorda em um campo aninhado.

Assista ao vídeo completo aqui no canal do YouTube dele IAmTimCorey e obtenha mais insights sobre escrever testes em C# legíveis e sustentáveis na série de Treinamento de 10 Minutos.

Hero Worlddot related to Asserções Fluentes em Testes Unitários em C#
Hero Affiliate related to Asserções Fluentes em Testes Unitários em C#

Ganhe mais compartilhando o que você ama.

Você cria conteúdo para desenvolvedores que trabalham com .NET, C#, Java, Python ou Node.js? Transforme sua expertise em renda extra!

Equipe de Suporte Iron

Estamos online 24 horas por dia, 5 dias por semana.
Bater papo
E-mail
Liga para mim