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 ).
-
Modele e implemente as classes dos cromos ( Card ) e da caderneta ( Album ) (pode utilizar classes do pacote de colecções do Java).
-
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.