Desenvolvimento Orientado a Componentes e Reuso Testes de Software JUnit Prof. Thiago Affonso de M. N. Viana [email protected] Por que usar testes? • Castigo para programador • Gastos com tempo e dinheiro • Leis de Murphy – 1ª. Se uma coisa pode sair errado, sairá. – 2ª. Se tudo parece estar indo bem, é porque você não olhou direito. – 3ª. A natureza sempre está a favor da falha oculta. Por que usar testes? • "É de conhecimento geral entre os analistas de software que nunca se elimina o último bug de um programa. Os bugs são aceitos como uma triste realidade. Esperamos eliminá-los todos, um por um, mas nunca conseguiremos nos livrar deles." DeMarco, Tom , Editora Campus, 91 • Desculpas: – "Depois eu escrevo o plano de testes..." – "Vamos deixar os testes para a próxima fase..." – "Na minha máquina funcionou..." – "Temos que entregar o produto na semana que vem..." Falta, Erro e Falha • Falta (Bug) if(x > 1){ //fazer alguma coisa } • Erro (estado transiente que não deveria acontecer) x=0 • Falha (erro perceptível) – Causaria uma excecao no sistema – Tela azul • Tolerancia a falhas – habilidade de um sistema continuar em operação mesmo na ocorrência de falhas O que são testes de software? • “Execuções de um programa com a intenção de encontrar erros". Myers, 1979 • Processo de testes – Foco na prevenção de erros (como outras fases da garantia da qualidade de software); – descobrir sintomas causados por erros; – fornecer diagnósticos claros para que os erros sejam facilmente corrigidos Elementos complicadores • erros nem sempre são óbvios; • erros diferentes podem ter a mesma manifestação; • saber quem um programa não esta correto não necessariamente é saber como corrigir o erro. Teste x Depuração • Objetivos do teste : mostrar que o software tem erros. • Objetivos da depuração : encontrar a causa do erro detectado no teste, e projetar e implementar as modificações no programa para correção do erro. Processo de Testes • Etapas – Definir os processos – Medir os processos – Controlar os processos (garantir que a variabilidade é estável e os resultados previsíveis) – Melhorar os processos Tipos de testes • • • • • • • Teste Estrutural (Caixa branca) Teste Funcional (Caixa preta) Teste baseado em erros Teste de unidade Teste de sistema Teste de regressão Teste de aceitação Teste Estrutural (caixa branca) • • • • Os caminhos lógicos são testados com o intuito de garantir que todos os caminhos independentes dentro de um módulo tenham sido exercitados pelo menos uma vez. Executa todas as decisões lógicas para valores falsos ou verdadeiros Executa todos os laços em suas fronteiras Exercita as estruturas de dados internas Teste Estrutural (caixa preta) • • • São usados para demonstrar que as funções dos softwares são operacionais, que a entrada é adequadamente aceita e a saída é corretamente produzida; que a integridade das informações externas é mantida. Atividade complementar aos testes de caixa branca, com a finalidade de descobrir tipos/classes de erros. Procura descobrir erro em: – funções incorretas ou ausentes; – erros de interface; – erros nas estruturas de dados ou em acesso a bancos de dados externos; – erros de desempenho; – erro de inicialização e término Teste baseado em erros • Consiste em incluir propositalmente algum erro no programa e observar o comportamento do programa com erro, comparando-o com o comportamento do programa original. Teste de unidade • • • Deve ser escrito pelo mesmo programador que desenvolveu o código a ser testado. Serve como documentação do sistema Essencial para análise de desempenho Teste de sistema • • • Comparar o sistema com seus objetivos originais Enfatizar a análise do comportamento da estrutura hierárquica de chamadas de módulos Fase mais complexa, devido à quantidade de informações envolvidas Teste de regressão • • Teste necessário para assegurar que modificações no programa não causaram novos erros baseado em arquivo de 'log' Teste de aceitação • • • A validação é bem sucedida quando o software funciona de uma maneira razoavelmente esperada pelo cliente . Pressman , 1995 Expectativas dos clientes documentadas Uso da documentação do usuário Testes num nível mais prático • Teste o código em seus limites; – Para cada pequeno trecho de código (um laço, ou if por exemplo) verifique o seu bom funcionamento; – Tente ume entrada vazia, um único item, um vetor cheio, etc. • Teste de pré e pós condições; – Verificar certas propriedades antes e depois de trechos de código; • Programe defensivamente; • Sempre verificar se ocorreram erros ao abrir, ler, escrever e principalmente fechar arquivos; • Use excecoes; • Sempre tratar as possíveis exceções; Testes num nível mais prático • Teste incrementalmente – Durante a construção do sistema; – Após testar dois pacotes independentemente teste se eles funcionam juntos; • Teste primeiro partes simples – Tenha certeza que partes básicas funcionam antes de prosseguir; – Testes simples encontram erros simples; • Conheça as saídas esperadas – Conheça a resposta certa; – Para programas mais complexos valide a saída com exemplos conhecidos; – Numéricos - exemplos conhecidos, características; – Gráficos - exemplos, não confie apenas nos seus olhos; Testes num nível mais prático • Testar com grandes quantidades de dados – Gerados automaticamente; – Erros comuns: – Overflow nos buffers de entrada, vetores e contadores; • Não continue a implementação de novas características se já foram encontrados erros; – Teste em várias máquinas, compiladores e SOs(Se possível) Conclusões • "Teste e depois codifique" (Hetzel) • "Teste cedo e frequentemente" (Beizer) Ferramentas • JUnit – Framework para escrever testes • Maiores informacoes em – http://www.testingfaqs.org/ JUnit - 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 JUnit: Framework Java para testes de unidade • Framework de testes • Integrado com várias ferramentas: Eclipse, Netbeans, ... • Funcionamento simples e eficaz Um exemplo do uso do framework • Para testar uma classe, criamos uma classe de testes correspondente • O mínimo que se deve fazer é o seguinte (versão 4.4+): • Criar uma classe que tenha o nome TestNomeDaClasse. • Usar @Test ou @Test(expected= ...Exception) antes da assinatura do método. • Fornecer um ou mais métodos com o nome test...() sem parâmetros e sem valor de retorno. • 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() • Usado para construir um contexto para um teste – tearDown() • Usado para desfazer o que setUp() faz – runTest() • Para controlar a execução de um teste particular • É raro fazer override disso Escrevendo testes • Testes de unidade: forma mais simples – Expressões num depurador – Expressões impressas em uma saída padrão – Requerem observação humana • JUnit – Forma simples que não requer observação humana – Use a annotation @Test no método – Para verificar um valor use os métodos assertEquals e assertNotSame Escrevendo testes • Exemplo @Test public void testSimpleAdd() { Money m12CHF= new Money(12, "CHF"); Money m14CHF= new Money(14, "CHF"); Money expected= new Money(26, "CHF"); Money result= m12CHF.add(m14CHF); assertTrue(expected.equals(result)); } Escrevendo testes • Para testes similares a testes já realizados, usamos Fixture • Útil para configurar todos os dados (conjunto de objetos) necessários a bateria de testes • Usa annotations especiais – @Before –inicializar toda as variáveis do método – @After – liberar os recursos alocados pelo método • Uma vez tendo uma Fixture, pode-se escrever diversos Test Cases. Escrevendo testes public class MoneyTest { private Money f12CHF; private Money f14CHF; private Money f28USD; @Before public void setUp() { f12CHF= new Money(12, "CHF"); f14CHF= new Money(14, "CHF"); f28USD= new Money(28, "USD"); } } Executando Testes • JUnit prove ferramentas para definir os testes a serem executados e mostrar os resultados – org.junit.runner.JUnitCore.runClasses(TestClass1.class, ...); – Declare um metodo estático suite que retorna um teste public static junit.framework.Test suite() { return new JUnit4TestAdapter(Example.class); } – Para compatibilidade com versoes anteriores do JUnit • Exceções esperadas – verificar que o código executa com sucesso ou se comporta como esperado em situações especiais são partes do programa de teste Executando Testes new ArrayList<Object>().get(0); @Test(expected=IndexOutOfBoundsException.class) public void empty() { new ArrayList<Object>().get(0); } Funcionamento dos testes • Todos os testes corretos – barra verde. • Houve falha em algum dos testes – barra vermelha. • Erro – foi jogada alguma exceção e não foi tratada – barra azul. Componentes de um teste Junit • Classes a serem testadas: Triangulo.java Circulo.java • Classe contendo os casos de teste TesteTriangulo.java TesteCirculo.java • Chamador dos testes TestesAceitacao.java Classe com casos de teste • Devem estender da classe junit.framework.TestCase • Possui os métodos – setUp() – tearDown() • Todos os casos de teste começam com “test” e não possuem parâmetros. JUnit - Casos de teste • assert – Garante que uma determinada condição seja verdadeira. Caso não seja uma exceção é levantada(AssertionFailedError). Ex:. assert(“mensagem de erro”, var1 == var2); • assertEquals – Verifica se dois valores passados como parâmetro são iguais Ex:. assert(“mensagem de erro”, var1, var2); • fail – Retorna uma exceção caso execute esta linha. Ex:. fail(“não deveria passar por esta linha”); Exemplos de testes • Testando valores validos – Somente o assert é necessário. String retorno = Triangulo.classificaTriangulo(new String[] {"4", "4", "8"}); assertEquals("Valores não deveriam formar um triângulo.","NAO É TRIÂNGULO", retorno); Exemplos de testes • Testando se operações não retornam exceções String[] texto = new String[]{"A", "A", "A"}; try { Sring retorno = Triangulo.classificaTriangulo(texto); fail("Não levantou NumberFormatException para " + trianguloString(texto)); } Exemplos de testes • Testando se operações retornam exceções indevidas String[] texto = new String[]{"A", "A", "A"}; try { Sring retorno = Triangulo.classificaTriangulo(texto); } catch (NumberFormatException nfe) { //ok } catch (Throwable th) { fail("Lançou exceção diferente de NumberFormatException para os valores de entrada" + th.getMessage()); } SetUp • Inicializa o ambiente para a realização dos testes setup() caso de teste () ... setup() caso de teste() ... TearDown • Usado para “limpar” o ambiente depois da realização dos testes setup() caso de teste () tearDown() setup() caso de teste() tearDown() ... Observação Importante! • JUnit não garante ordem de execução dos testes • Para isso, cada caso de teste deve ser adicionado na classe chamadora. Exercício • Crie a classe Triangulo.java: • A classe possui um método classificar: recebe três valores de lado e informa: – – – – Se os lados formam um triângulo eqüilátero Se os lados formam um triângulo isósceles Se os lados formam um triângulo escaleno Se os lados NÃO formam um triângulo • Crie a classe de caso de testes • Crie uma classe que executa uma suíte de testes