Como usar JUnit + JMock no mundo real?

Salve, pessoas!

Faz um tempo que recebi a tarefa de incorporar os testes unitários aqui na empresa.

Para tal fim, adotei JUnit + JMock. Aprendi como funcionam e tentei implementá-los usando classes reais.

Aqui começa minha jornada: estou com muita dificuldade para elaborar os testes pois nenhum dos exemplos que encontrei foi além do trivial. :?

Preciso de alguém com experiência em desenvolvimento de casos de teste para me dar uns exemplos reais de implementação.

I’ll apreciate the help. :wink:

Olá Bruno!

Acho que a sua dificuldade está sendo em entender pra que servem os testes unitários. Dê uma lida em TDD (Test Driven Development) na net. Pode ser na wikipedia em português mesmo. Aí vc vai entender melhor o que é bom testar. Pra começar, procure pensar no teste como um exemplo de utilização do código de produção. O que cada método tem que devolver quando é chamado com determinados parâmetros?

ps. tente primeiro começar com o JUnit (vc tá usando o 4 né?), depois pega o JMock (que será útil também, mas primeiro o JUnit).

flw!

Não, não.

Já entendi muito bem como funcionam.

Meu problema é relacionar teoria com prática, por isso preciso de um exemplo: para entender os padrões, para saber como os PRO’s fazem.

Como seria implementado um testCase para a classe abaixo, p.e.:

public CommandResponse execute(HttpServletRequest request) throws ServletException {
	try {
		TestaTiquete tt = new TestaTiquete();
		if (!tt.isTiqueteOK(request, request.getSession()))
			throw new Exception("Sessão já encerrada. Por favor, faça um novo acesso.") ;
	} catch (Exception e) {
		e.printStackTrace();
	}
}

Considerando que você já estudou sobre os conceitos do teste unitário, é hora de passar a compreender o seguinte: como desenvolver classes testáveis.

Infelizmente nem sempre é possível implementar testes unitários em sistemas existente, porque eles não foram desenvolvidos visando a testabilidade.
Pode ser isso o que está te deixando “empacado”, sem saber por onde começar.

Veja um exemplo simples de código não testável - ou seja, saindo do mundo ideal dos exemplos e entrando no perigoso mundo das aplicações legadas :frowning:

BigDecimal calcularValorDivida(long codigoCliente) {
     DividaDao dividaDao = new DividaDaoImpl();
     BigDecimal valorPrincipal = dividaDao.obterValorDividaPorCliente(codigoCliente);

     JurosService jurosService = new InitialContext().lookup("java:/servicos/JurosService");
     BigDecimal taxaJuros = jurosService.obterTaxaJurosVigente();

     BigDecimal dividaTotal = /* aplica formula de calculo de juros sobre a divida principal */
     return dividaTotal;
}

Não há como fazer testes unitários desse jeito, porque simplesmente não existe uma idéia de “unidade”. É tudo um bolo só, tudo muito acoplado, a fórmula de cálculo (que é o coração desse método, e que provavelmente seria o maior ponto de atenção dos testes unitários) não pode ser testada isoladamente.

Por outro lado, se o desenvolvedor criasse esse código pensando em testabilidade, seria algo mais parecido com isso:

class CalculoDivida {

private DividaDao dividaDao;
private JurosService jurosService;

BigDecimal calcularValorDivida(long codigoCliente) {
     BigDecimal valorPrincipal = dividaDao.obterValorDividaPorCliente(codigoCliente);

     BigDecimal taxaJuros = jurosService.obterTaxaJurosVigente();

     BigDecimal dividaTotal = /* aplica formula de calculo de juros sobre a divida principal */
     return dividaTotal;
}

/* Restante da classe */

Agora melhorou… O Dao e o serviço externo se tornaram dependências que deverão ser injetadas. Nosso método de regra de negócio is free! DividaDao e JurosService são interfaces, o que significa que podem ser mockados.

Para terminar, vamos ver nossa classe de teste unitário (aqui estamos vendo apenas um caso de teste, crie quantos forem necessários para cobrir todas as situações possíveis!)

public void testCalculoValorDivida() {
     CalculoDivida calc = new CalculoDivida();

     // Usando o JMock para simular um DAO que retorne valor principal de 1000.00
     DividaDao dividaDao = mock(DividaDao.class)
               .expects(once())
               .method("obterValorDividaPorCliente")
               .with(eq(1L))
               .will(returnValue(new BigDecimal("1000.00")));
     calc.setDividaDao(dividaDao);

     // O JurosService vai retornar taxa de 1%
     JurosService jurosService = mock(JurosService.class)
               .expects(once())
               .method("obterTaxaJurosVigente")
               .will(returnValue(new BigDecimal("0.01"))); // 0.01 == 1.0%
     calc.setJurosService(jurosService);

     BigDecimal valorCalculado = calc.calcularValorDivida(1L);

     // Aqui vc usa o seu conhecimento do negocio para calcular o valor previsto no resultado.
     // Como todas as variaveis estao sob controle, basta verificar se tudo deu certo.
     assertEquals(new BigDecimal("1025.87"), valorCalculado);
}

OBS: Fiz a sintaxe do jmock aqui de cabeça, não ligue se tiver algo errado!

ah tá, agora entendi onde vc queria chegar.

Kra, não me leva a mal, eu posso estar errado, mas o problema nesse caso que vc postou aí encima não é nos testes: é na classe de produção. Eu não sei o que o método isTiqueteOK faz exatamente, mas, a princípio, ele deveria ficar acoplado só com classes do seu domínio, não com o request. Testar desse jeito que vc colocou aí vai dar um trampo alucinado, pois vc teria que fazer um mock de uma classe de request e passar os parâmetros na munheca.
Trocando em miúdos: rola criar um POJO com o que vem pelo request? Se puder, cria o tal POJO e faz o mock dele (do POJO) ao invés do request.

Se eu entendi direito, o seu código de teste ia ficar assim:

pojoObject = SeuObjectDePOJO();
pojoObject.setParametro1QueTaNoRequestAgora(valor);
// outros parâmetros do POJO...

TestaTiquete tt = new TestaTiquete();
assertTrue(tt.isTicketOk(pojoObject));

t+

Neste exemplo que vc citou…

Quando vc instancia dentro do método, vc está deixando o código fortemente acoplado… seria bom (se possivel) conectar as camadas usando um container IOC, como o spring.

Ex: No código abaixo, se a variavel tt fosse injetada pelo Spring, você poderia mocka-la.

public class ClasseQualquer {
  private TestaTiquete tt;//injetada por algum container ioc
  public CommandResponse execute(HttpServletRequest request) throws ServletException {  
      try {  
          if (!tt.isTiqueteOK(request, request.getSession()))  
              throw new Exception("Sessão já encerrada. Por favor, faça um novo acesso.") ;  
      } catch (Exception e) {  
          e.printStackTrace();  
      }  
  }
  public setTt(TestaTiquete tt) {
    this.tt = tt;
  }
}  

O teste ficaria algo do tipo:

@Test
public void testClasseQualquer{
  ClasseQualquer c = new ClassQualquer();
  c.setTt(Mockito.mock(TestaTiquete.class));
  //etc...
}

Depois vc ia ter que mockar o metodo isTiqueteOK, o httpsequest e o httpsession, etc…

Resumindo… ia dar um trabalhinho… recomendo usar o vraptor 3 pq já abstrai boa parte pra vc…

Esse caso complica um pouquinho, porque está em uma classe que trabalha muito com objetos de um framework (Servlets).

O que se costuma fazer nesses casos é utilizar libs de teste auxiliares para criar os objetos “fake” necessários.
Além disso, daria essas dicas:

  • É melhor testar unitariamente a classe TestaTiquete (ao invés do command), ela tem uma responsabilidade bem definida que é mais fácil de verificar.
  • Esse código possui um erro um pouco grave, se o ticket for inválido o sistema dá um printStackTrace e segue a vida normalmente… talvez esse não seja o comportamento desejado.

Por exemplo, supondo que eu tenho um framework imaginário para teste de Actions (o meu é imaginário, mas eles existem hein! Só não utilizo nenhum real porque não sei a sintaxe), poderia fazer um teste como esse:

public setUp() {
    tt = new TestaTiquete();
}
public void testVerificaTiqueteSessaoNula() {
   // Nao ha sessao aberta
   HttpServletRequest request = FakeRequestFactory.createHttpRequest();
   HttpSession session = null;

   boolean result = tt.isTiqueteOK(request, sessao);
   assertFalse(result);
}
public void testVerificaTiqueteNumTiqueteNulo() {
   // Existe uma sessao, mas nao possui o atributo tiquete
   HttpServletRequest request = FakeRequestFactory.createHttpRequest();
   HttpSession session = FakeRequestFactory.createHttpSession();
   session.setAttribute("tiquete", null);

   boolean result = tt.isTiqueteOK(request, sessao);
   assertFalse(result);
}
public void testVerificaTiqueteOk() {
   // Existe uma sessao e o atributo tiquete esta presente
   HttpServletRequest request = FakeRequestFactory.createHttpRequest();
   HttpSession session = FakeRequestFactory.createHttpSession();
   session.setAttribute("tiquete", "1A22CVSA");

   boolean result = tt.isTiqueteOK(request, sessao);
   assertTrue(result);
}

Só um exemplo… evidentemente os cenários testados vão depender de como funciona a classe sob teste.

Obrigado pela ajuda, fellas.

Guardarei esse post no favoritos com muito carinho. :slight_smile:

Hoje, que resolvi pegar pra fazer mesmo, entendi o significado de ‘não-testável’.

O método que tenho que testar tem simplesmente 150 linhas de código entre métodos de outras classes, if’s e try-catch’s. :shock:

Tenho certeza de que tem alguma coisa errada. É simplesmente ignorância demais mockar e programar comportamentos de mais de 15 classes. :?

Creio que a aproximação mais saudável seria testar somente os métodos chamados dentro dele.

Sou só eu ou vocês se deparam com esse tipo de coisa também? Como resolvem esse salcifufu? :lol:

Bruno,

estou com o mesmo problema que o seu.

Já estudei tudo que podia sobre JUnit e Mock’s, aprendi pra que ambos servem, etc e talz.

O problema é ter a lógica pra poder gerar o test.

Com esses posts já consegui adquirir mais algum conhecimento.

Outra coisa importante é que testar classes de sistemas prontos dá muito trabalho já que elas não foram desenvolvidas pensando em testes.

Tenho um sistema aqui na empresa dessa maneira.

Abraço.

Mas então, progrediu alguma coisa?

Eu realmente gostaria de um help nesse assunto. :lol:

Galera, o ponto não é desenvolver classes “pensando em testes”, mas sim desenvolver classes coesas, com baixo grau de acoplamento. Leiam esses tópicos na wikipedia (desacoplamento e coesão) e vcs vão entender melhor onde eu quero chegar.

Regra básica do desenvolvimento corporativo. Seria bom se a seguissem.

Eu vou um pouco além: regra básica pra qualquer desenvolvimento.