Aproxime o momento de teste com o desenvolvimento!

Conheça, explore e contribua com o JTCGen.

1. Sobre o projeto

A motivação para desenvolver este projeto se deu a partir de um TCC, que foi concluído. A validação deste framework com a comunidade foi um ponto chave para a conclusão do trabalho e pelas respostas e o feedback que coletamos dos participantes nos motivou a continuar com a implementação desta ferramenta.

Para detalhes técnicos de implementação, veja: Documentação.

Testar software é muito importante, mas é uma tarefa de grande custo que se deve estudar bem as funcionalidades que deverão ter uma maior carga de testes e/ou testes mais elaborados. Este framework nasceu para validar o que acreditamos ser o "meio do caminho", que esta entre outros dois caminhos que já conhecemos: Testar seu código após a codificação ou utilizando, uma técnica cada vez mais popular chamada, Test-Driven Development, onde o desenvolvimento acaba sendo guiado pelo desenvolvimento de testes automatizados.

Link: Questionário para validação de estudo empregado na Lib. JTCGen

2. Primeiros Passos (Overview)

Para facilitar o teste do framework, clicando aqui você pode encontrar um pequeno projeto com algumas classes para explorar o framework.

INFO Ao executar o framework, caso exista um teste com o mesmo nome de arquivo que será gerado, será perguntado se deseja sobrescreve-lo. Por segurança, todo caso de testes sobrescrito é criada uma cópia de backup.

2.1 Pré-requisitos e Dependências

Este projeto foi desenvolvido utilizando recursos da JDK Versão 8.

O framework não funciona nas versões anteriores do Java.

Este projeto cria casos de teste no padrão do JUnit, e escrita de Mock Objects no padrão do Mockito.

JUnit: http://junit.org/junit4/

Mockito: http://site.mockito.org/

2.2 Instalação

Este framework foi desenvolvida na linguagem Java para ser utilizada nos casos de testes desenvolvidos também em projetos Java.

Faça o download do .jar, integre-o em seu build path.

Após esta configuração inicial, será necessária a implementação de uma classe de manifesto. Conforme o exemplo:

          
            public class Manifest {
              public static void main(String[] args) {
                JTCGenenerator jtcGenenerator = new JTCGenenerator();

                jtcGenenerator.generateTests();
              }
            }
          
        

2.3 Utilização Básica

Na sua classe de produção, marque a classe com a Annotation @JTCGen e acima do método que deseja criar o teste automatizado, informe através de uma expressão baseada na documentação desta lib. o caso de teste informando os dados necessários:

            
              ...

              @JTCGen
              class ContaCorrente extends Conta {
                
                  ...

                  @Test("setup([10, 12, 100.0]).parameter([{c: 'ContaPoupanca@getSaldo()', v: 200.0}]).eq(300.0)")
                  public double somaValoresDasContas(ContaPoupanca cp) {
                    return this.saldo + cp.getSaldo();
                  }
              }
            
          

Execute a classe de Manifesto e obtenha como saída a classe de Teste automatizado implementada:

            
              public class ContaAplicacaoTest {

                ...
                
                @Test
                public void somaValoresDasContas() {
                  ContaCorrente contacorrente = new ContaCorrente(10, 12, 100.0);

                  ContaPoupanca contapoupanca = mock(ContaPoupanca.class);
                  when(contapoupanca.getSaldo()).thenReturn(200.0);

                  double expected = contacorrente.somaValoresDasContas(contapoupanca);
                  assertEquals(300.0, expected, 0.0001);
                }
              }
            
          

Este framework tem como dependência os frameworks JUnit e Mockito.

3. Documentação

Você pode encontrar um projeto de exemplo neste link: https://github.com/rgoncalves94/jtcgen-example

A utilização dos recursos oferecidos pelo framework foram desenvolvidos pensando nos cenários dos casos de teste.

Ao criar um teste automatizado precisamos pensar em um cenário, inicializar os objetos e variaveis necessários para que a ação à ser testada seja executada de maneira devida, para, que por fim, possamos comparar o resultado desta com o o valor de saída correspondente àquele cenário.

Este framework implementa algumas annotations para serem declaradas dentro da sua "classe de produção", onde sera informada como texto dentro delas os parametros para gerar o caso de teste invocando o método que deseja testar.

Podemos ver isso com mais clareza nos próximos exemplos:

3.1. Utilização

Para casos de testes simples, o framework fornece algumas annotations com uma sintaxe mais textual, pois trabalha apenas com variaveis escalares (tipo primitivos). Vejamos alguns exemplos à seguir:

            
              @JTCGen
              public class Descontos {
                
                ... // Atributos
                
                @SetUp({"100.0", "100.0", "100.0","100.0"})
                public Descontos(double vt, double am, double ad, double adiant) {
                  this.valeTransporte = vt;
                  this.assistMedica = am;
                  this.assistDental = ad;
                  this.adiantamento = adiant;
                }

                ... //Getters and Setters
                
                @TestEquals({"", "400.0"})
                public double obtemDescontosSomados() {
                  return this.adiantamento + this.assistDental + this.assistMedica + this.valeTransporte;
                }
                
              }
            
          

Ao executar o framework, temos como resultado o caso de teste:

            
              public class DescontosTest {

                private Descontos instance;

                @Before
                public void setUp() throws Exception {
                  this.instance = new Descontos(100.0,100.0,100.0,100.0);
                }

                @After
                public void tearDown() throws Exception {
                  this.instance = null;
                }

                @Test
                public void obtemDescontosSomados() {
                  double resultado = this.instance.obtemDescontosSomados();
                  assertEquals(400.0, resultado, 0.00001);
                }
                
              }
            
          

Abaixo, temos algumas caracteristicas dos recursos e do funcionamento do framework:

3.1.1 Annotation de permissão

Importante Para que o framework identifique que você deseja gerar um caso de teste para a mesma, você precisa anota-la com @JTCGen.

            
                  @JTCGen
                  public class Descontos { 
                    ...
                  }
            
          

A função dela é simplesmente deixar a classe visível ao framework. Caso não queira que mais que o framework gere casos de testes para ela, apenas remova ou comente a annotation.

3.1.2 Declarando dados de inicialização do Método Construtor

O framework gera casos de teste no padrão que o JUnit implementa. Com a annotation SetUp é criado o método SetUp do JUnit, onde realiza os passos iniciais do teste automatizado.

Ao anotar esta marcação no seu método construtor é necessário informar respectivamente os valores que serão utilizados como parâmetros quando for instanciada a classe no caso de teste.

O exemplo abaixo inicializa a classe adicionando double 100.0 para cada argumento presente na assinatura do método, respectivamente.

            
                  ...
                  @SetUp({"100.0", "100.0", "100.0","100.0"})
                  public Descontos(double vt, double am, double ad, double adiant) {
                    this.valeTransporte = vt;
                    this.assistMedica = am;
                    this.assistDental = ad;
                    this.adiantamento = adiant;
                  }
                  ...
            
          

Se torna uso obrigatório caso a sua classe possua um método construtor com valores iniciais.

3.1.3 Declarando Casos de Testes

Para gerar casos de testes, existem algumas annotations capazes de executar apenas um dos métodos assert do JUnit. Abaixo o exemplo:

            
                  ...
                  @TestEquals({"", "400.0"})
                  public double obtemDescontosSomados() {
                    return this.adiantamento + this.assistDental + this.assistMedica + this.valeTransporte;
                  }
            
          

A annotation @TestEquals implementa o método "assertEquals" do Junit ela requer dois parâmeros:

  • 1º - Valores que serão assumidos nos argumentos, separados por ponto e virgula;
  • 2º - Valor esperado como retorno quando invocado o método com os valores passados via parâmetro

Observação: Caso tenha utilizado a annotation @SetUp no método construtor, tenha em mente que os dados inicializados podem alterar o resultado final da execução. Pois então, os considere nos resultados esperados.

Outros exemplos:

            
                  ...
                  @TestEquals({"150.05", "400.0"})
                  public double obtemDescontosSomados(double outrosDescontos) {
                    return this.adiantamento + this.assistDental + this.assistMedica + this.valeTransporte;
                  }

                  ...
                  /**
                   * Passe os vários parametros separados por ponto e virgula
                   */
                  @TestEquals({"150.05;0.5", "400.0"})
                  public double obtemDescontos(double outrosDescontos, double percAuxEmpresa) {
                    return (
                      this.adiantamento + this.assistDental + this.assistMedica + this.valeTransporte
                    ) * percAuxEmpresa;
                  }

                  ...
                  /**
                   * Esta annotation apenas necessita da declaração dos parâmetros, pois o retorno esperado é true.
                   */
                  @TestTrue({"500.5"})
                  public boolean descontosPositivos(double outrosDescontos) {
                    return (
                      this.adiantamento + this.assistDental + this.assistMedica + this.valeTransporte
                    ) >= 0;
                  }
            
          

3.1.4 Criando Casos de Testes mais complexos (utilizando tipos abstratos e mock object)

Para que fosse possível gerar casos de testes automatizados que implementasse tipos abstratos de dados e também a possibilidade de criar objetos dubles (mock objects) foi criada uma unica annotation onde seria responsável por interpretar scripts de execução.

Neste caso foi utilizado uma API chamada Nashorn. Ela é um motor da linguagem JavaScript integrado a versão Java 8. Ela é utilizada dentro deste projeto para facilitar a escrita dos casos de teste, deixando menos complexa a declaração de tipos abstratos.

Abaixo, temos um exemplo completo para que possamos entender melhor:

          
            @JTCGen
            public class Funcionario {
              
              ... // Atributos
              
              public Funcionario(String nome, double salarioBase, TipoContratacao tipoContrato) {
                this.nome = nome;
                this.salarioBase = salarioBase;
                this.tipoContrato = tipoContrato;
              }
              
              /**
               * Calcula descontos do vale transporte
               * */
              @Test("setup(['User', 5000.0, {c:'TipoContratacao@getSigla()@getNome()', v:['CLT', 'Contrato']}]).parameter().eq(250.0)")
              public double calculaDescontoVT() {
                
                double percentualDesconto = 0.0;
                
                if(this.salarioBase > 2000.0 && this.tipoContrato.getSigla().equals("CLT") 
                    && this.tipoContrato.getNome().equals("Contrato")){
                  percentualDesconto = 0.05;
                }
                
                return this.salarioBase * percentualDesconto;
              }
              
              ...
            }

          
        

Nesta abordagem, é necessário declarar a execução de alguns scripts que o framework fornece desenvolvidos em JavaScript, para que seja gerado posteriormente o caso de teste desta maneira:

          
            @Test
            public void calculaDescontoVT() {
              TipoContratacao tipocontratacao = mock(TipoContratacao.class);
              when(tipocontratacao.getSigla()).thenReturn("CLT");

              when(tipocontratacao.getNome()).thenReturn("Contrato");

              Funcionario funcionario = new Funcionario("User", 5000.0, tipocontratacao);

              double expected = funcionario.calculaDescontoVT();
              assertEquals(250.0, expected, 0.0001);
            }
          
        

3.1.4.1. Funções disponibilizadas

Método: setup()

Este método é responsável por gerar a inicialização da classe do método que será testado.

Ele possui o mesmo fim da annotation @SetUp, mas ele possui a capacidade de processar a criação de tipos abstratos de dados, criando o mock de seu objeto e métodos.

Sua declaração deve ser correspondente ao método construtor que deseja utilizar:

setup([200.2, "Teste", {c:"TipoAbstrato@metodoASerExecutado()", v:["valor retornado"]}])

para que possa processar tipos abstratos é necessário passar um objeto JSON no padrão:

          
            {
              c: "Classe@executa()", // call
              v: "valorASerRetornado" // value            
            }
          
        

O framework compreenderá que você necessita de uma nova instancia de "Classe", e o método "executa" deve ser 'mockado' retornando o valor string "valorASerRetornado".

Método: parameter()

Este método é responsável por gerar a inicialização das classes que o método à ser testado possui dependencia para sua execução. E após isso testar o método.

A declaração dos parametros dele é identica ao método setup() explicado logo acima.

parameter([{c:"UmaClasse@method()@otherMethod()", v:[5000, 920.02]}, "testeParametro"])

Para realizar o mock de mais de um comportamento da classe declare os métodos em sequencia separados por @:

UmaClasse@method()@otherMethod().

E passe um vetor contendo os valores que serão executados, respectivamente.

[5000, 920.02]

Métodos de comparação: asserts

Para realizar a comparação com o retorno esperado do método são disponibilizadas os seguintes métodos:

  • eq('ValorDeComparação') - representa o assertEquals do JUnit
  • eqVoid('ValorDeComparação') - representa o assertEquals do JUnit com a capacidade de testar metodos void
  • isFalse() - representa o assertFalse(), não precisa de parametros de comparação.
  • isTrue() - representa o assertTrue(), não precisa de parametros de comparação.
  • isNull() - representa o assertNull(), não precisa de parametros de comparação.
  • isNotNull() - representa o assertNotNull(), não precisa de parametros de comparação.