Introdução ao Lambda Behave

Introdução ao Lambda Behave

1. Visão geral

Neste artigo, discutiremos uma nova estrutura de teste baseada em Java chamadaLambda Behave.

Como o nome sugere, essa estrutura de teste foi projetada para funcionar com o Java 8 Lambdas. Além disso, neste artigo, examinaremos as especificações e veremos um exemplo para cada uma.

A dependência do Maven que precisamos incluir é:


    com.insightfullogic
    lambda-behave
    0.4

A versão mais recente pode ser encontradahere.

2. Fundamentos

Um dos objetivos da estrutura é obter grande legibilidade. A sintaxe nos encoraja a descrever casos de teste usando frases completas, em vez de apenas algumas palavras.

Podemos alavancar testes parametrizados e quando não queremos vincular casos de teste a alguns valores predefinidos, podemos gerar parâmetros aleatórios.

3. Implementação do Teste Lambda Behave

Cada conjunto de especificações começa comSuite.describe.. Neste ponto, temos vários métodos integrados para declarar nossa especificação. Portanto, aSuite é como uma classe de teste JUnit e as especificações são como os métodos anotados com@Test em JUnit.

Para descrever um teste, usamosshould(). Da mesma forma, se nomearmos o parâmetro lambda de expectativa como“expect”,, poderíamos dizer qual resultado esperamos do teste, porexpect.that().

Se quisermos configurar ou destruir quaisquer dados antes e depois de uma especificação, podemos usarit.isSetupWith()eit.isConcludedWith(). Da mesma forma, para fazer algo antes e depois deSuite, Vou usarit.initiatizesWith()eit.completesWith().

Vamos ver um exemplo de uma especificação de teste simples para a classeCalculator:

public class Calculator {

    public int add() {
        return this.x + this.y;
    }

    public int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException();
        }
        return a / b;
    }
}

Vamos começar comSuite.describe e adicionar o código para inicializar oCalculator.

A seguir, testaremos o métodoadd() escrevendo uma especificação:

{
    Suite.describe("Lambda behave example tests", it -> {
        it.isSetupWith(() -> {
            calculator = new Calculator(1, 2);
        });

        it.should("Add the given numbers", expect -> {
            expect.that(calculator.add()).is(3);
        });
}

Aqui, nomeamos as variáveis“it”e“expect” para melhor legibilidade. Como esses são nomes de parâmetros lambda, podemos substituí-los por qualquer nome de nossa escolha.

O primeiro argumento deshould() descreve o uso de inglês simples, o que este teste deve verificar. O segundo argumento é um lambda, que indica nossa expectativa de que o métodoadd() retorne 3.

Vamos adicionar outro caso de teste para divisão por 0 e verificar se obtivemos uma exceção:

it.should("Throw an exception if divide by 0", expect -> {
    expect.exception(ArithmeticException.class, () -> {
        calculator.divide(1, 0);
    });
});

Neste caso, estamos esperando uma exceção, então declaramosexpect.exception()e dentro disso, escrevemos o código que deve lançar uma exceção.

Observe que a descrição do texto deve ser exclusiva para cada especificação.

4. Especificações baseadas em dados

Essa estrutura permite a parametrização de teste no nível de especificação.

Para criar um exemplo, vamos adicionar um método à nossa classeCalculator:

public int add(int a, int b) {
    return a + b;
}

Vamos escrever um teste baseado em dados para isso:

it.uses(2, 3, 5)
  .and(23, 10, 33)
  .toShow("%d + %d = %d", (expect, a, b, c) -> {
    expect.that(calculator.add(a, b)).is(c);
});

O métodouses() é usado para especificar dados de entrada em diferentes números de colunas. Os primeiros dois argumentos são os parâmetros da funçãoadd() e o terceiro é o resultado esperado. Esses parâmetros também podem ser usados ​​na descrição, como mostrado no teste.

toShow() é usado para descrever o teste usando os parâmetros - com a seguinte saída:

0: 2 + 3 = 5 (seed: 42562700892554)(Lambda behave example tests)
1: 23 + 10 = 33 (seed: 42562700892554)(Lambda behave example tests)

5. Especificações geradas - teste baseado em propriedade

Normalmente, quando escrevemos um teste de unidade, queremos nos concentrar em propriedades mais amplas que se aplicam ao nosso sistema.

Por exemplo, quando testamos uma função de reversãoString, podemos verificar se, se revertermos umString particular duas vezes, acabaremos com oString. original

Property-Based testing focuses on the generic property without hard-coding specific test parameters. Podemos conseguir isso usando casos de teste gerados aleatoriamente.

Essa estratégia é semelhante ao uso de especificações orientadas a dados, mas em vez de especificar a tabela de dados, especificamos o número de casos de teste a serem gerados.

Portanto, nosso teste baseado em propriedade de reversãoString ficaria assim:

it.requires(2)
  .example(Generator.asciiStrings())
  .toShow("Reversing a String twice returns the original String",
    (expect, str) -> {
        String same = new StringBuilder(str)
          .reverse().reverse().toString();
        expect.that(same).isEqualTo(str);
   });

Temos o método_ indicated the number of required test cases using the _requires(). Usamos a cláusulaexample() para indicar que tipo de objetos precisamos e como.

A saída para esta especificação é:

0: Reversing a String twice returns the original String(ljL+qz2)
  (seed: 42562700892554)(Lambda behave example tests)
1: Reversing a String twice returns the original String(g)
  (seed: 42562700892554)(Lambda behave example tests)

5.1. Geração de caso de teste determinístico

Quando usamos os casos de teste gerados automaticamente, torna-se bastante difícil isolar falhas de teste. Por exemplo,if our functionality fails once in 1000 times, a specification that auto-generates just 10 cases, will have to be run over and over to observe the error.

Portanto, precisamos da capacidade de executar deterministicamente os testes, incluindo casos com falha anterior.

O Lambda Behave é capaz de lidar com esse problema. Conforme mostrado na saída do caso de teste anterior, ele imprime a semente que foi usada para gerar o conjunto aleatório de casos de teste. Então, se algo falhar,we can use the seed to re-create previously generated test cases.

Podemos olhar a saída do caso de teste e identificar a semente:(seed: 42562700892554). Agora, para gerar o mesmo conjunto de testes novamente, podemos usar oSourceGenerator.

OSourceGenerator contém o métododeterministicNumbers() que leva apenas a semente como argumento:

 it.requires(2)
   .withSource(SourceGenerator.deterministicNumbers(42562700892554L))
   .example(Generator.asciiStrings())
   .toShow("Reversing a String twice returns the original String",
     (expect, str) -> {
       String same = new StringBuilder(str).reverse()
         .reverse()
         .toString();
       expect.that(same).isEqualTo(str);
});

Ao executar este teste, obteremos a mesma saída que vimos anteriormente.

6. Conclusão

Neste artigo, vimos como escrever testes de unidade usando as expressões lambda do Java 8, em uma nova estrutura de teste fluente, chamada Lambda Behave.

Como sempre, o código para esses exemplos pode ser encontradoover on GitHub.