Concrete Logo
Hamburger button

O mundo mágico do JUnit Runners

  • Blog
  • 28 de Maio de 2018

Algum dia vocês já se pegaram perguntando como o JUnit, o framework de testes mais popular na comunidade Java, consegue rodar todas as suas classes de teste? Ou se ele conseguiria montar uma suíte com alguns testes específicos? Até mesmo criar seu próprio Runner? Te convido a conhecer o mundo dos Runners, no qual você terá acesso a um novo leque de conceitos e ferramentas para escrever testes.

Mas afinal, o que são Runners?

Do ponto de vista do JUnit, um Runner é o responsável por instanciar uma classe de teste e executar todos os seus respectivos métodos. Existem várias implementações de Runners, como Parameterized e Suite, nas quais podemos controlar a parte de execução dos testes, mas falamos disso mais tarde. Por hora vamos dizer que um Runner está ligado ao começo do processo de execução de um teste.

Para definirmos um Runner em uma classe de teste utilizamos a anotação RunWith:

Neste exemplo, o BlockJUnit4ClassRunner está sendo atribuído como o Runner da classe MyClassTest. Vejamos agora como o JUnit chama esse Runner.

JUnitCore

Para entender melhor os Runners, nós temos que ir um pouco mais a fundo na arquitetura do JUnit.

Quando executamos uma classe de teste por linha de comando acabamos com algo mais ou menos assim:

Utilizamos uma classe que se chama JUnitCore porque dentro dela está o método main da aplicação. Por sua vez, ela pega o parâmetro passado, no caso o nome da classe, e instancia o Runner que aquele teste requer (ele sabe pela anotação RunWith). Caso não haja um Runner anotado o default (BlockJUnit4ClassRunner) é utilizado. Então começa um novo processo de criação de classes, mas dessa vez comandado pelo Runner. As classes de teste são passadas e ele cuida de executar os métodos anotados como Test.

No caso de uma IDE, como o Intellij, existe uma classe que se chama JUnitStarter que em algum momento chama o JUnitCore e o mesmo processo começa.

Todo esse processo do JUnitCore é feito a base de reflection, tanto para conseguir identificar os Runners corretos de cada classe como para conseguir executar os métodos dentro dos Runners.

Implementações de Runner

O JUnit oferece algumas implementações de Runners: Suite, Parameterized, Categories e Enclosed. Vamos analisar separadamente cada uma delas.

Parameterized

Vamos supor que você tenha uma classe que faça a validação de um determinado input e retorna true ou false. O teste dela seria algo mais ou menos assim:

Neste caso temos que escrever um teste para cada cenário: válido, inválido, vazio, nulo e qualquer outro caso que quisermos validar. Se olharmos como os testes foram feitos, podemos reparar que o método que estamos testando é o mesmo, o que nos dá a sensação de como se estivéssemos utilizando o famoso “copiar/colar”. Com a ajuda do Parameterized conseguimos testar todos os cenários com um teste só:

Note que primeiro anotamos a classe com o Runner Parameterized. Depois criamos um método estático anotado como Parameters que retorna todos os parâmetros que precisamos para executar nossos testes. Por último um construtor, que vai receber nossos parâmetros quando o Runner instanciar nossa classe. Podemos também, no lugar de um construtor, anotar os campos que serão parametrizados com Parameter, que seriam injetados pelo Runner:

O número entre parênteses demonstra que o parâmetro será injetado naquele campo.

Suite

Com este Runner nós podemos definir uma série de classes de testes a serem executadas:

Categories (experimental)

Este Runner identifica somente os testes anotados com uma categoria. As categorias servem para filtrar os testes que serão executados, fazendo com que rodemos somente os testes desejados.

Vamos supor que temos um tipo de teste “A” e ele ocorre em vários testes. Se criarmos uma interface “A” e anotarmos todos os testes com Category(A.class) o Runner consegue filtrar somente os testes anotados.

Podemos incluir mais de uma categoria e excluir um tipo também, mas por que excluir? Bom, os métodos podem ter mais de uma categoria, no caso de querermos que somente os testes “A” e “B” sejam executados e o “C” não, teríamos algo como:

Enclosed (experimental)

Este Runner é utilizado quando temos uma classe de teste com inner classes. A classe “pai” precisa ser anotada como Enclosed e suas inner classes precisam ser estáticas para que funcione:

Cada inner class também pode ser anotada com um Runner diferente. Vamos utilizar a classe do exemplo dado no Parameterized e uma outra com o Runner default. Neste caso teríamos algo assim:

Existem ainda as implementações customizadas de um Runner . Um bom exemplo é o IntermittentTestRule , um Runner para cuidar de testes intermitentes da biblioteca tempus-fugit. Podemos também criar nossas próprias implementações mas este é um assunto para um próximo post, pois o mundo dos Runners customizados é vasto.

Para concluir, os Runners podem ser uma ferramenta poderosa na hora de criarmos nossos testes, nos ajudando a ter uma visão diferente na hora de escrever e ter controle de como serão executados. Se ficou alguma dúvida ou tem algo a acrescentar, aproveite os campos abaixo. Até a próxima!

A Concrete, parte da Accenture Technology, é referência em consultoria ágil, facilitando a transformação digital dos clientes ao criar produtos digitais inovadores. Só no capítulo Android contamos com 60 pessoas, que compartilham informações o tempo todo, em talks semanais e treinamentos frequentes. Nossos times têm autonomia para trabalhar e acesso direto aos gerentes, o que facilita a troca de conhecimento e a constante evolução técnica. Quer trabalhar com os melhores? Acesse: concrete.com.br/vagas