Detalhamento de um Framework Horizontal: JUNIT

Documentos relacionados
" ##$#$!% # & #$#$!!! "!!

Casos Notáveis. Forças. Problema. Contexto

JUnit: framework de testes unitários. Fred Lopes

Arquitectura de Sistemas de Software

Desenho com Padrões: Exemplo de desenho da framework de testes JUnit

Moldura de Objectos. Moldura de Objectos Junit em UML

Testes com JUnit. Treinamento ALESP SPL. Danilo Toshiaki Sato.

Padrões de Projeto. Padrões de Projeto. Além dos 23 Padrões GoF. Os 23 Padrões de Projeto. Documentação de um Padrão. Classificação dos Padrões

Singleton e Adapter. Professor: Nazareno Andrade (baseado no material de Hyggo Almeida e Jacques Sauvé)

JUnit. Alexandre Menezes Silva Eduardo Manuel de Freitas Jorge

Visitor. Um problema a resolver. Temos uma hierarquia de classes, provavelmente um Composite Exemplo: Numa rede elétrica, temos a seguinte hierarquia:

LEIC-T LERC MEIC-T 2011/2012 1º Semestre Programação com Objetos 2012/01/07 11h00m 3/10

Computação II Orientação a Objetos

Arquitectura de Sistemas de Software Mestrado em Engenharia Informática Licenciatura em Engenharia Informática e Computação

Testes de performance JUnitPerf

Abstract Factory. Prover uma interface para criar uma família de objetos relacionados ou dependentes sem especificar suas classes concretas

Notas de Aula 09: Tratamento de exceções

Classes e Objetos. Sintaxe de classe em Java

Herança. Prof. Fernando V. Paulovich 23 de agosto de 2010

Programação com Objectos 2º Teste Tipo 1º Semestre (120 minutos)

Tratamento de Exceção. Programação Orientada a Objetos Java (Rone Ilídio)

Iteradores. Iteradores. Isabel Harb Manssour. Roteiro. Coleções

TRATAMENTO DE EXCEÇÕES

Tópicos da Aula. POO e Padrões de Projetos. Considere três classes... Reuso de Classes. Locadora de DVD. Sistema Acadêmico

Iterator. Professor: Hyggo Almeida

Classes e Objetos. Prof. Fernando V. Paulovich 9 de agosto de 2010

Interfaces e Classes Internas

Segunda Parte (3 valores) Primeira Parte (7 valores) Nome: Número: PERGUNTA NOTA PERGUNTA RESPOSTA

Composite. Pronunciação americana: compósit Pronunciação canadense (Britânica): cómposit

Recapitulando. Construtores: (Overload assinatura) public Circle() {...} public Circle(double x, double y, double r) {... }

Aula 17: Estudo de Caso: JUnit

Análise e Projeto Orientados por Objetos

Tratamento de Erros. Sérgio Luiz Ruivace Cerqueira

Computação II Orientação a Objetos

Java para Desktop. Programação Orientada à Objetos 2 JSE

Padrões Fábrica. Simple Factory Factory Method

Computação II Orientação a Objetos

Singleton. Como a maioria dos programadores organizaria o código para acessar informação de configuração? Eis um exemplo:

Linguagem de Programação II Implementação

Programação Orientada a Objetos

Observer. Problema. Objetivo

Palavras Reservadas da Linguagem Java

Programação Orientada a Objetos. Vagner Luz do Carmo - Vluzrmos

Testes Unitários com JUnit

Linguagem de Programação Orientada a Objeto Polimorfismo, Classes Abstractas e Interfaces

Herança e Polimorfismo

PROGRAMAÇÃO ORIENTADA A OBJETOS: OCULTAR INFORMAÇÕES E ENCAPSULAMENTO

Tratando exceções em Java

Computação II Orientação a Objetos

Teste Automatizado POO. Prof. Marcio Delamaro

Classes e Objetos POO

Programação com Objectos. 2º Teste 2015/2016 1º Semestre

Classes o Objetos. Classes, objetos, métodos e variáveis de instância

Programação Orientada a Objectos - P. Prata, P. Fazendeiro

Computação II Orientação a Objetos

Nome: Número: Segunda Parte (3 valores) Primeira Parte (7 valores)

Programação Orientada a Objectos - P. Prata, P. Fazendeiro

LEIC-A / MEIC-A 2007/2008 (1º

Tratamento de Exceções

Objetos Dublês. Mariana Bravo AgilCoop Cursos de Verão 2009

INF1636 PROGRAMAÇÃO ORIENTADA A OBJETOS

Engenharia de Software Aula 21. Revisão da Prova 2. Eduardo Figueiredo.

Programação Orientada a Objectos - P. Prata, P. Fazendeiro

Programação Orientada a Objetos SANTOS, Rafael (PLT)

Programação Orientada a Objetos JAVA - NETBEANS

Programação Orientada a Objectos - P. Prata, P. Fazendeiro

INF1337 LINGUAGEM DE PROGRAMAÇÃO ORIENTADA A OBJETOS

Quando um programa viola as restrições semânticas da linguagem, a JVM assinala um erro ao programa, sob a forma de exceção.

Tratamento de Exceções cont. Profa. Thienne Johnson EACH/USP

Tratamento de Exceções. LPG II Java. Tratamento de Exceções. Conceito de Exceções. Exemplo

Conceitos de Programação Orientada a Objetos

Universidade Federal de Uberlândia Faculdade de Computação Programação Orientada a Objetos II Prof. Fabiano Dorça. Padrão Observer (Observador)

Ex: carro_desportivo poderá ser uma subclasse de automóvel (carro_desportivo é_um automóvel)

PROGRAMAÇÃO ORIENTADA A OBJETOS -TRATAMENTO DE EXCEÇÕES. Prof. Angelo Augusto Frozza, M.Sc.

Tratamento de Exceções. Profa. Thienne Johnson EACH/USP

Linguagem de Programação II Implementação

Tipos, Literais, Operadores

Tipos, Literais, Operadores

Apêndice A. Alguns construtores e métodos importantes e úteis da classe Vector são:

Lição 11 Herança, polimorfismo e interfaces

Padrões de Design. Padrões de Design. Abstract Factory. Padrões de Design. Padrões de Design Abstract Factory. Abstract Factory.

Interfaces POO. Prof. Marcio Delamaro

Threads. O que é uma Thread? Paralelismo

Implementando classes em C# Curso Técnico Integrado em Informática Fundamentos de Programação

Análise e Projeto Orientados por Objetos

Análise de Programação

JUNIT 28/03/2011. Márcio Delamaro Harry Trinta

Programação I Curso: Sistemas de Informação. Prof.: José Ronaldo Leles Júnior .:

Paradigmas de Programação. Java First-Tier: Aplicações. Orientação a Objetos em Java (I) Nomenclatura. Paradigma OO. Nomenclatura

Programação Java. Tratamento de Exceções

Interfaces. Universidade Católica de Pernambuco Ciência da Computação. Prof. Márcio Bueno.

nome = n; cargo = c; salario = s; public void print() { System.out.println(nome cargo salario); public void aumento( double fator){

Classe Abstrata e Interface

p Imagine que um Sistema de Controle do Banco pode ser acessado, além dos Gerentes, pelos Diretores do Banco

Múltiplas Linhas de Execução Java (Threads)

Programação com Objectos Teste Teórico (repescagem) 24 de Janeiro de 2009, 09:00 (120 minutos)

4 Diretrizes para o Projeto e Implementação de Frameworks usando Orientação a Aspectos

Transcrição:

Detalhamento de um Framework Horizontal: JUNIT Material baseado no artigo A Cook's Tour Ver também o artigo Test Infected: Programmers Love Writing Tests Um excelente exemplo da aplicação de vários Design Patterns Command, Template Method, Collecting Parameter, Factory Method, Iterator, Adapter, Pluggable Selector, Composite Objetivos Desenvolver um framework que programadores de fato usarão para escrever testes de unidade Testes de unidade são testes de classes individuais Exigir o mínimo do programador Evitar duplicação de esforços ao escrever testes Permitir escrever testes que retenham seu valor ao longo do tempo Um exemplo do uso do framework Para testar uma classe, criamos uma classe de testes correspondente O framework JUNIT nos ajuda a criar essa classe de testes O mínimo que se deve fazer é o seguinte: Herdar da classe TestCase do framework Fornecer um construtor recebendo como parâmetro o nome do teste a fazer Fornecer um ou mais métodos com o nome test...() sem parâmetros e sem valor de retorno Cada método de testes testa algo usando o método asserttrue(msg, condição) Fornecer um método estático suite() que informa quais são os métodos de testes Página 1 de 25

O framework fornece uma interface gráfica para executar os testes Basta informar o nome da classe de testes O que foi mencionado acima é o mínimo necessário para usar o framework Para ter um pouco mais de controle, podemos fazer override dos seguintes métodos, se necessário: setup() É um hook method chamado antes de cada método de teste Para manter testes independentes Usado para construir um contexto para um teste Exemplo: abrir uma conexão de banco de dados teardown() É um hook method chamado depois de cada método de teste Usado para desfazer o que setup() faz Exemplo: fechar uma conexão de banco de dados runtest() Para controlar a execução de um teste particular É raro fazer override disso Exemplo do uso de JUNIT Queremos testar a seguinte classe: /* * 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. Página 2 de 25

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 cla * 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(); val for(int naipe = primeironaipe(); naipe <= últimonaipe( // chama criacarta e não "new" para poder fazer over // 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. Página 3 de 25

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 * 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 * todos os naipes possíveis de cartas. Página 4 de 25

* @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úmer 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); Página 5 de 25

Uma possível classe de teste segue abaixo A cor vermelha indica classes, métodos, etc. do framework 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) { Página 6 de 25

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.últimonaip 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.últimonaip asserttrue("carta já vista",!cartasjávistas.contains(c)); cartasjávistas.add(c); assertequals("baralho não vazio", 0, b3.númerodecartas() Execute o JUNIT via applet aqui Sorry! Não funciona porque o JUNIT tenta ler um arquivo local (de preferências) e o mecanismo de segurança do applet não deixa. Um dia, vou ver se conserto essas coisas... Alternativamente, execute o teste da classe (comando testa.bat do diretório deste arquivo) O resultado deve ser algo assim: Página 7 de 25

O projeto de JUNIT Começaremos do zero e usaremos patterns para explicar o framework até montar a arquitetura final O início: TestCase Para manipular testes facilmente, eles devem ser objetos e não: Comandos de impressão Expressões de depurador Scripts de testes Isso concretiza os testes para que possam reter seu valor ao longo do tempo O pattern "Command" é útil aqui O padrão Command permite encapsular um pedido (de teste) como objeto Command pede para criar um objeto e fornecer um Página 8 de 25

método execute() Nós chamaremos este método de run() A definição da classe TestCase segue: public abstract class TestCase implements Test {... É declarada "public abstract" porque será reusada através de herança Por enquanto, esqueça da implementação da interface Test Cada teste é criado com um nome para que você possa identificar o teste quando falha public abstract class TestCase implements Test { private final String fname; public TestCase(String name) { fname= name; public abstract void run();... A arquitetura até agora é a seguinte (com indicação do design pattern usado): Completando a informação: run() Precisamos de um lugar conveniente para colocar o código de testes A parte comum para todos os testes deve ser fornecida pelo framework O que cada teste faz: Cria um contexto para o teste Executa código usando o contexto Página 9 de 25

Verifica algum resultado Limpa o contexto Testes terão seu valor maximizado se um teste não afetar o outro Portanto, os passos acima devem ser feito para cada teste Podemos assegurar que essas etapas serão sempre feitas usando o design pattern chamado Template Method Template Method define o esqueleto de um algoritmo em uma operação, deixando certos passos para subclasses. A estrutura do algoritmo não muda A execução dos passos não muda mas a forma de fazer cada passo será definida pelas subclasses O template method segue abaixo: public abstract class TestCase implements Test { //... public void run() { setup(); runtest(); teardown(); A implementação default dos métodos faz nada: public abstract class TestCase implements Test { //... protected void runtest() { // na realidade, o framework oferece uma implementação d // que veremos abaixo protected void setup() { // hook method protected void teardown() { // hook method Já que setup() e teardown() sofrerão override nas Página 10 de 25

subclasses, elas são declaradas como protected A arquitetura até agora: Obtendo resultados dos testes: TestResult Para cada teste, queremos: Um resumo supercondensado sobre o que funcionou Detalhes do que não funcionou Usaremos o pattern Collecting Parameter da coleção "Smalltalk Best Practice Patterns" do Guru Kent Beck Padrões quase tão famosos quanto os padrões do Gang of Four O padrão sugere que, quando você precisa colecionar resultados obtidos com vários métodos, adicione um parâmetro ao método e passe um objeto que vai colecionar os resultados para você Criamos um novo objeto, da classe TestResult, para juntar os resultados dos testes executados Esta versão simplificada de TestResult apenas conta os testes executados public class TestResult { protected int fruntests; public TestResult() { fruntests= 0; Para usar o objeto: Adicionamos um parâmetro à chamada TestCase.run() para coletar resultados Aqui, o "resultado" é só contar testes Página 11 de 25

public abstract class TestCase implements Test { //... public void run(testresult result) { result.starttest(this); setup(); runtest(); teardown(); Assim, TestResult pode contar os testes executados: public class TestResult { public synchronized void starttest(test test) { fruntests++; O método starttest() é synchronized para que TestResult possa colecionar resultados de vários testes, mesmo que executem em threads diferentes Para manter a interface externa simples de TestCase, criamos uma versão de run() sem parâmetros que cria seu próprio TestResult public abstract class TestCase implements Test { //... public TestResult run() { TestResult result= createresult(); run(result); return result; protected TestResult createresult() { return new TestResult(); Observe o uso do pattern Factory Method A arquitetura até agora: Página 12 de 25

Testes podem falhar de forma prevista e de forma não prevista (freqüentemente espetacular!) Portanto, JUNIT distingue entre falhas e erros Falhas são descobertas com uma exceção AssertionFailedError Erros são resultados de quaisquer outras exceções 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(); A exceção AssertionFailedError é lançada pelo método asserttrue() da classe TestCase Há várias versões de assertxxx() Um exemplo simples segue: public abstract class TestCase implements Test { //... protected void asserttrue(boolean condition) { if(!condition) { throw new AssertionFailedError(); Página 13 de 25

Os métodos de TestResult que colecionam os erros são mostrados abaixo: 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)); A classe TestFailure é uma classe interna que mantém o teste e a exceção juntos num objeto: public class TestFailure { protected Test ffailedtest; protected Throwable fthrownexception; O uso canônico do pattern Collecting Parameter exigiria que cada método de teste da classe de testes passasse um parâmetro com o TestResult Já que estamos usando exceções para indicar problemas, podemos evitar essa passagem de parâmetros (que poluiriam as interfaces) da seguinte forma: run() chama o próprio TestResult para executar o teste e TestResult chama os métodos de testes e captura as exceções Desta forma, o programador que bola os testes não tem que se preocupar com TestResult, que fica completamente escondido O teste abaixo mostra como o método de teste nada sabe sobre TestResult public abstract class MeuTeste extends TestCase { //... public void runtest() { assertequals(1, b1.menorvalor()); assertequals(13, b1.maiorvalor()); Página 14 de 25

assertequals(52, b1.númerodecartas()); JUNIT provê várias versões de TestResult: A implementação default conta falhas e erros e junta os resultados TextTestResult junta os resultados e os apresenta em forma textual UITestResult junta os resultados e os apresenta em forma gráfica O programador poderia fornecer um HTMLTestResult para reportar os resultados em HTML Porém, eu [Jacques] acho que poderíamos melhorar isso usando um único TestResult e usar o padrão Observer para conectá-lo à interface do usuário Talvez isso tenha sido feito numa versão mais recente de JUNIT Não crie subclasses estúpidas: TestCase revisitado Usamos o pattern Command para representar um teste O pattern precisa de um método execute() (chamado run(), aqui) Podemos chamar diferentes implementações do comando através dessa interface simples Precisamos de uma interface genérica para executar os testes Porém, todos os testes são implementados como métodos diferentes de uma mesma classe de testes Para evitar a proliferação de classes de testes Como chamar todos esses testes (com nomes diferentes) usando uma interface única? O próximo problema a resolver é portanto de fazer todos os testes parecerem iguais ao chamador do teste O pattern Adapter parece se aplicar Página 15 de 25

Adapter converte a interface de uma classe para uma outra interface que o cliente espera Há várias formas de fazer isso Uma classe adaptadora que usa herança para adaptar a interface Exemplo: override de runtest() para adaptar a interface e chamar outro método public class TesteBaralhar extends TesteBaralho { public TesteBaralhar() { super("testbaralhar"); protected void runtest() { testbaralhar(); A forma acima é ruim para o programador pois exige a criação de muitas classes Em Java, podemos usar classes anônimas para evitar a proliferação de classes: TestCase test = new TesteBaralho("testBaralhar") { protected void runtest() { testbaralhar(); ; Isso é muito mais conveniente que criar subclasses Uma outra forma se chama Pluggable Behavior A idéia é usar uma classe única que pode ser parametrizada para executar lógica diferente sem subclasses Uma forma de implementar Pluggable Behavior é de usar o Pluggable Selector O Pluggable Selector é um atributo da classe que indica o método a chamar Em Java, podemos usar o String representando o nome do método e usar a API de reflexão para fazer a chamada Isso é chamado Very Late Binding Página 16 de 25

Reflexão deve ser usada com cuidado! Não use sempre! É feio! (difícil de entender) JUNIT permite usar Pluggable Selector ou classes anônimas para implementar a adaptação: o programador escolhe Por default, o Pluggable Selector com reflexão já está implementado pelo framework 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 IllegalAccessExce Todos os métodos de testes devem ser públicos, já que a API de reflexão do Java só permite achar métodos públicos A arquitetura até agora: Um teste ou vários testes? TestSuite Página 17 de 25

Muitos testes devem ser executados, não apenas um Até agora, JUNIT roda um único teste (com vários métodos de testes) e registra os resultados num TestResult Agora, queremos executar vários testes O problema é fácil de resolver se o chamador de um teste não souber se está executando um teste ou vários Qual o pattern a usar? Composite! Composite permite tratar objetos individuais e composições de objetos de forma uniforme Queremos testes individuais, suites de testes, suites de suites de testes, suites de suites de suites de testes, etc. O pattern Composite requer os seguintes participantes: Component: declara a interface a usar para interagir com os testes Composite: implementa essa interface e mantém uma coleção de testes Folha: representa um caso de teste que obedece a interface Component Em Java, definimos uma interface para capturar o comportamento comum public interface Test { public abstract void run(testresult result); TestCase é a folha O Composite é a classe TestSuite public class TestSuite implements Test { private Vector ftests= new Vector(); O método run() de TestSuite delega o trabalho para seus filhos: public class TestSuite implements Test { //... Página 18 de 25

public void run(testresult result) { for(iterator it = ftests.iterator(); it.hasnext(); ) { Test test = (Test)it.next(); test.run(result); Observe o uso do padrão Iterator Finalmente, clientes podem adicionar testes a uma suite: public class TestSuite implements Test { //... public void addtest(test test) { ftests.add(test); Todo o código acima só depende da interface Test Já que TestCase e TestSuite implementam a interface Test, podemos fazer composições recursivas arbitrárias Cada programador cria suas suites Podemos rodar todas as suites dos programadores, criando uma nova TestSuite e adicionando as suites individuais No fim, basta chamar run() da suite do topo para executar todos os testes A arquitetura com Composite: Página 19 de 25

Segue um exemplo da criação de uma TestSuite: 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")); Porém, isso ainda é muito trabalho para o programador Ao criar um novo teste, ele não vai rodar se você esquecer de colocá-lo na suite JUNIT provê um método de conveniência que recebe a classe como parâmetro e extrai todos os métodos de testes, criando uma suite para eles O nome dos métodos de testes inicia com test... e eles não têm parâmetros O código acima pode ser simplificado para: public static Test suite() { return new TestSuite(TestaBaralho.class); Se desejado, a forma original pode ser usada para executar apenas um conjunto dos testes Dá para ver que os autores foram muito longe no sentido de dar pouco trabalho ao pobre programador que bola os testes Resumo A arquitetura final: Página 20 de 25

Observe que a classe TestCase está envolvida em vários padrões Essa "densidade de padrões" é comum em bons projetos, depois de maduros (isto é, depois de bastante uso e refactoring!) Os autores (Beck e Gamma) admitem que demorou um bocado até chegar ao design presente Não saiu assim de primeira A figura abaixo mostra a evolução da arquitetura com a aplicação de padrões A figura abaixo mostra iterações entre classes de testes e o framework Página 21 de 25

Observações sobre cada passo (após ter executado o framework indicando que deve testar TestaBaralho) 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 Página 22 de 25

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) Um diagrama de seqüência mais detalhado segue abaixo: Página 23 de 25

Página 24 de 25

Observações gerais Projetar com padrões é muito útil Explicar um projeto para outras pessoas usando padrões é também muito útil Frameworks maduros têm uma alta densidade de padrões Frameworks maduros são mais fáceis de usar, mas podem ser mais difíceis de mudar JUNIT foi testado usando o próprio JUNIT, claro! JUNIT é um framework muito simples: ele faz o trabalho mínimo necessário Extensões estão disponíveis em outro package (junit.extensions) Uma das extensões é um TestDecorator que permite executar código adicional antes e depois de um teste Por que JUNIT é um framework? Ele faz quase tudo que é necessário para testar Força o programador a dar apenas o que falta: Através de hook methods (setup, teardown, runtest, construtor da classe de testes) Fornece defaults para todos os hook methods, menos o construtor da classe de testes Fornece um padrão de nomes a seguir para os métodos de testes para que sejam descobertos automaticamente pelo framework O Hollywood Principle está em ação: A lógica básica está pronta O framework chama seu código de testes Porém observe que seu código também chama o framework (com os métodos asserttrue,...) frame-5 programa anterior próxima Página 25 de 25