Press enter to see results or esc to cancel.

FSCheck: Gerando valores customizados para seu teste de propriedade

Em um outro post, falamos um pouco sobre a utilização do FSCheck para criação de testes de propriedade usando C#. Mostrei os primeiros passos com o FSCheck, e a criação de testes de propriedade bem simples onde nosso teste dependia de um valor uint para representar uma porcentagem, porém esse tipo de valor pode ser maior do que 100, o que no nosso caso se encaixa muito bem dado que nossa porcentagem seria somente de 0 a 100.

Nesse post iremos nos aprofundar um pouco mais na criação de parâmetros customizáveis, limitando os valores, criando alguns mais específicos e descritivos.


Contexto

No primeiro post que fiz sobre o FSCheck mostrei um contexto de uma aplicação de FlashCards onde testávamos se um cartão tinha sua porcentagem de dificuldade diminuída caso o usuário acertasse a reposta ou se aumentava a dificuldade caso o usuário errasse. O código do nosso exemplo é esse aqui:

[Property]
public Property DeveDiminuirDificuldadeQuandoAcertarAResposta(int porcentagem)
{
 var calculadora = new CalculadoraDeDificuldade();
 var cartão = new Cartão("qlqr pergunta", porcentagem);

 var novoCartão = calculadora.AjustaDificuldade(cartão, true);

 return (novoCartão.Porcentagem < porcentagem).ToProperty();
}

A questão com esse teste é que o valor de porcentagem está variando entre todos os possíveis para uma variável do tipo int, e a nossa propriedade só é válida quando o valor da porcentagem for entre 0 e 100. O FsCheck possui o método When que faz exatamente isso, portanto podemos alterar a criação da nossa propriedade para que fique assim:

 
return (novoCartão.Porcentagem < porcentagem)
         .ToProperty()
         .When(porcentagem != 0 && porcentagem < 100); 

Utilizando o método When  conseguimos limitar o valor do parâmetro porcentagem que será válido para a nossa Propriedade, porém agora todo teste que dependa desse valor teremos que adicionar esse código. O que precisamos é que o FSCheck saiba gerar corretamente os valores, e para isso ele disponibiliza uma maneira de criar os nossos próprios geradores de valores.

Gerando valores customizáveis

Para criar esse valores customizaveis iremos criar uma classe para agrupar os geradores, e esses geradores serão métodos estáticos que retornam um Arbitrary do valor que quer gerar.
O nome do método podemos colocar algo que deixe explícito o retorno.
No nosso caso o tipo que usamos é int, iremos criar um método estatico que irá retornar um Arbitrary de int e iremos dar o nome de Porcentagem.

    public class Tipos
    {
        public static Arbitrary<int> Porcentagem() =>
            Arb.Default.Int32().Generator.
                Where(x => x > 0 && x <= 100).ToArbitrary();
    }

Olhando a implementação acima veja que utilizamos o próprio gerador de int da biblioteca Arb.Default.Int32().Generator e depois usamos um Where para filtrar o intervalo de valores que queremos. E para retornar um Arbitrary basta chamar o método ToArbitrary.
Agora que criamos esse gerador, precisamos fazer indicar para o nosso teste que deve utilizar essa classe como fonte. Conseguimos fazer isso apenas alterando o atributo Property:

[Property(Arbitrary = new[] {typeof(Tipos)})]

Com isso quando o nosso teste solicita uma entrada to tipo int, ele acha o nosso gerador e chama ele ao invés do padrão, com isso nosso código não precisa mais lidar com o tratamento do valor e podemos remover o When que tínhamos colocado.

Olhando nosso código, utilizamos a porcentagem para criar nosso objeto Cartão e usamos ele para fazer o teste. E se, ao invés de receber a porcentagem, nós recebessemos um objeto de Cartão já pronto?

Criando nosso Cartão

Para criar um novo gerador, precisamos alterar nossa classe Tipos, e adicionar agora um novo método que retorne um Arbitrary de Cartão. Nele podemos utilizar o gerador de Porcentagem que criamos e mapear para um Cartão. Veja como fica a implementação:

public static Arbitrary<Cartão> Cartão() =>
  Porcentagem().Generator
    .Select(porcentagem => new Cartão(Arb.Default.String()
                                                 .Generator
                                                 .Sample(20, 1)
                                                 .Single(),
                                      porcentagem))
    .ToArbitrary();

Veja que estamos mapeando uma porcentagem para o nosso Cartão, e utilizamos outro gerador padrão do FSCheck para construir a descrição do cartão de maneira automática também. Agora basta alterar nosso teste para receber um Cartão

        [Property(Arbitrary = new[] {typeof(Tipos)})]
        public Property DeveDiminuirDificuldadeQuandoAcertarAResposta3(Cartão cartão)
        {
          var calculadora = new CalculadoraDeDificuldade();

          var novoCartão = calculadora.AjustaDificuldade(cartão, true);

          return (novoCartão.Porcentagem < cartão.Porcentagem).ToProperty();
        }

Com isso conseguimos construir qualquer objeto nosso com as restrições que precisamos para determinada propriedade, e deixar nosso teste simples e descritivo.
Podemos também criar classes que representem estados dos nossos objetos, como por exemplo um CartãoComPorcentagemAcimaDe80 que estende o Cartão. Dá para viajar nisso, mas falaremos disso em outro post.

Se quiser dar uma olhada no código, ele está no meu github: fscheck-examples


Eai, o que achou da utilização de geradores customizáveis? Tem dúvidas? Já usou?
Deixe um comentário 🙂

Abraços

 

Imagem usada no post Orsolya Vékony em Unsplash

Comments

Leave a Comment