Pular para o conteúdo principal

Conheça o instancio

Uma biblioteca Java para automatizar a configuração de dados em testes de unidade.



Neste post, vou apresentar a biblioteca instancio (www.instancio.org), uma ferramenta poderosa para facilitar a criação de testes unitários em Java. A biblioteca instancio permite instanciar objetos complexos com apenas uma linha de código, evitando a necessidade de escrever construtores, setters ou builders. Além disso, a biblioteca instancio é compatível com o Junit, o framework mais popular para testes unitários em Java.


Para ilustrar o uso da biblioteca instancio, vou usar um exemplo de um sistema de escola que oferece cursos aos alunos. O sistema possui as classes Curso, Aluno, Plrofessor e Matricula, que representam os conceitos do domínio. Cada classe possui vários atributos e métodos, que não serão detalhados aqui por questão de simplicidade. O objetivo é testar o método matricularAluno da classe Curso, que recebe um objeto Aluno e cria uma nova Matricula associada ao curso e ao aluno.


Sem a biblioteca instancio, para criar um objeto Curso e um objeto Aluno para o teste, seria necessário escrever algo como:


Curso curso = new Curso();
curso.setId(1);
curso.setNome("Java Básico");
curso.setCargaHoraria(40);
curso.setPreco(500.0);

Aluno aluno = new Aluno();
aluno.setId(1);
aluno.setNome("João");
aluno.setEmail("joao@gmail.com");
aluno.setTelefone("999999999");


Com a biblioteca instancio, basta escrever:


Curso curso = Instancio.instance(Curso.class);                                                                      
Aluno aluno = Instancio.instance(Aluno.class);


A biblioteca instancio se encarrega de gerar valores aleatórios e válidos para os atributos das classes, seguindo algumas regras simples. Por exemplo, se o atributo é do tipo String, a biblioteca gera uma string alfanumérica; se o atributo é do tipo int, a biblioteca gera um número inteiro; se o atributo é do tipo boolean, a biblioteca gera um valor verdadeiro ou falso; e assim por diante. A biblioteca também suporta atributos que são outras classes, listas, enums ou interfaces.

Casos de teste

Bons testes devem cobrir todos os ramos da lógica condicional. Isso nos dá mais confiança em nosso código e, muitas vezes, revela problemas que podemos ter esquecido. Embora nosso serviço seja muito simples, ele ainda apresenta alguns cenários que precisam ser cobertos.

Depois de criar os objetos Curso e Aluno com a biblioteca instancio, basta chamar o método matricularAluno e verificar se o resultado é o esperado. Para isso, podemos usar o Junit e as suas asserções. O teste ficaria assim:

@Test
public void testMatricularAluno() {
  // Arrange
  Curso curso = Instancio.instance(Curso.class);                                                                      
  Aluno aluno = Instancio.instance(Aluno.class);

  // Act
  Matricula matricula = curso.matricularAluno(aluno);

  // Assert
  assertNotNull(matricula);
  assertEquals(curso, matricula.getCurso());
  assertEquals(aluno, matricula.getAluno());
}


Como podemos ver, a biblioteca instancio simplifica bastante a criação de testes unitários em Java, reduzindo a quantidade de código necessário e aumentando a legibilidade dos testes. A biblioteca instancio é gratuita e open source, e pode ser baixada no site www.instancio.org. Recomendo que você experimente a biblioteca instancio e veja como ela pode melhorar a qualidade dos seus testes unitários.

Configurando o projeto

Para usar a biblioteca instancio, você só precisa adicionar a dependência no seu projeto. Neste caso, estou utilizando maven:


<dependency>
    <groupId>org.instancio</groupId>
    <artifactId>instancio-core</artifactId>                                                                                 
    <version>2.16.1</version>
    <scope>test</scope>
</dependency>


A biblioteca instancio também suporta tipos genéricos, classes record e sealed, arrays e streams. Você pode criar coleções de objetos usando o método `ofList`:


List<Aluno> alunos = Instancio.ofList(Aluno.class).size(10).create();


Ou criar tipos genéricos usando o `TypeToken`:

Pair<List<Foo>, List<Bar>> pairOfLists = Instancio.create(new TypeToken<Pair<List<Foo>, List<Bar>>>() {});


Como personalizar os valores gerados

Se você precisar personalizar os valores gerados pela biblioteca instancio, você pode usar vários métodos da API para isso. Por exemplo, você pode usar o método `generate` para definir um gerador customizado para um campo específico:

Pessoa pessoa = Instancio.of(Pessoa.class)
.generate(field(Pessoa::getDataNascimento), gen -> gen.temporal().localDate().past())         
.create();


Esse código cria um objeto `Pessoa` com o campo `dataNascimento` gerado usando o gerador `gen.temporal().localDate().past()`, que retorna uma data passada aleatória.


Você também pode usar o método `set` para definir um valor fixo para um campo:


Pessoa pessoa = Instancio.of(Pessoa.class).set(field(Pessoa::getNome), "João")                  
.create();


Esse código cria um objeto `Person` com o campo `firstName` definido como "João".


Outro método útil é o `supply`, que permite fornecer um valor dinâmico para um campo usando um `Supplier`:


Pessoa pessoa = Instancio.of(Pessoa.class)
.supply(all(LocalDate.class), () -> LocalDate.now())                                                                  
.create();


Esse código cria um objeto `Pessoa` com todos os campos do tipo `LocalDate` definidos como o momento atual.


Além disso, você pode usar o método `onComplete` para executar uma ação após a criação do objeto:


Pessoa pessoa = Instancio.of(Pessoa.class)
.onComplete(all(Pessoa.class), (Pessoa p) ->
p.setNome(p.getGenero() == Genero.MASCULINO ? "João" : "Maria"))                                
.create();


Esse código cria um objeto `Pessoa` e depois define o campo `nome` de acordo com o campo `genero`.


Como criar modelos reutilizáveis de objetos

Se você precisar criar vários objetos com características semelhantes, você pode usar o conceito de modelos (Models) da biblioteca instancio. Um modelo é uma forma de definir um template de objeto que pode ser reutilizado em diferentes testes.


Por exemplo, se você quiser criar objetos da classe `Pessoa` que representam membros da família Simpson, você pode criar um modelo assim:


Model<Aluno> simpsons = Instancio.of(Aluno.class)
.set(field(Aluno::getSobrenome), "Simpson")
.set(field(Endereco::getCidade), "Springfield")
.generate(field(Aluno::getDataNascimento), gen -> gen.temporal().localDate().past())            
.toModel();


Esse modelo define que todos os objetos `Aluno` criados a partir dele terão o sobrenome "Simpson", a cidade "Springfield" e uma idade entre 40 e 50 anos.


Depois, você pode criar objetos específicos usando esse modelo:


Aluno homer = Instancio.of(simpsons)
.set(field(Aluno::getNome), "Homer")
.set(all(Genero.class), Genero.MASCULINO)                                                                          
.create();


Aluno marge = Instancio.of(simpsons)
.set(field(Aluno::getNome), "Marge")
.set(all(Genero.class), Genero.FEMININO)                                                                              
.create();


Esses códigos criam objetos `Aluno` que representam o Homer e a Marge, usando os valores definidos no modelo e sobrescrevendo os valores específicos de cada um.


Principais recursos da biblioteca instancio

A biblioteca instancio possui vários recursos interessantes que facilitam a geração de dados de teste. Alguns deles são:

  • Dados totalmente reproduzíveis em caso de falha nos testes. A biblioteca instancio usa uma semente (seed) para gerar os dados aleatórios, que pode ser especificada ou obtida pelo usuário. Assim, é possível reproduzir os mesmos dados que causaram a falha e corrigir o problema.
  • Suporte para anotações de validação de beans. A biblioteca instancio pode gerar dados que respeitam as anotações de validação de beans, como `@NotNull`, `@Size`, `@Email`, etc. Isso evita que os testes falhem por causa de dados inválidos.
  • Opções flexíveis de configuração. A biblioteca instancio permite configurar vários aspectos do comportamento da geração de dados, como o modo estrito ou leniente, a profundidade máxima, o mapeamento de subtipos, etc. Essas configurações podem ser feitas programaticamente ou usando um arquivo de propriedades.
  • Integração com JUnit Jupiter. A biblioteca instancio oferece uma extensão para JUnit Jupiter que facilita a reprodução de testes falhos, a injeção de configurações e a geração de argumentos para testes parametrizados.
  • Interface de provedor de serviço (SPI). A biblioteca instancio permite que o usuário crie e registre suas próprias implementações de interfaces como `GeneratorProvider`, `TypeResolver` e `TypeInstantiator`, para customizar ainda mais a geração de dados.


Conclusão


Neste post, apresentei a biblioteca instancio, uma ferramenta poderosa para gerar dados de teste em Java. Mostrei como usar sua API simples e flexível para criar objetos totalmente populados com dados aleatórios, personalizar os valores gerados e criar modelos reutilizáveis de objetos. Eu também listei alguns dos principais recursos da biblioteca, que tornam a geração de dados mais fácil e confiável.


Se você se interessou pela biblioteca instancio, você pode acessar o site https://www.instancio.org/user-guide para ver mais detalhes sobre sua documentação, exemplos e artigos. Você também pode contribuir com o projeto no GitHub: https://github.com/instancio/instancio.


E como sempre, um exemplo de utilização no meu GitHub: https://github.com/sandrogiacom/school-test

Espero que você tenha gostado deste post e que a biblioteca instancio possa te ajudar a escrever testes melhores e mais concisos em Java. Até a próxima!


Referências:






Comentários

Postagens mais visitadas deste blog

Java Records

  Java Records Imutável, Simples e limpa Esta funcionalidade da linguagem apareceu pela primeira vez na versão 14 como experimental e assim continuou até a versão 15 . Agora liberada de forma definitiva no Java 16 . O objetivo é ser possível ter classes que atuam como portadores transparentes de dados imutáveis. Os registros podem ser considerados tuplas nominais. Ou seja, após criado, um record não pode mais ser alterado. Records oferece uma uma sintaxe compacta para declarar classes que são portadores transparentes para dados imutáveis superficiais visando reduzir significamente o detalhamento dessas classes e irá melhorar a capacidade de leitura e manutenção do código. Vamos seguir um exemplo de uma classe chamada Pessoa . O primeiro exemplo vamos utilizar o modo tradicional. public class Pessoa { private String nome; private int idade; public Pessoa (String nome, int idade) { super (); this .nome = nome; this .idade = idade; } public String getNo

Java 8 ao 18: Mudanças mais importantes na plataforma Java

    Vamos rever muitas das mudanças mais importantes na plataforma Java que aconteceram entre a versão 8 (2014) e 18 (2022)   O Java 8 foi lançado em março de 2014 e o Java 18 em março de 2022. São 8 anos de progresso, 203 JEPs (JDK Enhancement Proposals ), entre essas duas versões. Neste post, revisaremos as mudanças mais importantes e discutiremos os benefícios e desafios da adoção de versões mais recentes do JDK para novos aplicativos e para os mais antigos compilados com versões mais antigas. Desde a versão 9, o Java tem novos recursos a cada 6 meses e é muito difícil acompanhar essas novas mudanças. A maioria das informações na internet descreve as mudanças entre as duas últimas versões do Java. No entanto, se você estiver em uma situação semelhante à minha, não está usando uma das versões mais recentes do Java, mas uma das várias versões anteriores (Geralmente 8 ou 11 que são as versões de suporte estendido). Então é útil saber quais novos recursos foram adicionados d

O suporte de longo prazo e o que o LTS significa para o ecossistema Java

A arte do suporte de longo prazo e o que o LTS significa para o ecossistema Java Aqui está o que o Java 17 tem em comum com o Java 11 e o Java 8. Em junho de 2018, há pouco mais de três anos, a Oracle e outros participantes do ecossistema Java anunciaram uma mudança no modelo de cadência de lançamento para Java SE. Em vez de ter um lançamento principal planejado a cada dois ou quatro anos (que geralmente se torna de três a quatro anos), um novo modelo de lançamento de recursos de seis meses seria usado: a cada três anos, um lançamento seria designado como Long-Term Support (LTS) e receba apenas atualizações trimestrais de segurança, estabilidade e desempenho. Esse padrão foi emprestado descaradamente do modelo de lançamento do Mozilla Firefox, mas o ajustou para ficar mais alinhado com os requisitos de uma plataforma de desenvolvimento. A primeira versão do Java lançada sob esse modelo foi o Java SE 11. O lançamento do Java SE 17, o segundo lançamento do LTS sob o novo