Tópicos

  • Escrita de testes JUnit

Breve Introdução à Escrita de Testes

Quando se produz um programa, é importante garantir que funciona de acordo com as especificações. É, no entanto, difícil de dar esta garantia, especialmente em programas complexos. Com o propósito de simplificar a tarefa, os testes podem ser realizados modularmente, i.e., testanto a aplicação por partes. Assim, se todos os módulos se comportam individualmente segundo as especificações, é mais simples de produzir testes que avaliem o comportamento da aplicação como um todo.

JUnit é uma biblioteca que simplifica e sistematiza a produção de testes para cada um dos componentes (as unidades) da aplicação. Embora seja uma biblioteca Java, o princípio que materializa é geral e, de facto, existem versões para outras linguagens (cppunit, por exemplo, para C++; phpunit, para PHP, etc.).

Na prática, os testes executam como asserções sobre expectativas, i.e., cada bateria de teste ou teste individual verifica se uma dada propriedade é verificada pelas instâncias das classes em teste (ou pelos resultados das operações sobre essas instâncias). As propriedades podem ser booleanas (verdadeiro/falso), igualdade, etc.

Considere-se o exemplo dos blobs (classe Blob) e uma possível classe de teste (classe BlobTest) utilizando JUnit 3.8.x.

A classe Blob define o método equals, que deve apresentar as propriedades habituais ( a.equals(b) => b.equals(a), etc.). Define ainda o método blob, descrevendo a semântica da interacção entre blobs.

public class Blob {
 int _radius;
 int _weight;

 public Blob(int r, int w) {
   _radius = r;
   _weight = w;
 }

 public int getRadius() { return _radius; }
 public int getWeight() { return _weight; }

 public boolean equals(Blob b) {
   if (b == null) return false;
   return _radius == b.getRadius() &&
          _weight == b.getWeight();
 }

 public boolean equals(Object o) {
   return o instanceof Blob && equals((Blob)o);
 }

 public Blob blob(Blob b) {
   if (_radius < b.getRadius() || _weight < b.getWeight())
     return this;
   return new Blob(_radius + b.getRadius(),
                   _weight + b.getWeight());
 }

}

A classe de teste ( BlobTest) é definida sobre as propriedades esperadas para o método equals ( testEquals) e sobre o funcionamento do método blob ( testBlobbiness). O método suite tem uma função semelhante ao da função main, permitindo agrupar vários testes para execução conjunta.

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class BlobTest extends TestCase {

 private Blob _smallBlob;
 private Blob _largeBlob;

 public BlobTest() {}
 public BlobTest(String s) { super(s); }

 protected void setUp() {
   _smallBlob = new Blob(1, 2);
   _largeBlob = new Blob(100, 200);
 }

 public void testEquals() {
   Assert.assertTrue(!_smallBlob.equals(null));
   Assert.assertTrue(!_largeBlob.equals(null));
   Assert.assertEquals(_smallBlob, new Blob(1, 2));
   Assert.assertEquals(_largeBlob, new Blob(100, 200));
   Assert.assertFalse(_smallBlob.equals(_largeBlob));
   Assert.assertFalse(_largeBlob.equals(_smallBlob));
 }

 public void testBlobbiness() {
   Blob b1 = new Blob(101, 202);
   Blob b2 = _largeBlob.blob(_smallBlob);
   Blob b3 = _smallBlob.blob(_largeBlob);
   Assert.assertEquals(b1, b2);
   Assert.assertEquals(_smallBlob, b3);
 }

 public static Test suite() {
   TestSuite suite = new TestSuite();
   suite.addTest(new BlobTest("test equalities") {
                   protected void runTest() {
                     testEquals();
                   }
                 });
   suite.addTest(new BlobTest("test blobbiness") {
                   protected void runTest() {
                     testBlobbiness();
                   }
                 });
   suite.addTest(new BlobTest("testBlobbiness"));
   // Failure: method "smallTest" does not exist
   // suite.addTest(new BlobTest("smallTest")); 
   return suite;
 }

}  // end of BlobTest

A execução dos testes pode ser realizada pelos processos normais em Java, i.e., por recurso a um método main, ou podem ser utilizadas classes específicas para execução de testes (runners), que na forma mais simples e directa executam todos os métodos cujo nome comece por "test".

Numa classe de teste, podem ainda ser definidos os métodos setUp e tearDown que, respectivamente, preparam a execução de cada teste e executam procedimentos de limpeza após cada teste.

Compilação das classes da aplicação (Blob.java) e de teste (BlobTest.java):

  • javac Blob.java
  • javac -cp path/to/file/junit.jar:. BlobTest.java

Execução dos testes, assumindo o uso da interface de texto ( textui ):

  • java -cp path/to/file/junit.jar:. junit.textui.TestRunner BlobTest

Exercício 1: Caderneta e Cromos

Considere uma caderneta de cromos. Os cromos têm um número e uma imagem. Não é necessário modelar a imagem, considere a imagem como sendo uma instância da seguinte classe:

class Image { /* conteúdo omitido */ }

A caderneta guarda os cromos pela ordem de numeração e não permite guardar cromos repetidos. É possível adicionar cromos a uma caderneta (método add ) e é possível remover um cromo se for indicado o seu número (método remove ). Duas cadernetas dizem-se iguais ( equals ) se tiverem o mesmo número de cromos (independentemente das características dos cromos individuais). É possível obter uma lista ordenada (por número) contendo os cromos de uma caderneta (método getAll ).

  1. Modele e implemente as classes dos cromos ( Card ) e da caderneta ( Album ) (pode utilizar classes do pacote de colecções do Java).

  2. Escreva uma classe de teste que contenha um teste

    • que verifique se a inserção de um cromo na caderneta funciona; e outro

    • que verifique as propriedades associadas ao método equals .

[ resolução: procurar resolver antes de consultar ]

Exercício 2: 1001 Noites (Arabian Nights)

Realização de testes sobre o mesmo enunciado da Aula Prática 04 ("Arabian Nights"), considerando-se as seguintes alterações:

Os métodos grantWish não retornam qualquer valor ( void ), mas lançam excepções (especializações de SomethingWrongWithGenieException):

  • NoMoreWishesException (no caso de um FriendlyGenie já ter realizado todos os desejos);
  • BadMoodException (no caso de um GrumpyGenie já ter realizado o primeiro desejo);
  • OopsException (lançada sempre que um RecyclableDemon realiza um desejo); e
  • TiredOhSoTiredException (no caso de um RecyclableDemon já ter sido reciclado).

As classes de teste devem pertencer ao pacote (package) arabiannights.tests e devem verificar o correcto comportamento dos génios.

O JUnit a utilizar é o da versão 3.8.x.