Detalhamento de um Framework Horizontal: JUNIT

Objetivos

Um exemplo do uso do framework

Exemplo do uso de JUNIT

/*
 * Desenvolvido para a disciplina Programacao 1
 * Curso de Bacharelado em Ciência da Computação
 * Departamento de Sistemas e Computação
 * Universidade Federal da Paraíba
 *
 * Copyright (C) 1999 Universidade Federal da Paraíba.
 * Não redistribuir sem permissão.
 */

package p1.aplic.cartas;

import java.util.*;
import java.lang.Math.*;

/**
 * Um baralho comum de cartas.
 * Num baralho comum, tem 52 cartas:
 * 13 valores (AS, 2, 3, ..., 10, valete, dama, rei)
 * de 4 naipes (ouros, espadas, copas, paus).
 *
 * @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
 * @version 1.1
 * <br>
 * Copyright (C) 1999 Universidade Federal da Paraíba.
 */
public class Baralho {
  /**
   * O baralho é armazenado aqui.
   * É protected porque alguns baralhos subclasses dessa classe
   * poderão talvez ter que mexer diretamente aqui
   * para construir baralhos especiais.
   */
  protected Vector baralho;

  /**
   * Construtor de um baralho comum.
   */
  public Baralho() {
    // Usa um Vector para ter um iterador facilmente
    baralho = new Vector();
    // enche o baralho
    for(int valor = menorValor(); valor <= maiorValor(); valor++) {
      for(int naipe = primeiroNaipe(); naipe <= últimoNaipe(); naipe++) {
        // chama criaCarta e não "new" para poder fazer override
        // de criaCarta em baralhos de subclasses e
        // criar classes diferentes.
        baralho.add(criaCarta(valor, naipe));
      }
    }
  }

  /**
   * Cria uma carta para este baralho.
   * @param valor O valor da carta a criar.
   * @param naipe O naipe da carta a criar.
   * @return A carta criada.
   */
  protected Carta criaCarta(int valor, int naipe) {
    return new Carta(valor, naipe);
  }

  /**
   * Recupera o valor da menor carta possível deste baralho.
   * É possível fazer um laço de menorValor() até maiorValor()
   * para varrer todos os valores possíveis de cartas.
   * @return O menor valor.
   */
  public int menorValor() {
    return Carta.menorValor();
  }

  /**
   * Recupera o valor da maior carta possível deste baralho.
   * É possível fazer um laço de menorValor() até maiorValor()
   * para varrer todos os valores possíveis de cartas.
   * @return O maior valor.
   */
  public int maiorValor() {
    return Carta.maiorValor();
  }

  /**
   * Recupera o "primeiro naipe" das cartas que podem estar
   * no baralho.
   * Ser "primeiro naipe" não significa muita coisa,
   * já que naipes não tem valor
   * (um naipe não é menor ou maior que o outro).
   * Fala-se de "primeiro naipe" e "último naipe" para poder
   * fazer um laço de primeiroNaipe() até últimoNaipe() para varrer
   * todos os naipes possíveis de cartas.
   * @return O primeiro naipe.
   */
  public int primeiroNaipe() {
    return Carta.primeiroNaipe();
  }

  /**
   * Recupera o "último naipe" das cartas que podem estar
   * no baralho.
   * Ser "último naipe" não significa muita coisa,
   * já que naipes não tem valor
   * (um naipe não é menor ou maior que o outro).
   * Fala-se de "primeiro naipe" e "último naipe" para poder
   * fazer um laço de primeiroNaipe() até últimoNaipe() para varrer
   * todos os naipes possíveis de cartas.
   * @return O primeiro naipe.
   */
  public int últimoNaipe() {
    return Carta.últimoNaipe();
  }

  /**
   * Recupera o número de cartas atualmente no baralho.
   * @return O número de cartas no baralho.
   */
  public int númeroDeCartas() {
    return baralho.size();
  }
  
  /**
   * Recupera um iterador para poder varrer todas
   * as cartas do baralho.
   * @return Um iterador do baralho.
   */
  public Iterator iterator() {
    return baralho.iterator();
  }

  /**
   * Baralha (traça) o baralho.
   */
  public void baralhar() {
    int posição;
    for(posição = 0; posição < númeroDeCartas() - 1; posição++) {
      // escolhe uma posição aleatória entre posição e númeroDeCartas()-1
      int posAleatória = posição +
                         (int)((númeroDeCartas() - posição) *
                               Math.random());
      // troca as cartas em posição e posAleatória
      Carta temp = (Carta)baralho.get(posição);
      baralho.set(posição, baralho.get(posAleatória));
      baralho.set(posAleatória, temp);
    }
  }

    /**
     * Retira uma carta do topo do baralho e a retorna.
     * A carta é removida do baralho.
     * @return A carta retirada do baralho.
     */
  public Carta pegaCarta() {
    if(númeroDeCartas() == 0) return null;
    return (Carta)baralho.remove(númeroDeCartas()-1);
  }
}
package p1.aplic.cartastestes;

import junit.framework.*;
import p1.aplic.cartas.*;
import java.util.*;

/**
 * Testes da classe Baralho.
 *
 */
public class TestaBaralho extends TestCase {
  protected Baralho  b1; // fica intacto

  public TestaBaralho(String name) {
    super(name);
  }
  public static void main(String[] args) {
    junit.textui.TestRunner.run(suite());
  }
  protected void setUp() {
    b1 = new Baralho();
  }
  public static Test suite() {
    return new TestSuite(TestaBaralho.class);
  }
  public void testNúmeroDeCartas() {
    assertEquals(1, b1.menorValor());
    assertEquals(13, b1.maiorValor());
    assertEquals(52, b1.númeroDeCartas());
  }
  public void testBaralhoNovo() {
    assertTrue(baralhoEstáCompleto(b1));
  }
  public void testBaralhar() {
    Baralho b2 = new Baralho();
    b2.baralhar();
    assertTrue(baralhoEstáCompleto(b2));
  }
  public boolean baralhoEstáCompleto(Baralho b) {
    Vector cartasJáVistas = new Vector();
    Iterator it = b.iterator();
    while(it.hasNext()) {
      Carta c = (Carta)it.next();
      // vê se carta está ok
      int v = c.getValor();
      int n = c.getNaipe();
      assertTrue("Valor não ok",
                 v >= c.menorValor() && v <= c.maiorValor());
      assertTrue("Naipe não ok",
                 n >= c.primeiroNaipe() && n <= c.últimoNaipe());
      assertTrue("Carta já vista",
                 !cartasJáVistas.contains(c));
      cartasJáVistas.add(c);
    }
    return cartasJáVistas.size() == 52;
  }
  public void testPegaCarta() {
    Vector cartasJáVistas = new Vector();
    Baralho b3 = new Baralho();
    Carta c;
    while((c = b3.pegaCarta()) != null) {
      // vê se carta está ok
      int v = c.getValor();
      int n = c.getNaipe();
      assertTrue("Valor não ok",
                 v >= c.menorValor() && v <= c.maiorValor());
      assertTrue("Naipe não ok",
                 n >= c.primeiroNaipe() && n <= c.últimoNaipe());
      assertTrue("Carta já vista",
                 !cartasJáVistas.contains(c));
      cartasJáVistas.add(c);
    }
    assertEquals("Baralho não vazio", 0, b3.númeroDeCartas());
  }
}

wpe9D.jpg (33288 bytes)

O projeto de JUNIT

O início: TestCase

public abstract class TestCase implements Test { 
  ...
}
public abstract class TestCase implements Test { 
  private final String fName; 
  public TestCase(String name) { 
    fName= name; 
  } 

  public abstract void run(); 
  ...
}

tour1.gif (1820 bytes)

Completando a informação: run()

public abstract class TestCase implements Test { 
  // ...
  public void run() { 
    setUp(); 
    runTest(); 
    tearDown(); 
  }
}
public abstract class TestCase implements Test { 
  // ...
  protected void runTest() {
    // na realidade, o framework oferece uma implementação default
    // que veremos abaixo
  }

  protected void setUp() { // hook method
  } 

  protected void tearDown() { // hook method
  }
}

tour2.gif (2490 bytes)

Obtendo resultados dos testes: TestResult

public class TestResult { 
  protected int fRunTests; 
  public TestResult() { 
    fRunTests= 0; 
  } 
}
public abstract class TestCase implements Test { 
  // ...
  public void run(TestResult result) { 
    result.startTest(this);
    setUp();
    runTest();
    tearDown();
  }
}
public class TestResult { 
  public synchronized void startTest(Test test) { 
    fRunTests++; 
  }
}
public abstract class TestCase implements Test { 
  // ...
  public TestResult run() { 
    TestResult result= createResult(); 
    run(result); 
    return result; 
  } 
  protected TestResult createResult() { 
    return new TestResult(); 
  }
}

tour3.gif (2946 bytes)

public abstract class TestCase implements Test { 
  // ...
  public void run(TestResult result) { 
    result.startTest(this); 
    setUp(); 
    try { 
      runTest(); 
    } catch (AssertionFailedError e) {
      result.addFailure(this, e); 
    } catch (Throwable e) {
      result.addError(this, e); 
    } finally {
      tearDown(); 
    } 
  }
}
public abstract class TestCase implements Test { 
  // ...
  protected void assertTrue(boolean condition) { 
    if(!condition) {
      throw new AssertionFailedError();
    }
  }
}
public class TestResult { 
  // ...
  public synchronized void addError(Test test, Throwable t) { 
    fErrors.addElement(new TestFailure(test, t)); 
  }
 
  public synchronized void addFailure(Test test, Throwable t) { 
    fFailures.addElement(new TestFailure(test, t)); 
  }
}
public class TestFailure { 
  protected Test fFailedTest; 
  protected Throwable fThrownException; 
}
public abstract class MeuTeste extends TestCase { 
  // ...
  public void runTest() {
    assertEquals(1, b1.menorValor());
    assertEquals(13, b1.maiorValor());
    assertEquals(52, b1.númeroDeCartas());
  }
}

Não crie subclasses estúpidas: TestCase revisitado

public class TesteBaralhar extends TesteBaralho { 
  public TesteBaralhar() {
    super("testBaralhar");
  } 
  protected void runTest() {
    testBaralhar();
  }
}
TestCase test = new TesteBaralho("testBaralhar") { 
  protected void runTest() {
    testBaralhar();
  } 
};
public abstract class TestCase implements Test { 
  // ...
  protected void runTest() throws Throwable { 
    Method runMethod = null; 
    try { 
      runMethod = getClass().getMethod(fName, new Class[0]); 
    } catch (NoSuchMethodException e) { 
      assertTrue("Method \"" + fName + "\" not found", false); 
    } 
    try { 
      runMethod.invoke(this, new Class[0]); 
    } 
    // catch InvocationTargetException and IllegalAccessException 
  }
}

tour4.gif (3694 bytes)

Um teste ou vários testes? TestSuite

public interface Test { 
  public abstract void run(TestResult result); 
}
public class TestSuite implements Test { 
  private Vector fTests= new Vector(); 
}
public class TestSuite implements Test { 
  // ...
  public void run(TestResult result) {
    for(Iterator it = fTests.iterator(); it.hasNext(); ) { 
      Test test = (Test)it.next(); 
      test.run(result); 
    } 
  }
}
public class TestSuite implements Test { 
  // ...
  public void addTest(Test test) { 
    fTests.add(test); 
  }
}

tour5.gif (4858 bytes)

public static Test suite() { 
  TestSuite suite= new TestSuite(); 
  suite.addTest(new TestaBaralho("testNúmeroDeCartas")); 
  suite.addTest(new TestaBaralho("testBaralhoNovo")); 
  suite.addTest(new TestaBaralho("testBaralhar")); 
  suite.addTest(new TestaBaralho("testPegaCarta")); 
}
public static Test suite() {
  return new TestSuite(TestaBaralho.class);
}

Resumo

tour6.gif (8950 bytes)

tour7.gif (5295 bytes)

junit1.gif (8918 bytes)

  1. O framework chama o método estático suite() da classe de teste
  2. A classe de testes cria um novo TestSuite, passando a classe de testes como parâmetro
  3. O construtor de TestSuite usa reflexão para descobrir os métodos de testes da classe de testes e instancia um objeto da classe de testes para cada método de teste
  4. O framework chama run() para cada objeto instanciado
  5. setUp() é chamado para criar um contexto para o teste
  6. O método particular testXpto() é chamado
  7. O método de teste pode fazer uma ou mais asserções
  8. Uma asserção poderá estar falsa, em cujo caso uma exceção é lançada ...
  9. ... e é capturada pelo framework, que então exibe o erro na interface do usuário (gráfica ou textual)

junit3.gif (20802 bytes)

Observações gerais

Por que JUNIT é um framework?

frame-5 programa anterior próxima