Renato Lourenço Moreira
Planejando e executando casos de testes com a Técnica de Teste
do Caminho Básico e JUnit
Palmas
2006
i
Renato Lourenço Moreira
Planejando e executando casos de testes com a Técnica de Teste
do Caminho Básico e JUnit
“Relatório
apresentado
como
requisito parcial da disciplina de
Prática de Sistemas de Informação I
(Estágio) do curso de Sistemas de
Informação, orientado pela Profª.
Cristina D’Ornellas Filipakis”.
Palmas
2006
ii
Renato Lourenço Moreira
Planejando e executando casos de testes com a Técnica de Teste
do Caminho Básico e JUnit
“Relatório
apresentado
como
requisito parcial da disciplina de
Prática de Sistemas de Informação I
(Estágio) do curso de Sistemas de
Informação, orientado pela Profª.
Cristina D’Ornellas Filipakis”.
Aprovada em ___/___/_______
BANCA EXAMINADORA
______________________________________________________
Profª. Cristina D’Ornellas Filipakis - Orientadora
Centro Universitário Luterano de Palmas
_______________________________________________________
Prof. Ricardo Marx Costa Soares de Jesus
Centro Universitário Luterano de Palmas
_______________________________________________________
Profª. Madianita Bogo
Centro Universitário Luterano de Palmas
Palmas
2006
iii
AGRADECIMENTOS
Gostaria de agradecer primeiramente a Deus, por me conceder a
oportunidade de estar aqui hoje completando mais um passo rumo à conclusão
minha primeira graduação. A toda minha família, especialmente meus pais, que
tanto contribuíram para a minha formação, incentivando e apoiando nos
momentos decisivos. Aos meus colegas de faculdades, que através de suas
críticas construtivas, me ajudaram a me empenhar mais no cumprimento de
minhas atividades. Aos professores de nosso curso e à minha tão prestativa
orientadora, a professora Cristina D’Ornellas Filipakis, que me ajudou muito,
mostrando as minhas maiores dificuldades no desenvolvimento desse projeto e
também me ensinando a supri-las. Por último, com muito carinho, Ayanna, que
não é apenas a minha namorada, mas sim, minha companheira de todas as
horas, que sempre esteve do meu lado, me incentivando e reanimando.
iv
SUMÁRIO
1
INTRODUÇÃO ................................................................................................ 9
2
REVISÃO LITERÁRIA................................................................................... 11
2.1
Engenharia de Software......................................................................... 11
2.2
Teste de Software .................................................................................. 14
2.2.1
Técnicas de teste de software ........................................................ 16
2.2.1.1 Projeto de casos de teste ............................................................ 17
2.2.1.2 Plano de teste ............................................................................. 17
2.2.1.3 Caso de teste .............................................................................. 18
2.2.1.4 Suíte de teste .............................................................................. 18
2.2.2
Teste estrutural ou teste de caixa branca ....................................... 19
2.2.3
Teste funcional ou teste de caixa preta .......................................... 20
2.2.4
Teste de caminho básico ................................................................ 21
2.2.5
Teste de condição........................................................................... 24
2.2.6
Teste de laços................................................................................. 25
2.2.7
Teste unitário .................................................................................. 26
2.2.8
Testes de verificação e validação de software................................ 27
2.2.9
Teste de integração ........................................................................ 29
2.2.9.1 Integração Top-down .................................................................. 30
2.2.9.2 Integração Bottom-up.................................................................. 31
2.2.10
3
Depuração de software................................................................... 31
2.3
Linguagem de Programação Java ......................................................... 31
2.4
JUnit....................................................................................................... 32
2.5
Estudo de caso ...................................................................................... 36
MATERIAIS E MÉTODOS ............................................................................ 38
3.1
Materiais ................................................................................................ 38
v
3.1.1
Linguagem de Programação........................................................... 38
3.1.2
JUnit................................................................................................ 39
3.1.3
JCreator LE..................................................................................... 39
3.2
Métodos ................................................................................................. 39
3.2.1
Pesquisa Bibliográfica..................................................................... 40
4
RESULTADOS E DISCUSSÕES .................................................................. 41
5
CONSIDERAÇÕES FINAIS .......................................................................... 63
6
REFERÊNCIAS BIBLIOGRÁFICAS .............................................................. 65
vi
LISTA DAS FIGURAS
Figura 01. Fluxograma de um teste do caminho básico....................................... 22
Figura 02. Grafo de fluxo do fluxograma da figura 1. ........................................... 22
Figura 03. Tipos de laços – retirada de (MARIATH, 2004)................................... 26
Figura 04: Diagrama de Classes .......................................................................... 36
Figura 05: Grafo do método fatorial() ................................................................... 42
Figura 06: Código do método testFatorial(). ......................................................... 43
Figura 07: Menu de escolha da versão de apresentação dos resultados ............ 44
Figura 08: Resultado dos testes no método fatorial()........................................... 44
Figura 09: Resultado dos testes no método fatorial()........................................... 45
Figura 10: Resultado dos testes no método fatorial()........................................... 45
Figura 11: Código do método testFatorial() alterado. ........................................... 46
Figura 12: Resultado do teste negativo no método fatorial(). ............................... 46
Figura 13: Grafo do método maiorNumero(). ....................................................... 47
Figura 14: Código do método testMaiorNumero(). ............................................... 48
Figura 15: Resultado do testes no método maiorNumero().................................. 49
Figura 16: Código do método testMaiorNumero() alterado. ................................. 49
Figura 17: Resultado do teste negativo no método maiorNumero(). .................... 50
Figura 18: Grafo do método par()......................................................................... 51
Figura 19: Código do método testPar(). ............................................................... 51
Figura 20: Resultado do teste no método par(). ................................................... 52
Figura 21: Código do método testPar() alterado. ................................................. 52
Figura 22: Resultado do teste negativo no método par(). .................................... 53
Figura 23: Resultado dos testes da classe Cálculos ............................................ 53
Figura 24: Grafo do método insertSort()............................................................... 55
Figura 25: Código dos métodos testInsertSort() e testBoubleSort(). .................... 56
vii
Figura 26: Grafo do método boubleSort()............................................................. 57
Figura 27: Resultado dos testes nos métodos insertSort() e boubleSort(). .......... 58
Figura 28: Código do método testInsertSort() alterado. ....................................... 59
Figura 29: Resultado de teste negativo no método insertSort(). .......................... 59
Figura 30: Resultado da Suíte de Testes. ............................................................ 60
Figura 31: Código da classe TSuite.class ............................................................ 61
viii
RESUMO
A finalidade desse trabalho é apresentar alguns conceitos a cerca dos tipos
de testes de software, bem como, a utilização do framework JUnit para a criação
e execução automatizada de testes em classes implementadas na linguagem de
programação Java.
O framework JUnit consiste em um “ambiente de trabalho” que possibilita a
elaboração execução e obtenção de resultados dos testes, ele tem a capacidade
de encontrar as falhas e mostrar exatamente onde as mesmas ocorreram. O JUnit
reduz os esforços dos desenvolvedores no que diz respeito aos testes. Os casos
de testes gerados com ele são facilmente utilizados em outros testes, sem que
seja necessário refazê-los.
Palavra-chave: JUnit.
1
INTRODUÇÃO
A engenharia de software é constituída de diversos procedimentos que
possibilitam a adequação do processo de criação de um software, afim de que o
mesmo tenha qualidade ao final de seu desenvolvimento. A engenharia é
responsável por estipular o tempo que será gasto para se desenvolver cada
módulo do sistema, bem como, o custo de cada um, tanto de recursos pessoais
quando de equipamentos.
Por Pressman (1995), o processo de engenharia de software se divide
basicamente em três elementos: métodos, ferramentas e procedimentos, sendo
os métodos, responsáveis por detalhar a maneira de se construir um software, ou
seja, análise, projeto, codificação e teste; as ferramentas, sistemas gerenciadores
de banco de dados utilizados, linguagens de programação e ferramentas CASE; e
por fim, procedimentos, que unem os métodos às ferramentas.
O processo de engenharia é constituído ainda, por três fases: a primeira
consiste no “o quê?” será desenvolvido, aqui são analisados e determinados os
requisitos e funcionalidades do sistema. A segunda consiste no “como?” será
desenvolvido o projeto, a codificação e os testes. Por último, a terceira fase, que
trata da manutenção do sistema, controla todas as mudanças realizadas no
sistema, seja por correção de falhas ou adaptação de plataforma.
Esse trabalho enfoca uma das fases da engenharia de software, que é a
fase de testes. A mesma é responsável por varrer o sistema à procura de falhas
dos mais variados tipos. Existem testes específicos para validar o código fonte,
que são chamados de testes de caixa branca, e se preocupam com a estrutura
interna do sistema, com a lógica de programação utilizada. Existem ainda, outros
testes denominados de caixa preta, que testam se o sistema faz o que foi
10
especificado, ou seja, se as saídas obtidas são as esperadas, se as
funcionalidades estão operando normalmente.
O objetivo deste trabalho consiste em apresentar conceitos de engenharia
de software, especialmente acerca dos testes de software, as características de
cada um deles, e ainda, analisar o JUnit, um framework para testes, ou seja, um
ambiente de trabalho que propicia a criação e execução de testes de unidades em
classes implementadas na linguagem de programação Java. Exemplos reais da
aplicação dessa ferramenta também serão apresentados no decorrer desse
trabalho.
A estrutura desse trabalho está disposta da seguinte maneira: o capítulo 2
dispõe a revisão literária, na qual serão apresentados detalhadamente conceitos
de engenharia de software, tipos de testes de software, JUnit, e um estudo de
caso para aplicar os conceitos do JUnit. No capítulo 3 estão os materiais e
métodos utilizados no desenvolvimento do trabalho, todas as ferramentas, como
linguagem de programação, o próprio JUnit, livros, entre outros. No capítulo 4
encontram-se explanados os resultados e discussões, onde o resultado da
utilização do JUnit será apresentado, ou seja, a implementação dos casos de
testes será exibida. Após, no capítulo 5, encontram-se as conclusões finais, o que
foi alcançado com o desenvolvimento do trabalho, quais as principais dificuldades
encontradas e trabalhos futuros. E por fim, o capítulo 6 apresenta todas as
referências bibliográficas utilizadas, livros consultados e sites navegados.
11
2
REVISÃO LITERÁRIA
Esta seção abordará teoricamente os conceitos de engenharia de software,
os teste de software, a linguagem de programação Java, a ferramenta para
elaboração e execução dos testes, JUnit, e por fim uma atividade mais prática, um
estudo de caso.
2.1 Engenharia de Software
Pressman (1995) afirma que, antes dos anos 80, a preocupação dos
desenvolvedores estava quase completamente voltada à produção de hardware
que minimizasse o custo de processamento e armazenamento de dados. O
software era apenas um incremento básico, porém essencial, que acompanhava a
máquina. Ao decorrer dos anos, a microeletrônica evoluiu muito e possibilitou
grandes avanços no desenvolvimento e produção de hardwares poderosos, estes
foram industrializados com maior sofisticação a um custo consideravelmente
reduzido. Com os obstáculos encontrados no campo de desenvolvimento de
hardwares
superados,
os
engenheiros
tinham
pela
frente
um
desafio
completamente distinto ao anterior, o desafio de desenvolver softwares eficientes,
de alta qualidade e segurança.
Segundo o mesmo autor, no início dos anos 80 o software sofreu um rápido
amadurecimento e começou a ser uma peça fundamental para o sucesso, ou
fracasso, das grandes empresas. Isso causou certa insegurança aos gerentes e
administradores dessas empresas. A mídia colocava a automatização como algo
perigoso, e, ao final da década de 80, publicações do tipo: “Podemos Confiar em
12
Nosso Software?”, passaram a ser comuns. A insegurança dos administradores
das empresas tinha fundamento, mas sabiam que era necessário encarar a
realidade, a automatização das empresas tornou-se uma obrigação e correr esse
risco passou a ser inevitável.
Logo que se iniciou a década de 90, o software superou de vez o hardware,
havia uma procura maior por softwares de qualidade do que por hardware de
ponta, o gasto com software aumentou consideravelmente. Isso ocorreu porque
era o software que fazia a diferença na hora de administrar o empreendimento. O
software era responsável por armazenar as informações, tratá-las e disponibilizálas em forma de relatórios. O software fazia com que uma empresa fosse melhor
do que suas concorrentes, ele passou a ser, de fato, o diferencial das empresas.
Logo, essas empresas passaram a procurar, cada vez mais, por softwares de
melhor qualidade, que oferecessem uma maior segurança nas transações, que
tivessem funcionalidades mais específicas e que atendessem melhor às suas
necessidades.
A engenharia de software consiste em um conjunto de atividades que
oferecem um ambiente propício ao desenvolvimento de softwares que seguem o
mais alto padrão de qualidade, softwares que proporcionem benefícios e lucros
reais a seus detentores (PRESMAN, 1995). Através da engenharia de software é
possível prever os prazos e os custos do projeto, a maneira como será
desenvolvido o projeto, como será feito o remanejamento do pessoal envolvido,
como e quando serão as reuniões de discussão do progresso do projeto dentre
outras atividades. A utilização dos princípios da engenharia de software aumenta
a probabilidade de qualidade final do produto, bem como, a sua aceitação no
mercado.
No
processo
de
engenharia
de
software
existem
três
fases,
independentemente do tamanho e da complexidade do projeto, Pressman (1995)
define essas três fases como definição, desenvolvimento e manutenção, sendo
que elas estarão presentes no decorrer de todo o projeto.
Na primeira fase é definido “o que” será desenvolvido. Os desenvolvedores
são responsáveis por identificar todas as características do software, quais dados
serão processados, quais as funcionalidades que serão mais importantes, o tipo
de interface adequada, a questão da validação dos dados, as restrições e o
13
desempenho desejado. Todos os critérios que favoreçam a qualidade e a
eficiência do produto devem ser detectados.
A fase de definição se divide em: análise do sistema, planejamento do projeto de
software e análise de requisitos.
ƒ
Análise do sistema: define o papel de cada módulo dentro do sistema e com
qual propósito o software será desenvolvido, ou seja, qual a razão de sua
existência no mundo real.
ƒ
Planejamento do projeto de software: após ser definido o sistema a ser
desenvolvido, faz-se o planejamento do projeto, que consiste em analisar os
riscos, fazer a previsão estimada de custos e prazos, e ainda, criar o
cronograma de trabalho a ser seguido.
ƒ
Análise de requisitos: antes de iniciar o trabalho de desenvolvimento, é
necessário colher diversas informações, essas informações indicarão a
direção em que o projeto caminhará, qual será realmente a utilidade do
sistema.
A segunda fase, de desenvolvimento, preocupa-se em “como” o sistema
será construído. Nessa fase, detalhes de implementação são discutidos e
aplicados, as linguagens de programação a serem utilizadas são escolhidas,
dentre outras atividades. A fase de desenvolvimento consiste em três passos:
projeto de software, codificação e realização de testes do software.
ƒ
Projeto de software: transforma os requisitos coletados em representações,
sendo que essas representações são identificadas como a estrutura de dados,
a arquitetura, a interface, entre outras características do sistema.
ƒ
Codificação: nesse passo, os desenvolvedores irão criar e reaproveitar o
código fonte. Esse código consiste em instruções dadas pelos programadores
ao computador, que são executadas pelo mesmo, e o resultado é a conversão
do código em interface gráfica repleta de funcionalidades.
ƒ
Realização de testes de software: o software desenvolvido deve ser testado
com a finalidade de detectar erros de lógica e implementação.
A última fase, a manutenção, é responsável por corrigir problemas gerados
durante as mudanças realizadas no software, ou seja, nessa fase corrigem-se as
falhas encontras e fazem-se adaptações no software de acordo com o perfil e as
exigências do cliente. Algumas mudanças são feitas com intuito de aumentar o
14
desempenho e as capacidades do software. A fase de manutenção divide-se em:
correção, adaptação e melhoramento funcional.
ƒ
Correção: cada erro encontrado exige mudanças corretivas no software, ou
seja, o software sofre mudanças para correção de falhas. Apesar dos vários
testes realizados no software durante o seu desenvolvimento, normalmente,
defeitos são constatados pelos clientes.
ƒ
Adaptação: a mudança adaptativa é realizada quando o ambiente para o qual
o software foi desenvolvido sofre mudança, ou seja, troca de sistema
operacional ou plataforma, ou ainda, alteração de hardware. O software sofre
a mudança adaptativa para suportar as mudanças no ambiente.
ƒ
Melhoramento funcional: é também conhecida como mudança perfectiva.
Consiste em pequenas mudanças incrementais, geralmente sugeridas pelos
próprios clientes após o uso constante do software. Os clientes detectam
funcionalidades que podem ser adicionadas ao software. Normalmente, são
mudanças pequenas que oferecem benefícios ao software.
ƒ
Outros autores adicionam outro tipo de manutenção, a manutenção preventiva
que consiste em modificar o software com a finalidade de aumentar a
confiabilidade e diminuir o risco de possíveis manutenções no futuro.
2.2 Teste de Software
A última etapa do desenvolvimento de um software é a fase de testes. A
fase de testes é de fundamental importância, pois através dela é possível detectar
e solucionar erros no software. No decorrer de todo o desenvolvimento, atividades
de garantia de qualidade do software são executadas, porém, a possibilidade de
continuar encontrando erros é enorme, mesmo depois de concluído o
desenvolvimento do software.
Segundo Pressman (1995), na fase de testes, os engenheiros executam
uma série de atividades que consistem em varrer o software criado a procura de
qualquer tipo de erro ou falha na codificação, que possam vir a interferir no
correto funcionamento do aplicativo. O teste é responsável por detectar o maior
número possível de erros, pois encontrar todos é praticamente impossível.
15
Os testes de software são responsáveis por executar o software utilizando
valores reais, com a finalidade de encontrar erros. Caso os testes sejam
realizados e nenhuma falha seja descoberta, provavelmente, os casos de testes
não foram bem elaborados. Seguindo o raciocínio de Pressman (1995), os casos
de testes devem elevar a probabilidade de detecção de erros, caso contrário, os
testes não serão bem sucedidos. Pressman (1995) defende ainda que o teste
bem sucedido, além de apontar falhas ainda não constatadas, deve fazer isso
com o mínimo tempo e esforço.
Os testes são realizados com a inserção de dados atuais para que se
possam obter os resultados esperados de acordo com os requisitos e
especificações previamente estabelecidos. Se os dados de saída são os
esperados, significa que as informações geradas pelo software são legítimas e
totalmente confiáveis.
Apesar de todos os esforços exigidos na fase de teste, é comum que o
software não funcione completamente sem a ausência de erros. Falhas fazem
parte do código de programação, isso muitas vezes acontece devido à
complexidade
do
desenvolvimento
de
software
e
da
negligência
dos
desenvolvedores nas fases iniciais da elaboração de um sistema.
Mesmo que os testes, por mais eficazes que sejam, não consigam detectar
e remover todos os erros contidos no software, estes são indispensáveis pois
aumentam o grau de segurança, e conseqüentemente, a confiança no produto
pelo desenvolvedor e pelo cliente.
Inthurn (2001) afirma que há um gasto freqüente muito grande de tempo e
dinheiro em correções de software, em que o desenvolvimento não foi adequado
e a fase de testes não foi satisfatória. Assim, entende-se que os testes são
importantes, também, para evitar surpresas desagradáveis no futuro. Além disso,
os testes são responsáveis por mostrar que o software possui erros e que os
mesmos devem ser corrigidos, mas isso não significa que a partir das correções,
o software estará imune a possíveis falhas posteriores. Novos erros podem ser
inseridos ao software no momento em que outros erros são corrigidos, através de
alterações realizadas no código a fim de corrigir os erros descobertos até o
momento.
16
De acordo com Inthurn (2001), os testes bem sucedidos conseguem, em
média, corrigir aproximadamente 60% das falhas contidas no software. O custo
dos testes realizados dentro do processo de desenvolvimento é relativamente
pequeno se comparado ao gasto com manutenção corretiva do software pronto,
sem mencionar que para, um software ter qualidade, é necessário que haja uma
rotina exaustiva de testes, que se estende por todo o desenvolvimento do projeto.
Jamais a atividade de teste deve trabalhar de maneira isolada, mas sim de
maneira conjunta com as demais atividades e de forma iterativa. Os casos de
teste devem testar as partes produzidas em cada etapa do projeto e o produto
completo ao final do projeto. Só assim a qualidade do produto final é garantida.
A atividade de testes é dividida de diferentes maneiras por diversos
autores. Por exemplo, segundo Peters (2001), a atividade de testes é constituída
das atividades chaves, planos de teste, projetos de teste, casos de teste,
procedimentos de teste, execução de teste e relatório de teste.
Já Pressman (1995) divide como técnicas de teste de software e
estratégias de teste de software. Segue os estudos das atividades de teste de
software focando as definições deste último autor.
2.2.1 Técnicas de teste de software
Segundo Pressman (1995), técnicas de teste de software consistem em
elaborar casos de teste capazes de varrer o software a procura de erros ainda
não constatados. As atividades de testes não visam culpar alguém por falhas ou,
ainda, testar o software com finalidade destrutiva. Os testes são realizados com o
objetivo de encontrar as falhas e, posteriormente, corrigi-las, garantindo assim a
qualidade do mesmo.
Dentro das atividades de testes todos os resultados obtidos são avaliados,
essa avaliação é realizada comparando-se os resultados obtidos com os
esperados. Caso sejam encontrados erros, é feita a depuração.
17
2.2.1.1 Projeto de casos de teste
Um projeto de teste requer uma atenção especial dos engenheiros, pois os
testes a serem realizados devem possuir uma alta probabilidade de descobrir os
erros ainda não detectados, em um prazo relativamente curto e com esforços
irrelevantes. Outro fato que exige que o projeto seja bem elaborado é a questão
dos valores, pois o custo de manutenção para correção de erros após o término
do desenvolvimento pode ser de 60 a 100 vezes mais do o custo da manutenção
realizada durante o desenvolvimento (PRESSMAN, 1995).
Um software pode ser testado de duas formas. A primeira, quando se
conhecem as suas funções e quais as saídas esperadas quando se recebem tais
entradas, é popularmente conhecida como teste de caixa preta. E a segunda,
quando se conhece a estrutura e o funcionamento interno, a maneira que os
métodos estão interligados, quais as responsabilidades de cada um deles, dentre
outros detalhes, é chamada de teste de caixa branca.
Segundo Inthurn (2001), um projeto de teste é responsável por gerenciar a
fase de teste identificando e descrevendo qual tipo de teste será aplicado em
determinado momento. O projeto de teste é dependente de um plano de teste,
esse plano mostra as estratégias para a execução do teste com base numa lista
de casos de teste.
2.2.1.2 Plano de teste
Dentre outros objetivos, o plano de teste deve identificar os componentes
do software que serão testados e os recursos necessários para a correta
execução dos mesmos, recomendando e descrevendo as estratégias de teste a
serem aplicadas. O plano de teste deve conter e disponibilizar a documentação
referente ao projeto em desenvolvimento, seus requisitos funcionais e nãofuncionais, a fim de entender o que será testado no software. O plano de teste
deve conter as estratégias de teste, pois nela encontra-se a maneira como o teste
será realizado e quais técnicas e critérios serão utilizados. Listas contendo os
tipos de testes a serem aplicados, ferramentas, recursos de hardware e de
18
pessoas disponíveis também são parte do plano de teste. A cada teste realizado,
informações sobre o que foi realizado, data e hora de inicio e fim da atividade,
nome do responsável pela mesma e resultados obtidos são registrados
(INTHURN, 2001).
2.2.1.3 Caso de teste
Um caso de teste pode ser definido como um documento que descreve as
etapas a serem seguidas para realização de um teste. Desde os dados de
entrada, a atividade em execução e o resultado de saída. É utilizado para
observar se o resultado de saída é realmente o esperado. Em caso positivo o
teste foi realizado com êxito (INTHURN, 2001).
O caso de teste serve para verificar se o sistema segue as definições
impostas no projeto e se o mesmo foi devidamente construído.
Segundo Bartié (2002), o caso de teste é o documento que registra todo o
planejamento dos testes, nele devem ser abordados os seguintes itens:
ƒ
identificação das condições de testes;
ƒ
identificação do que será testado;
ƒ
definição de cada caso de teste identificado;
ƒ
detalhamento das classes de dados de entrada;
ƒ
detalhamento da saída gerada;
ƒ
responsáveis pela atividade de teste;
ƒ
definição de como será realizada a bateria de testes;
ƒ
cronograma das atividades.
2.2.1.4 Suíte de teste
É o documento que faz o detalhamento final dos testes, nele é identificado
o comportamento de cada caso de teste durante a execução. A suíte de teste
19
estabelece a ordem de execução e a maneira que cada caso de teste irá proceder
(BARTIÉ, 2002).
O objetivo do conjunto de teste é auxiliar no exercício de verificar as
especificações dos requisitos funcionais e não funcionais do sistema, ou seja,
constatar se o sistema procede normalmente durante a execução das funções.
2.2.2 Teste estrutural ou teste de caixa branca
Pressman (1995) define o teste de caixa branca como um método de
projeto de casos de testes voltado a testar a estrutura interna do software. O teste
de caixa branca, ao contrário de teste de caixa preta, se preocupa em como o
código
fonte
foi
construído
e
a
lógica
de
programação
utilizada
no
desenvolvimento dos métodos. O teste é totalmente realizado na estrutura interna
do software (INTHURN, 2001).
O teste estrutural visa confirmar ou se certificar de que a estrutura interna
do software esteja correta. Para isso, é preciso que o caso de teste percorra todos
os caminhos internos possíveis. A atividade de teste estrutural pode ser
considerada complexa porque deve abranger todos os níveis de hierarquia, no
caso de classes com herança. O fato de um método funcionar corretamente em
uma classe não garante que o mesmo não apresente erros na classe que a herde
(INTHURN, 2001).
Teoricamente, um teste de caixa branca seria capaz de avaliar e classificar
um software como 100% correto, isso porque o teste de caixa branca percorre
todos os caminhos lógicos possíveis, o que resulta num teste exaustivo e
complexo, pois mesmo em um programa pequeno, os caminhos lógicos são
muitos (PRESSMAN, 1995). O teste de caixa branca, também, pode ser
conhecido como testes de caixa de vidro, ou ainda, testes de caixa clara
(SOMMERVILLE, 2003).
Bartié (2002) aponta dificuldades em aplicar o teste de caixa branca em
alguns casos. O teste de caixa branca deve ser realizado por um profissional
competente e extremamente experiente, pois o responsável por aplicar o teste
deve conhecer minuciosamente a estrutura do software. O teste será executado
20
pelos próprios desenvolvedores, e como os desenvolvedores normalmente
trabalham sob pressão, o software tem que ficar pronto rapidamente. Este fato faz
com que a preocupação do desenvolvedor seja maior com a implementação, de
forma que os testes acabam ficam em segundo plano.
2.2.3 Teste funcional ou teste de caixa preta
Segundo Inthurn (2001), o teste de caixa preta consiste em verificar na
interface do software se todas as funcionalidades estão operando corretamente.
Este teste é aplicado com a finalidade de encontrar: funções incorretas ou
ausentes, erros de interface, erros nas estruturas de dados ou no acesso ao
banco de dados, erros de desempenho e, ainda, erros de inicialização e término.
O teste funcional, como o próprio nome já diz, é realizado com base nos
requisitos funcionais do software, assim, é possível analisar as entradas e saídas
de todas as unidades. Para isso os dados de entrada devem ser normalmente
aceitos pelo sistema e a saída deve ser a esperada. Caso a saída não seja a
esperada, o erro é constatado. Esse tipo de teste não se preocupa em saber
como o código fonte foi produzido e sim se o mesmo faz o que lhe foi
especificado.
As técnicas de caixa preta são responsáveis por garantir que todos os
requisitos do sistema funcionem de acordo com o especificado durante a
elaboração do projeto. Cabe aos testes de caixa preta averiguar que o sistema
forneça todos os resultados esperados.
Uma vantagem do teste de caixa preta em relação ao teste de caixa branca
é que nesse não é necessário que a equipe de testes conheça ou entenda a
estrutura interna, a tecnologia utilizada e a maneira como foi implementado, e sim,
que verifique se faz o que foi proposto e se atenderá as necessidades do futuro
usuário. No teste de caixa preta o profissional responsável pela execução do teste
necessita apenas entender os requisitos e analisar os resultados produzidos
(BARTIÉ, 2002).
O teste de caixa preta consiste em validar os requisitos funcionais do
sistema, o objetivo é encontrar erros de interface, erros relacionados ao
21
desempenho, ao acesso ao banco de dados, erros de inicialização e término,
falha ou ausência de funções (PRESSMAN, 1995).
Este último autor define alguns tipos de teste de caixa preta, entre eles o
particionamento da equivalência e análise do valor limite.
No primeiro, os dados de entrada são divididos em classes de forma que
os casos de teste possam ser derivados. O particionamento de equivalência
permite que seja definido um caso de teste capaz de encontrar uma ou mais
classes de erros. Um exemplo poderia ser um caso de teste que identifique
problemas sempre que um tipo específico de dado é utilizado. O particionamento
de equivalência é normalmente representado por um conjunto de dados de
entrada, válidos ou inválidos para o sistema.
Na segunda, análise do valor limite, a intenção do teste é colocar em prova
os valores fronteiriços, ou seja, analisar as extremidades do sistema. Difere-se um
pouco do particionamento de equivalência porque além de testar os dados de
entrada, analisa também os dados de saída. O teste pode ser feito da seguinte
forma: se os parâmetros de entrada devem respeitar o intervalo entre 0 e 4, inserir
números normais, ou seja, entre 0 e 4, depois um número abaixo do especificado
e em seguida um número acima do especificado. Assim, é possível testar se as
condições de entrada estão sendo validadas. O mesmo critério deve ser utilizado
para analisar dos dados obtidos na saída.
2.2.4 Teste de caminho básico
O teste de caminho básico é conhecido como uma técnica de teste de
caixa branca. Através do teste de caminho básico é possível definir os diversos
caminhos de execução do software e garantir que cada um deles seja executado
ao menos uma vez (PRESSMAN, 1995). O número de caminhos é proporcional
ao tamanho e complexidade do software. Devido aos módulos não trabalharem
sozinhos, ou seja, são apenas partes de um sistema, fica inviável a utilização dos
testes de estrutura. Dessa forma, testes de caminho básico são mais utilizados
nos testes de unidade (SOMMERVILLE, 2003).
22
Antes de executar o teste de caminho básico é importante criar o modelo
de representação do trecho de código a ser testado, para facilitar o entendimento
do fluxo de controle. Nessa representação são utilizados fluxogramas e grafos. A
figura 01 mostra um fluxograma simplificado de um teste de caminho básico.
1
2
3
4
6
7
8
5
9
10
11
Figura 01. Fluxograma de um teste do caminho básico
O fluxograma da figura 1 pode ser facilmente representado em forma de grafos de
fluxo, confira na figura 02.
1
2,3
6
7
4,5
8
9
10
11
Figura 02. Grafo de fluxo do fluxograma da figura 1.
23
As linhas do grafo traçam os possíveis caminhos. É importante que a cada
laço o teste utilize um caminho independente até que todos os caminhos sejam
percorridos. Caminhos independentes são aqueles que executam instruções que
ainda não foram executadas (PRESSMAN, 1995).
Para encontrar o número de caminhos independentes é necessário realizar
o cálculo da complexidade ciclomática do grafo de fluxo do programa, esse
cálculo é feito através da fórmula (SOMMERVILLE, 2003):
CC(G) = Número (ramos) – Número (nós) + 2,
sendo as setas correspondentes aos ramos e os círculos correspondentes
aos nós, para o grafo de fluxo na figura 02, CC(G) = 11 – 9 + 2 = 4. Quatro é o
número de caminhos independentes.
Caminhos independentes:
Caminho 1: 1-11
Caminho 2: 1-2-3-4-5-10-1-11
Caminho 3: 1-2-4-6-8-9-10-1-11
Caminho 4: 1-2-3-6-7-9-10-1-11
Caminho não independente:
Caminho 5: 1-2-3-4-5-10-1-2-3-6-8-9-10-1-11
O caminho 5 não é independente porque todas as suas instruções já foram
executadas anteriormente, ou seja, não encontrou instruções ainda não
executadas.
Pressman (1995) mostra duas outras maneiras de encontrar o número de
caminhos independentes, além da fórmula apresentada por Sommerville (2003).
Uma é encontrando o número de regiões (fechamento dos nós), que corresponde
ao número de caminhos. A outra, o número de nós predicativos (nó que apresenta
condicional) mais um corresponde ao número de caminhos independentes. De
acordo com o grafo de fluxo:
CC(G) = Ramos – Nós + 2 = 11 – 9 + 2 = 4
CC(G) = Nós predicativos + 1 = 3 + 1 = 4
CC(G) = Regiões = 4
24
Genericamente, o teste de caminho básico não testa todas as combinações
possíveis de todos os caminhos do software, pois, devido ao grande número de
laços de repetições existentes, a quantidade de combinações de caminhos é
infinita (SOMMERVILLE, 2003).
O teste de caminho básico é simples e eficaz, e ainda pode ser
complementado por outros tipos de testes, para, assim, encontrar e corrigir o
maior número possível de falhas, são eles o teste de condição, teste de fluxo de
dados e o teste de laços (PRESSMAN, 1995).
2.2.5 Teste de condição
O teste de condição é um método de projeto de casos de testes
responsável por certificar que todas as condições lógicas de um módulo do
programa funcionem corretamente. Uma condição simples pode ser definida
como duas expressões aritméticas separadas por um operador relacional
(PRESSMAN, 1995):
E1 <operador relacional> E2,
em que E1 e E2 são as expressões aritméticas e o operador relacional sendo
qualquer um desses (<, ≤, =, ≠, >, ≥) ou ainda operadores booleanos (OR, AND e
NOT). Uma condição composta é definida com a união de duas ou mais
condições simples.
O teste de condição consiste em testar cada condição existente no
programa à procura de erros. Uma condição é considerada incorreta quando pelo
menos um componente da condição está incorreto, ou seja, operadores lógicos
ou booleanos ausentes ou a mais. O mesmo acontece com os parênteses, além
de erros na própria expressão aritmética ou operador relacional.
Para encontrar erros nos operadores relacionais, três testes são realizados,
considerando que as expressões aritméticas estejam corretas, a E1 deve receber
valores, maior, menor e igual a E2.
25
2.2.6 Teste de laços
Normalmente o número de estruturas de repetição, laços ou loops, como
são conhecidos, utilizados na implementação de sistemas é muito grande, mesmo
em pequenos softwares, isso porque essas estruturas são extremamente
necessárias, ou melhor, é inviável a construção de um sistema sem a utilização
de estruturas de repetição.
O teste de laços consiste em validar a estrutura dos laços. Existem quatro
classes de laços: laços simples, laços concatenados, laços aninhados e laços
não-estruturados (PRESSMAN, 1995):
ƒ
Laços simples: cinco testes são realizados, 1) não entrando no laço; 2)
somente uma passagem pelo laço; 3) duas passagens pelo laço; 4) enquanto
m<n passagens pelo laço; e 5) n-1, n, n+1 passagens pelo laço.
ƒ
Laços aninhados: deve-se testar primeiro o laço interno e iniciar com valores
mínimos todos os laços; execute os testes de laços simples no laço interno
sem alterar os valores do laço externo; continue realizando testes com valores
inaceitáveis. Repita até que todos os laços sejam testados.
ƒ
Laços concatenados: se os laços foram implementados de maneira individual,
um independe do outro, o teste realizado é o mesmo do mostrado
anteriormente no laço simples. Se os laços foram implementados de forma
que um interaja com outro, ou seja, se o contador do primeiro laço for utilizado
como valor inicial para o segundo laço, o teste a ser realizado é o mesmo
mostrado anteriormente no laço concatenado.
ƒ
Laços não-estruturados: para facilitar o teste é necessário reprojetar os grupos
de laços de forma que os mesmos se assemelhem à programação
estruturada.
26
Figura 03. Tipos de laços – retirada de (MARIATH, 2004).
2.2.7 Teste unitário
O teste unitário consiste em validar a menor unidade do projeto, uma
classe de métodos, funções isoladas e unidades lógicas. A partir disso, testa-se
módulos integrados a outros e outros, até que se tenha o sistema completo. O
mesmo deve ser realizado diversas vezes, dia a dia, isso porque as falhas sempre
aparecem e fica mais fácil corrigir pequenas falhas do que corrigir o sistema
inteiro depois de construído (MEDEIROS, 2005).
Medeiros (2005) afirma que a importância do teste unitário se dá na
prevenção de futuros erros no sistema. A única maneira de saber se o código é
realmente bom é testando sua reação diante de uma bateria de testes exaustivos,
esses testes validam situações normais, que deveriam ser bem sucedidas e
situações de erro.
O mesmo autor ainda cita um exemplo do mundo real: pensando na
fundamental importância do teste unitário, imagine o resultado dos testes em um
avião ou mesmo em um navio apenas após o término de sua construção. Se algo
27
sair errado, os danos podem ser irreparáveis. Nada mais apropriado para
descrever o resultado, do que uma tragédia.
2.2.8 Testes de verificação e validação de software
Toda a fase de teste de um software refere-se ao (V&V) ou verificação e
validação. A verificação consiste em atividades que garantem que as funções do
sistema tenham sido implementadas corretamente, e a validação em verificar que
o software corresponde às expectativas do cliente (PRESSMAN, 1995).
Segundo Sommerville (2003), os testes de validação ou verificação são
responsáveis por constatar se o sistema desenvolvido está realmente de acordo
com as especificações fornecidas pelo cliente e se satisfaz as suas necessidades.
Os testes de validação devem evoluir de acordo com o desenvolvimento do
software, não sendo executados de maneira isolada, ou seja, sempre que um
módulo ou uma classe é desenvolvido, testes são realizados. Os testes são
incrementados a cada etapa de desenvolvimento, mais classes e funções
implementadas, mais testes realizados.
Para
que
os
erros
sejam
encontrados
no
início
do
processo,
resumidamente Sommerville (2003) divide os testes de validação em 5 (cinco)
estágios, sendo eles teste de unidade, teste de módulo, teste de subsistema,
teste de sistema e teste de aceitação.
ƒ
Teste de unidade: tem como objetivo averiguar que um componente do
sistema está funcionando corretamente, para isso o teste é realizado em cada
componente separadamente.
ƒ
Teste de módulo: num sistema, um módulo corresponde a uma classe, um
conjunto de métodos ou funções interligadas, ou seja, dependentes uma da
outra. O teste é realizado visando verificar que o módulo todo está
funcionando corretamente, se as conexões entre os métodos estão corretas e
produzindo o esperado. O teste de módulo, assim como o teste de unidade, é
normalmente executado por seus próprios desenvolvedores.
28
ƒ
Teste de subsistemas: responsabiliza-se basicamente por testar pequenos
conjuntos de módulos integrados, o objetivo é encontrar erros de interface de
módulos e suas ligações.
ƒ
Teste de sistema: o sistema consiste na união coerente dos subsistemas, o
teste de sistema, por sua vez, consiste em descobrir possíveis falhas nas
interações desses módulos e, ainda, em verificar que todos os requisitos
funcionais ou não, estejam presentes.
ƒ
Teste de aceitação: o teste de aceitação é o último realizado antes da
entregado do software ao cliente. O teste é realizado utilizando valores reais
vindos dos próprios clientes. O teste de aceitação pode trazer à tona diversos
tipos de erros anteriormente não constatados, pois os dados reais criam
situações não pensadas pelos desenvolvedores. Através do teste de validação
é possível perceber se o sistema é estável e se possui um desempenho que
corresponda às expectativas do cliente.
Segundo Inthurn (2001), o teste de sistema se subdivide em quatro outros
tipos envolvendo todas as partes do software: Teste de recuperação; teste de
segurança; teste de stress; e, por último, teste de desempenho.
ƒ
Teste de recuperação: consiste em provocar diversos tipos de falhas, o
sistema, porém, deve ser capaz de tolerar as mesmas, ou seja, um sistema
tolerante a falhas é aquele consegue se recuperar de falhas sem que essas
atrapalhem o seu funcionamento. O sistema deve se recuperar das falhas
completamente num tempo relativamente pequeno e com o mínimo de
intervenção humana. A geração de um log detalhando as atividades realizadas
antes das falhas é interessante. Um dos objetivos do teste de recuperação é
averiguar a maneira como o sistema se recupera dos erros, os mais comuns
são, problemas no disco rígido, erros de interface e falhas referentes à falta de
memória.
ƒ
Teste de segurança: é responsável por verificar se todos os recursos de
defesa do sistema realmente impedem os possíveis acessos indevidos. Esse
tipo de teste deve ser realizado por uma equipe que não tenha participado do
desenvolvimento. O seu objetivo é garantir que o sistema continue
29
funcionando normalmente sob tentativas de acessos não permitidos. O correto
é que as políticas de segurança do próprio sistema se encarreguem de
protegê-lo desses acessos. Durante a execução dos testes, os responsáveis
por ele devem utilizar todos os métodos disponíveis para tentar, de alguma
forma, acessar o sistema indevidamente, neste caso, vale conseguir senhas
ilegalmente, tentativas de acesso durante o processo de recuperação do
sistema, entre outras ocasiões de falhas provocadas pelos próprios
testadores.
ƒ
Teste de stress: é realizado com o objetivo de forçar o sistema, por exemplo,
executar várias buscas de dados no disco, abrir o maior número de janelas
possível a fim de causar falta de memória. Enquanto isso, deve-se analisar a
queda de desempenho do sistema e a maneira como ele se recupera, em
caso de parada do sistema durante o processo, mais testes de recuperação
devem ser executados.
ƒ
Teste de desempenho: como o próprio nome já diz, é responsável por testar o
desempenho do sistema, pode ser realizado em conjunto com o teste de
stress. O objetivo desse tipo de teste é garantir que os requisitos de
desempenho relatados durante o planejamento sejam alcançados. O teste de
desempenho analisa o tempo de resposta das atividades, a capacidade do
banco de dados, os momentos em que o sistema perde mais desempenho em
função de um grande número de processos executando, dentre outros. Esse
teste pode ser realizado executando alguma instrução inúmeras vezes, assim,
é possível observar se o desempenho cai ou não.
2.2.9 Teste de integração
Durante o desenvolvimento do sistema os gerentes decidem quem ficará
responsável por cada fase de teste, normalmente cada programador se encarrega
de testar seu próprio código, ao final, as partes desenvolvidas por cada
programador são passadas a outra equipe que irá unificar os códigos. Essa
equipe realiza a integração do software e mais testes são feitos. Os testes são
30
realizados em partes separadas ou subsistemas e no sistema como um todo, por
fim o teste é detalhadamente documentado e arquivado (SOMMERVILLE, 2003).
Um dos objetivos do teste de integração é detectar problemas junto às
interfaces, ou seja, erros que aparecem com a junção das partes do sistema.
Antes da junção são realizados testes nos pedaços individuais do sistema, os
erros encontrados são corrigidos, outros erros aparentemente “menores”, nem
são notados. Mais tarde, com as partes do sistema integradas, aqueles erros
“menores” podem se apresentar não tão pequenos assim, e causar falhas
inaceitáveis no sistema (PRESSMAN, 1995).
Basicamente, o teste de integração consiste em verificar se as partes
testadas individualmente funcionam também unidas umas as outras, (INTHURN,
2001).
Pressman (1995) define dois tipos de integração de sistemas: a nãoincremental e a incremental. A primeira consiste em desenvolver todos os
módulos do sistema, realizar a junção dos mesmos, e só depois iniciar a fase de
testes. Normalmente os erros são tantos e a dificuldade de correção é tamanha
que o resultado é um sistema completamente inaceitável. O teste de integração
incremental tende a assegurar que o sistema possua uma quantidade menor de
erros, isso se faz possível pelo fato de que cada módulo do sistema é testado
individualmente antes da junção com os demais módulos, com isso fica mais fácil
detectar e corrigir os erros existentes. Após a junção novos testes são realizados
para detectar outros erros que possam aparecer. Um módulo pode funcionar
corretamente quando testado sozinho, e não funcionar normalmente quando
integrado a outros módulos, por isso a importância dos testes antes e depois da
integração.
2.2.9.1 Integração Top-down
É uma abordagem da integração incremental. Segue uma ordem
hierárquica, de cima para baixo. Os módulos mais importantes do sistema são
desenvolvidos primeiro, depois os menos importantes. Após desenvolvido, cada
módulo é testado individualmente, integrado aos outros módulos e testado
31
novamente, assim, é possível conferir se a interface com os demais módulos
continua compatível.
2.2.9.2 Integração Bottom-up
A ordem de teste dos módulos é inversa à top-down, os módulos menos
importantes, ou de níveis mais baixos da hierarquia são desenvolvidos e testados
primeiro, depois os próximos níveis a cima na hierarquia até que o último módulo
seja testado.
2.2.10 Depuração de software
A depuração ou debugging ocorre quando um caso de teste encontra e
mostra erros do sistema, ou seja, para que se faça a depuração é necessário que
os testes realizados tenham obtido êxito.
A depuração consiste em analisar os resultados obtidos com os esperados,
e, no caso de diferença entre eles, encontrar a causa, ou seja, encontrar o motivo
da não correspondência entre os dados reais e os esperados, e, por fim, corrigir o
problema (PRESSMAN, 1995). Em resumo, a depuração objetiva encontrar e
corrigir a causa, ou causas, dos erros no sistema.
2.3 Linguagem de Programação Java
Java é uma linguagem de programação de alto nível, ideal para o
desenvolvimento de aplicativos baseados na Internet, dispositivos que utilizam
uma rede como meio de comunicação, rede tradicional ou sem fio. A linguagem
Java se popularizou muito rapidamente e se tornou a principal linguagem de
desenvolvimento utilizada em projetos de grande porte e, também, em estudos
32
didáticos, sendo adotada como uma boa linguagem orientada a objetos (DEITEL,
2003).
A proposta da linguagem Java foi juntar clareza, desempenho e
organização numa linguagem, a fim de que a mesma seja flexível e produza
resultados rápidos independente dos recursos de hardware, não exigindo
hardware de ponta. Desde o de seu surgimento em 1995, a linguagem Java
apenas cresceu, hoje fornece os mais diversos tipos de aplicações destinadas
aos usuários finais, dentre elas estão: aplicações para aparelhos celulares,
pagers, entre outros (DEITEL, 2003).
Java possibilitou uma mudança na maneira de desenvolver sites, a
utilização de conteúdos dinâmicos fez com que os sites ficassem mais completos,
por exempo, áudio, vídeos e animações foram adicionados. A interação com os
usuários foi intensificada.
A linguagem de programação Java é baseada em classes e orientação a
objetos, o que possibilita mais fácil a reutilização do código. Dentre as vantagens
de se utilizar essa linguagem estão: o fato de ser multiplataforma; não exigir
muitos recursos de hardware; ser totalmente orientada a objetos; prover
segurança e alto desempenho ao sistema.
Outra vantagem é o próprio JUnit, que se integra muito facilmente à
linguagem, é necessário apenas efetuar o download do arquivo junit.zip no site
www.junit.org, descompactá-lo na mesma pasta que contém o código Java criado,
compilar e executar o programa.
2.4 JUnit
JUnit é um framework de código-aberto utilizado para facilitar a elaboração
e execução de testes de softwares em Java. Ele permite a criação de classes que
contêm módulos responsáveis por testar determinadas funções do sistema. Um
dos objetivos do JUnit é prover a criação de casos de teste que sejam eficazes.
Os testes podem ser executados em partes separadas do sistema, em partes
integradas ou em todo o sistema de uma só vez. Claro que, se os testes forem
executados apenas nas etapas finais do desenvolvimento, a probabilidade de
33
encontrar erros graves e de difícil correção é muito grande. O ideal é que para
cada módulo implementado, um módulo de testes seja desenvolvido para se
testar o mesmo (OLIVEIRA, 2005).
Oliveira (2005) afirma, ainda, que o JUnit permite que seus módulos de
testes sejam reutilizados em outros projetos, o que garante menores esforços em
futuros testes.
Para se obter bons casos de testes, é necessário ter em mãos boas
ferramentas, no caso, um framework para a realização de testes unitários. Isso
deve estar presente na cabeça dos desenvolvedores, pois os testes são
fundamentais e é necessário tempo para planejá-los e executá-los de forma
adequada. Vale lembrar que o retorno que o teste proporciona é muitas vezes
maior do que o gasto na sua elaboração (TECHNOLOGIES, 2004).
O JUnit framework foi criado por Eric Gamma e Kent Beck. Gamma é um
dos autores de um dos melhores livros sobre padrões de projetos e Beck é
conhecido pelos trabalhos envolvendo padrões e programação XP (WIKIPÉDIA,
2006). Através do JUnit é possível testar um código-fonte desenvolvido na
linguagem de programação Java antes de terminá-lo, ou seja, o teste é realizado
na estrutura interna do sistema. Logo, é um teste do tipo caixa branca que permite
a detecção para posterior correção de erros em métodos e objetos.
O JUnit fornece um conjunto de classes que ajudam na detecção e
remoção de erros no software, com essas classes é possível criar aplicações com
representações gráficas para apresentação e remoção dos problemas no sistema.
O uso do JUnit é favorecido por diversos motivos: pode verificar se cada
unidade do software, por menor que seja, está funcionando adequadamente;
facilita na elaboração dos testes, na representação e principalmente correção dos
erros, isso passa a ser feito automaticamente; é orientado a objetos; é gratuito
(MEDEIROS, 2005).
Para melhor entender o JUnit é preciso conhecer o teste unitário, descrito
anteriormente, na subseção 2.2.7, pois o JUnit executa testes para cada método,
como o teste unitário.
Outras
motivações
para
utilizar
o
JUnit
aparecem
porque
os
desenvolvedores sabem que o código gerado deve necessariamente ser testado
e porque o JUnit possui algumas regras já definidas de apresentação dos erros e
34
possibilidade
de
automação,
ou
seja,
testes
podem
ser
realizados
automaticamente, o que facilita na execução dos testes sempre que necessário
(BARROS, 2006).
Barros (2006) resume o JUnit a uma classe pai de todos os casos de
testes, que, por sua vez, são chamados “TestCase”, e possuem várias aplicações
que executam os testes, geram relatórios com os resultados, entre outros.
O JUnit permite encontrar o erro e mostrar exatamente onde o mesmo
ocorreu, assim, a dificuldade de detecção e correção da falha é reduzida ao
mínimo.
Para construir os testes é utilizada a API do JUnit “junit.framework.*” que
fornece classes que permitem a criação dos testes, algumas delas são: Test,
TestCase, TestSuite. Para executar os testes são utilizados os métodos de
asserções: assertTrue(), assertEquals(), fail, entre outras. A visualização dos
resultados do teste é feita através da aplicação “TestRunner”. O TestRunner é
responsável pela interface com os usuários, ele pode ser executado nas versões
Text, apenas texto “console”, AWT e Swing, ambos gráficos, sendo o segundo
um pouco mais completo que o primeiro. O TestRunner permite executar os
testes individualmente ou em conjunto na forma de suítes e, ao final, faz a
apresentação dos resultados, tanto no caso falhas quanto de sucesso. Ainda
existe o “TestResult”, que é responsável por armazenar os resultados dos testes
(USP, 2004).
Para cada classe a ser testada, deve-se criar uma classe de teste
correspondente, ou seja, se a classe a ser testada é denominada “Conta”, a
classe de teste deve se chamar “ContaTest”, assim facilita a identificação das
mesmas. A classe de teste deve sempre herdar da classe “TestCase” do
framework JUnit, como pode ser observado no código apresentado a seguir:
import junit.framework.*;
public class ContaTest extends TestCase {
public ContaTest(String name) {
super(name);
}
}
...
35
Os métodos pertencentes à classe de teste também devem seguir esse
padrão, construir os métodos de teste utilizando “test” no início da nomenclatura,
alguns exemplos da assinatura de métodos “test” são:
–
public void testCredito()
–
public void testDebito()
–
public void testCreditoNegativo()
–
public void testDebitoNegativo()
Após a criação dos métodos de testes, deve-se definir o comportamento de
cada teste, isso é feito utilizando os seguintes métodos:
–
assertEquals(objetoEsperado, objetoTeste);
–
assertTrue(expressaoBooleana);
–
assertNull(objeto);
–
assertSame(objeto a, objeto b);
–
fail(mensagem);
As asserções representam as afirmações, assim, em caso de resultados
incorretos as falhas são apontadas. Em assertSame() por exemplo, os dois
objetos inseridos devem ser iguais para que o resultado dê positivo, caso não
sejam iguais, o JUnit apontará uma falha.
Existem ainda dois métodos que possibilitam a execução de configurações
antes e depois dos métodos de testes, ou seja, algo que seja comum aos
métodos, como por exemplo, a abertura e o fechamento de conexão com o banco
de dados. O primeiro, setUp(), é utilizado quando se deseja executar instruções
antes dos métodos de teste, e o segundo, tearDown(), é utilizado quando se quer
executar instruções depois dos métodos de testes (USP, 2004).
Pensando na automatização dos testes, o JUnit define a classe TestSuite,
que permite a inclusão de vários métodos de teste, formando um conjunto de
testes organizados para serem executados sequencialmente. Esse conjunto de
métodos de testes é chamado suíte de testes. O TestSuite permite também definir
36
uma classe que execute todas as suítes de testes. Assim, a execução dos testes
é feita de maneira automática, o TestSuite vai executado cada suíte de teste
sequencialmente, que por sua vez executa os métodos de testes (USP, 2004).
Outra característica do JUnit é que ele permite a correção de erros em
tempo de execução, ou seja, quando um erro é encontrado, é possível acessar o
código, realizar as eventuais alterações corretivas e compilar a classe novamente,
sem que o JUnit encerre seu processo, basta dar partida no método de teste
novamente clicando no botão Run, o usuário pode escolher se o teste será
executado novamente em apenas um dos métodos ou em toda a classe de testes.
2.5 Estudo de caso
O caso de uso em questão não constitui um sistema em si, é formado
basicamente por duas classes avulsas construídas para mostrar na prática
algumas aplicações comuns do JUnit. A figura 04 mostra o diagrama de classes.
Ordenacao
Calculos
num : Integer
Calculos()
Calculos(valor : Integer)
fatorial(valor : Integer)
maiorNumero(v1 : Integer, v2 : Integer, v3 : Integer)
par(valor : Integer)
aux : Integer
i : Integer
j : Integer
Ordenacao()
InsertSort(vet[] : Integer, n : Integer)
BoubleSort(vet[] : Integer, n : Integer)
Figura 04: Diagrama de Classes
As duas classes escolhidas para serem testadas são constituídas de
métodos que realizam cálculos lógicos e matemáticos, e, ainda, métodos que
realizam a ordenação de elementos em vetores. A primeira: Calculos.class,
possui dois métodos construtores, um que não recebe nada como parâmetro e
outro que recebe um elemento inteiro como parâmetro. Essa classe possui, ainda,
mais quatro métodos que realizam pequenos cálculos. Um deles recebe um valor
inteiro e retorna o cálculo de seu fatorial, outro, recebe três números inteiros e
retorna o maior deles, um terceiro método recebe um valor inteiro e retorna true
no caso do valor recebido ser um número inteiro par e false se o mesmo for
ímpar. A segunda classe: Ordenacao.class possui três métodos, um construtor,
37
que inicializa as variáveis, um que recebe um vetor de inteiros e o número de
elementos do vetor, e retorna uma String com os elementos do vetor ordenado
utilizando a técnica InsertSort e outro com as mesmas características, porém,
utilizando a técnica BoubleSort para ordenar o vetor.
O teste do caminho básico será executado nos métodos da duas classes
visando encontrar erros de implementação e os casos de testes serão elaborados
e executados com o JUnit para analisar os resultados produzidos nas saídas dos
métodos, também das duas classes. Sendo que o JUnit será executado
primeiramente nas classes separadas e depois será desenvolvida um suíte de
testes para testar as duas classes juntas.
Vale ressaltar que o efeito do teste é o mesmo, tanto em métodos
pequenos e simples quanto em métodos mais complexos, pois da maneira como
foi implementado, apenas os dados de saída dos mesmos são analisados pelo
JUnit.
38
3
MATERIAIS E MÉTODOS
Esta seção consiste em apresentar as ferramentas e os métodos utilizados
no desenvolvimento desse projeto.
3.1 Materiais
A maior parte desse trabalho foi desenvolvida com o auxilio de um
Notebook Acer 3003LCi com processador AMD Sempron 3000+, HD de 40 GB,
512 MB de memória e sistema operacional Microsoft Windows XP Profissional.
Foram utilizados também os laboratórios de informática do CEULP/ULBRA para
navegar na Internet à procura de conteúdos relacionados aos abordados no
trabalho.
3.1.1 Linguagem de Programação
A intenção do trabalho foi voltada para a ferramenta de elaboração e
execução de testes em Java, o JUnit. Logo a linguagem de programação utilizada
para implementar as classes do programa foi Java. Essa linguagem foi utilizada
também para implementar as classes de teste, possibilitando a realização
automatizada dos mesmos.
39
3.1.2 JUnit
O principal objetivo do trabalho foi o estudo e a aplicação da ferramenta
JUnit. Ela permitiu que casos de testes fossem criados e executados e, através
dela, foi possível testar o código, constatar as falhas e observar exatamente onde
elas ocorreram. Os resultados dos testes podem ser visualizados de maneira
simples e rápida, em modo visual ou em modo texto no console.
Essa ferramenta possibilitou a automatização dos testes e está disponível
para download gratuitamente no site: www.junit.org.
3.1.3 JCreator LE
Além, da linguagem de programação Java, foi utilizado também o JCreator
LE, que é uma ferramenta de desenvolvimento bastante utilizada no meio
acadêmico, requer pouco espaço no disco rígido, serve como editor que permite
facilmente a manipulação do código Java. O JCreator LE foi desenvolvido na
linguagem C++, não exige muito do hardware, é bem fácil de encontrar, instalar e
utilizar. Um ponto importante do JCreator LE é que a sua interface é muito
amigável, o que facilita a sua utilização. A mesma é ainda, multiplataforma e
gratuita. O download dessa ferramenta pode ser feito no site: www.jcreator.com.
3.2 Métodos
O principal método utilizado para o desenvolvimento desse trabalho foi a
pesquisa, tanto em livros, quanto em sites disponíveis na Internet. Foram
analisados também trabalhos desenvolvidos anteriormente por outros colegas.
Além de tudo disso, as orientações de alguns colegas e principalmente da
professora Cristina Filipakis foram especialmente utilizadas.
40
3.2.1 Pesquisa Bibliográfica
Grande parte das pesquisas foi realizada nos livros disponibilizados pela
biblioteca do próprio CEULP/ULBRA, também em outros oferecidos pela
orientadora e ainda, por outros próprios. Outra fonte de pesquisa importantíssima
foi a Internet, ela contribuiu bastante para o desenvolvimento do projeto, não só
como meio de acesso às informações, mas também como principal canal de
comunicação para orientação.
41
4
RESULTADOS E DISCUSSÕES
Esta seção consiste em apresentar a elaboração e execução dos testes do
caminho básico e testes com o JUnit, bem como seus resultados.
Para testar as classes Calculos.class e Ordenacao.class foram
elaborados testes do caminho básico e implementadas as classes de testes
CalculosTest.class e OrdenacaoTest.class, que são subclasses de TestCase
que é uma classe do framework JUnit.
A primeira classe possui três métodos a serem testados: fatorial(),
maiorNumero() e par(). O processo de teste utilizando a técnica do teste do
caminho básico no primeiro método é o seguinte: primeiro deve-se numerar o
programa, como pode-se observar a seguir:
Código numerado:
public int fatorial(int valor){
int fat=1;
1
if (valor<0){
System.out.println("Valor Negativo!!! Não é válido!!!");
fat=-1;
}
else
3
4
if (valor==0 || valor==1)
return fat;
else
5
2
42
6
while (valor>0){
valor--;
7
fat+=(fat*valor);
}
8
return fat;
}
9
O próximo passo consiste em desenhar o grafo para visualização dos
caminhos a serem executados, conforme a figura 05:
1
3
4
6
2
5
7
9
8
Figura 05: Grafo do método fatorial()
Na seqüência, deve-se calcular a complexidade ciclomática para se
certificar que o número de caminhos está correto. Viu-se na seção 2.2.4 a
maneira correta de realizar esse cálculo. Ele pode ser feito de três maneiras:
CC(G) = Ramos – Nós + 2 = 12 – 9 + 2 = 5
CC(G) = Nós predicativos + 1 = 4 + 1 = 5
CC(G) = Regiões = 5
O resultado comum aos três cálculos indica maior probabilidade do grafo
estar correto.
Por último, deve-se descrever os possíveis caminhos e valores de entrada,
como é mostrado a seguir:
43
Caminhos Básicos
1. 1, 2, 9
2. 1, 3, 5, 9
3. 1, 3, 4, 5, 9
4. 1, 3, 4, 6, 9
5. 1, 3, 4, 6, 7, 8, ..., 6, 9
Entradas
1. valor = -2 Æ -1 "Valor Negativo!!! Não é válido!!!"
2. valor = 0 Æ 1
3. valor = 1 Æ 1
4. Inválido. Deveria entrar no “while”
5. valor = 5 Æ 120
Dessa forma, encontram-se e percorrem-se todos os possíveis caminhos
do método. A próxima etapa consiste em automatizar os testes utilizando o JUnit.
A figura 06 mostra o código dos casos de testes construídos para testar o
método fatorial().
Figura 06: Código do método testFatorial().
Os métodos de asserções consistem em afirmar que suas instruções são
verdadeiras, como se observa nas linhas de 16 a 19, ou seja, se o valor inserido
no método for negativo o retorno é “-1”, se for 0 ou 1, o retorno é “1”, e se for um
número positivo maior que 1 o retorno é seu fatorial. Neste caso, o fatorial de 5 é
120, então o resultado do teste deve ser positivo, caso não seja, o método possui
erros.
Ao executar a classe de teste, o usuário pode escolher uma entre as três
versões de apresentação dos resultados disponibilizadas pelo JUnit, como mostra
a figura 07.
44
Figura 07: Menu de escolha da versão de apresentação dos resultados
O Resultado da execução dos testes desse método utilizando a versão
Swing é exibido na figura 08.
Figura 08: Resultado dos testes no método fatorial().
A barra verde indica que todos os testes executados foram bem sucedidos,
ou seja, o método testado está trabalhando corretamente. Runs indica o número
de métodos testados na classe. Errors mostra o número de erros encontrados.
Failures mostra a quantidade de testes que falharam.
45
O resultado do mesmo teste reapresentado com a versão AWT está
apresentado na figura 09 e com a versão Text na figura 10.
Figura 09: Resultado dos testes no método fatorial().
Figura 10: Resultado dos testes no método fatorial().
46
Para aumentar a probabilidade de encontrar erros no método é muito
interessante executar testes em que o resultado seja negativo. A figura 11 exibe o
código do método testFatorial() alterado a fim de gerar um resultado negativo.
Figura 11: Código do método testFatorial() alterado.
A instrução assertTrue() (linha 16) afirma que o fatorial de 5 é igual a 150,
como de fato não é, o teste necessariamente tem que ser negativo. A figura 12
exibe o resultado do teste.
Figura 12: Resultado do teste negativo no método fatorial().
Os demais testes seguem a mesma lógica do anteriormente apresentado.
47
O próximo método maiorNumero() foi implementado e numerado como
mostra a figura xx.
public int maiorNumero(int v1, int v2, int v3){
int maior=0;
1
2
if (v1>v2 && v1>v3)
maior=v1;
else
3
4
5
if (v2>v1 && v2>v3)
maior=v2;
6
maior=v3;
7
else
return maior;
}
8
9
A figura 13 exibe o grafo referente código exibido anteriormente.
1
4
2
5
3
6
7
8
9
Figura 13: Grafo do método maiorNumero().
CC(G) = Ramos – Nós + 2 = 11 – 9 + 2 = 4
CC(G) = Nós predicativos + 1 = 3 + 1 = 4
CC(G) = Regiões = 4
48
Caminhos Básicos
1. 1, 4, 7, 8, 9
2. 1, 2, 3, 8, 9
3. 1, 4, 5, 6, 8, 9
4. 1, 2, 4, 5, 6, 8, 9
Entradas
1. v1 = 33, v2 = 33, v3 = 99 Æ 99
2. v1 = 99, v2 = 33, v3 = 51 Æ 99
3. v1 = 33, v2 = 99, v3 = 51 Æ 99
4. v1 = 51, v2 = 99, v3 = 33 Æ 99
Segue com o código do método testMaiorNumero() na figura 14.
Figura 14: Código do método testMaiorNumero().
Ao contrário do método assertTrue(), o assertFalse() (linhas 24, 25 e 26),
afirma que suas instruções são falsas, ou seja, sabe-se que o maior dos valores
inseridos é o 99, o método em questão consiste em retornar o maior, no caso 99.
Nota-se que na linha 23 o assertTrue() afirma que o maior número é de fato 99, e
nas demais linhas que o número maior não é 99, logo, o resultado deve ser
positivo, a não ser que haja problemas no funcionamento do método. A figura 15
mostra o resultado desse teste.
49
Figura 15: Resultado do testes no método maiorNumero().
Novamente, observa-se através da barra verde que o resultado do teste é
positivo, logo, o método funciona. A figura 16 exibe o código modificado para
causar erro e a figura 17 mostra o resultado negativo.
Figura 16: Código do método testMaiorNumero() alterado.
Percebe-se que o resultado do teste necessariamente será negativo, pois o
assertTrue() (linha 23) afirma que o maior número do conjunto é 51, quando de
fato é 99.
50
Figura 17: Resultado do teste negativo no método maiorNumero().
Com os resultados obtidos constata-se que o método testado funciona
normalmente.
Segue o último teste da classe Cálculos.class, a ser realizado no método
par() que deve recebe um valor inteiro e retornar um valor booleano true ou false,
o primeiro no caso do valor recebido ser um número par e o segundo se o mesmo
for ímpar. Abaixo o código numerado:
public boolean par(int valor){
if (valor % 2==0)
return true;
1
2
else
return false;
}
4
3
51
Observa-se na figura 18 o grafo do método.
1
3
2
4
Figura 18: Grafo do método par().
Segue os demais passos:
CC(G) = Ramos – Nós + 2 = 4 – 4 + 2 = 2
CC(G) = Nós predicativos + 1 = 1 + 1 = 2
CC(G) = Regiões = 2
Caminhos Básicos
1. 1, 3, 4
2. 1, 2, 4
Entradas
1. valor = 3 Æ false
2. valor = 6 Æ true
A figura 19 exibe o código desenvolvido do método testPar().
Figura 19: Código do método testPar().
52
No JUnit, a forma mais adequada de testar esse tipo de saída é utilizar a
asserção assertEquals() (linhas 27 e 28). Sua função compara se dois objetos
são iguais, sabe-se que 6 é um número inteiro par, então compara-se com true,
sabe-se também que 3 é um número inteiro ímpar, logo compara-o com false,
para certificar-se que o método foi construído corretamente. A figura 20 exibe o
resultado do teste.
Figura 20: Resultado do teste no método par().
Na seqüência, observa-se na figura 21 o código do método testPar()
alterado, forçando ao erro, e na figura 22 o resultado negativo do teste.
Figura 21: Código do método testPar() alterado.
53
Figura 22: Resultado do teste negativo no método par().
Sabe-se que o JUnit permite que todos os métodos de uma classe de teste
sejam executados de uma só vez, isso é observado na figura 23.
Figura 23: Resultado dos testes da classe Cálculos
54
É necessário, ainda, testar a classe Ordenação.class, que possui dois
métodos, ambos ordenam um vetor de inteiros, um com a técnica Insert-Sort e
outro com a técnica Bouble-Sort. O primeiro, insertSort(), tem seu código
numerado a seguir:
public String InsertSort(int vet[], int n){
for(i=0;i<n;i++){
2
aux=vet[i];
3
4
for (j=i-1;j>=0 && aux<vet[j];j--){
vet[j+1]=vet[j];
}
5
6
vet[j+1]=aux;
}
7
8
for(i=0;i<vet.length;i++)
ordenado+=" " +vet[i];
9
return ordenado;
}
10
A figura 24 representa o grafo do código apresentado anteriormente.
55
1
8
2
9
3
7
10
4
5
6
Figura 24: Grafo do método insertSort().
Continuando com teste do caminho básico, necessita-se calcular a
complexidade ciclomática, encontrar os caminhos básicos e os dados de entrada:
CC(G) = Ramos – Nós + 2 = 13 – 10 + 2 = 5
CC(G) = Nós predicativos + 1 = 4 + 1 = 5
CC(G) = Regiões = 5
Caminhos Básicos
1. 1, 8, 10
2. 1, 2, 3, 7, ..., 1, 8, 10
3. 1, 2, 3, 4, 7, ..., 1, 8, 9, ..., 8, 10
4. 1, 2, 3, 4, 5, 6, ..., 3, 7, ..., 1, 8, 9, ..., 8, 10
5. 1, 2, 3, 4, 5, 6, ..., 3, 7, ..., 1, 8, 10
56
Entradas
1. vet = {}, n = 0 Æ “vazio”
2. vet = {2}, n = 1 Æ 2
3. vet = {2,3}, n = 2 Æ 2,3
4. vet = {2,4,3}, n = 3 Æ 2,3,4
5. Inválido. Deveria entrar no último “for”.
Na seqüência, executa-se os testes com o JUnit. A figura 25 mostra o
código dos métodos testInsertSort() e testBoubleSort().
Figura 25: Código dos métodos testInsertSort() e testBoubleSort().
Observa-se que os dois casos de testes possuem estruturas semelhantes,
o que mostra a possibilidade do reaproveitamento do código em outros casos.
Nas linhas 20, 22, 24 e 26 são realizadas as afirmações, ou seja, se o
resultado for positivo, significa que o método funciona bem. O mesmo acontece
no método seguinte.
Abaixo a execução completa do teste do caminho básico do método
boubleSort().
57
public String BoubleSort(int vet[],int n){
1
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
2
if(vet[i]>vet[j]){
3
aux=vet[i];
vet[i]=vet[j];
4
vet[j]=aux;
}
5
for(i=0;i<vet.length;i++)
ordenado+=" " +vet[i];
6
return ordenado;
}
7
1
5
7
2
6
3
4
Figura 26: Grafo do método boubleSort().
Caminhos Básicos
1. 1, 5, 7
2. 1, 2, 5, 6, ..., 5, 7
3. 1, 2, 3, 2, ..., 5, 6, ..., 5, 7
4. 1, 2, 3, 4, 2, ..., 5, 6, ..., 5, 7
5. 1, 2, 3, 4, 2, ..., 5, 7
58
Entradas
1. vet = {}, n=0 Æ “vazio”
2. vet = {5}, n=1 Æ 5
3. vet = {3,5}, n=2 Æ 3,5
4. vet = {3,1,5}, n=3 Æ 1,3,5
5. Inválido. Deveria entrar no último “for”.
O resultado dos testes com JUnit é finalmente exibido na figura 27.
Figura 27: Resultado dos testes nos métodos insertSort() e boubleSort().
Assim, como previsto, o resultado do teste foi positivo. Alterando a ordem
de qualquer número no assertEquals(), acarreta em erro, fazendo com que o
teste seja negativo, desta forma, constata-se que os métodos testados estão
59
funcionando corretamente. A figura 28 mostra o trecho do código alterado a fim de
gerar erros nos testes e a figura 29 mostra o resultado do teste.
Figura 28: Código do método testInsertSort() alterado.
Nota-se que o vetor criado na linha 19 não está ordenado em relação ao
que foi comparado no assertEquals (linha 20), o que causou o erro. O mesmo fato
pode ser transferido para o outro método de ordenação.
Figura 29: Resultado de teste negativo no método insertSort().
Outra característica do JUnit é disponibilizar dois métodos, setUp() e
tearDown(), neles são colocadas instruções que sejam comuns a todos os
60
métodos de teste, o primeiro é executado antes dos testes e o segundo após os
testes.
O JUnit disponibiliza, ainda, uma aplicação bastante útil, que permite
automatizar ainda mais a execução dos casos de teste. O TestSuite possibilita
que todas as classes de teste sejam executadas de uma só vez, sendo possível
estabelecer a seqüência com que os casos de testes serão executados. O
resultado de todos os testes é exibido ao final. A figura 30 mostra o resultado da
execução da classe TSuite.class, a mesma foi implementa para testar as classes
CalculosTest.class e OrdencaoTest.class juntas.
Figura 30: Resultado da Suíte de Testes.
Novamente todos os testes realizados comprovaram que os métodos estão
operando normalmente.
61
A última classe de testes implementada foi TSuite.class, que é
responsável por executar todos os testes juntos. A implementação dessa classe é
bastante simples, existe apenas um método no qual são adicionadas as classes
de testes, depois é só chamar o TestRunner. A figura 31 mostra o código
desenvolvido para essa classe.
Figura 31: Código da classe TSuite.class
Nas linhas 14 e 15 as classes de testes foram adicionadas, o TestRunner
foi chamado na linha 27 na versão Swing, na linha 30 na versão AWT e por
último na versão Text, na linha 32, possibilitando ao usuário a escolha da versão
no momento da execução dos testes.
Diversos testes foram elaborados e executados, percebendo-se aí a
eficácia do JUnit, pois em todos os métodos testados ele mostrou que realmente
estão funcionando corretamente. Prova maior do correto funcionamento dos
métodos foi quando o JUnit exibiu os resultados de testes negativos, pois os
mesmos foram propositais, servindo apenas para constatar que o JUnit é uma
ferramenta eficiente e que realmente encontra as falhas, quando elas existem.
Ao obter os resultados dos testes dados pela ferramenta JUnit, constata-se
que a mesma oferece muitas facilidades relacionadas a detecção de falhas,
contribuindo bastante com os profissionais responsáveis por testar o software. É
fácil entender o motivo pelo qual muitos desenvolvedores não testam
adequadamente o software antes de entregá-lo ao cliente, pois o teste sem uma
62
ferramenta como o JUnit se torna caro, cansativo e demorado,faltando aos
desenvolvedores um maior conhecimento do framework JUnit.
Nota-se que o JUnit atende bem as necessidades que os profissionais de
desenvolvimento têm ao testar softwares. Ele permite que casos de testes sejam
elaborados com pouquíssimas linhas de código, o que expressa a facilidade
encontrada em sua utilização. Anteriormente foi possível observar como se
aplicam algumas de suas funcionalidades principais, bem como a clareza com
que os resultados foram exibidos. Em alguns casos, além de mostrar a linha em
que o erro ocorreu, o JUnit informa o que o método retornou e o que o usuário
queria que ele retornasse, como ocorreu no teste do método de ordenação de
vetores testInsertSort(), no qual o usuário informou que a saída do método seria
“...4 3 2...” quando na verdade era “...2 3 4...”.
No JUnit os erros encontrados podem ser corrigidos e os testes iniciados
novamente sem que sua janela seja fechada, ou seja, após compilar e executar a
classe de teste, se algum erro é encontrado, o analista faz as correções
necessárias no código, compila a classe novamente e não a executa, apenas
clica em Run na janela do JUnit, que os testes serão realizados novamente.
O que se observa ao estudar o JUnit são relatos de desenvolvedores
satisfeitos pelos ganhos obtidos através da utilização dessa ferramenta, alguns
desses relatos podem ser encontrados em artigos disponíveis no site
www.linhadecodigo.com.br. Mas é claro o JUnit necessita, ainda, de alguns
aprimoramentos como manipulação de variáveis e métodos não públicos, o que já
pode ser notado em novas extensões do mesmo, como o JUntiX.
63
5
CONSIDERAÇÕES FINAIS
O desenvolvimento desse trabalho possibilitou a realização de testes de
software automatizados através da utilização do framework JUnit. Essa
ferramenta é capaz de encontrar as falhas e mostrar exatamente o local as
mesmas ocorreram. O JUnit proporciona um ganho de tempo muito grande aos
desenvolvedores no que diz respeito ao tempo gasto para encontrar erros, que
muitas vezes são simples, mas que sem a ajuda de uma ferramenta de testes
como essa, não são encontrados, e muito menos corrigidos.
Além de automatizar os testes, o JUnit ainda permite a reutilização do
código em outros testes, proporcionando maior facilidades aos desenvolvedores,
pois eles não terão que reescrever alguns semelhantes no futuro.
Através dos testes realizados neste trabalho, foi possível constatar que o
JUnit realmente agiliza e facilita muito a elaboração e execução dos testes e,
posteriormente, a correção dos erros. Porém, necessita de alguma técnica de
teste para auxiliá-lo, como é o caso do teste de caminho básico, teste de caixa
branca realizado manualmente, capaz de encontrar erros de implementação que
o JUnit não detecta. Um complementa o outro da seguinte forma: enquanto o
teste do caminho básico analisa todos os possíveis caminhos a serem testados
pelo desenvolvedor, o JUnit avalia o resultado, a partir do que foi gerado ao final
da execução dos caminhos identificados no teste do caminho básico.
Dessa maneira, compreende-se que o JUnit é uma ferramenta de teste
bastante útil, que possibilita aos profissionais que a utilizam a obtenção de
ganhos significativos com a redução dos gastos destinados à fase de testes do
projeto, ao economizar tempo de elaboração e execução dos testes e nas
despesas com o pessoal envolvido. Porém, o mesmo não consegue sozinho
64
encontrar todos os tipos de erros do sistema, sendo então necessária também a
execução do teste do caminho básico.
Depois de pesquisar e executar testes com o JUnit, percebe-se o tamanho
de sua importância, sendo uma ferramenta indispensável ao ambiente de
desenvolvimento de softwares. Vê-se também que o JUnit oferece tantas outras
vantagens que favorecem a sua utilização, o fato de ser gratuita, fácil de obter e
utilizar, ser multiplataforma e funcionar bem em máquinas modestas, pois não
exige equipamento de ponta. E, acompanhado do teste do caminho básico,
oferece ainda mais vantagens, pois outros diferentes tipos de falhas serão
descobertas.
Com a conclusão desse trabalho objetiva-se que outros colegas o utilizem
como fonte de pesquisa para desenvolver novos trabalhos, ou mesmo dar
continuidade a este, intensificando as pesquisas e os estudos relacionados aos
testes de software e consequentemente ao JUnit e outras ferramentas de testes,
como o JUnitX e o JunitPerf, sendo que este último avalia o desempenho do
sistema, analisando o tempo de resposta de cada método, dentre outras
funcionalidades importantes.
65
6
REFERÊNCIAS BIBLIOGRÁFICAS
PRESSMAN, Roger S. Engenharia de Software. São Paulo: Makron Books,
1995.
DEITEL, H. M. Java, como programar / H. M. Deitel e P. J. Deitel; trad. Carlos
Arthur Lang Lisboa. – 4. ed. – Porto Alegre: Bookman, 2003.
BARTIÉ,
Alexandre.
Garantia
da
qualidade
de
software:
adquirindo
maturidade organizacional. Rio de Janeiro: Campus, 2002.
INTHURN, Cândida. Qualidade & Teste de Software. Florianópolis: Visual
Books, 2001.
PETERS, James. Engenharia de Software: Teoria e Prática / James F. Peters,
Witold Pedrycz; trad. Ana Patrícia Garcia. – 3. Reimpressão – Rio de Janeiro:
Elsevier, 2001.
PAULA FILHO, Wilson de Pádua. Engenharia de Software: Fundamentos,
Métodos e Padrões. Rio de Janeiro: LTC, 2001.
OLIVEIRA, Eric C M, Linha de Código – Java: Testes Unitários e JUnit, jan.
2005. Disponível em: <http://www.linhadecodigo.com.br/artigos.asp?id_ac=576>,
acessado em 08/03/2006.
66
WIKIPÉDIA:
a
enciclopédia
livre,
abr,
2006.
Disponível
em:
<http://pt.wikipedia.org/wiki/JUnit>, acessado em 25/04/2006.
TECHNOLOGIES, Néki, HotWork Solution: JUnit User Guide, fev, 2004.
Disponível
em
<http://hotwork.sourceforge.net/hotwork/manual/junit/junit-user-
guide.html>, acessado em 08/03/2006.
MEDEIROS,
Manoel
Pimentel,
DevMedia
Group,
2005.
Disponível
em
<http://www.devmedia.com.br/visualizacomponente.aspx?comp=1432&site=6>,ac
essado em 10/03/2006.
BARROS, Alexandra, UFPE: Centro de informática, dez. 2005. Disponível em
<http://www.cin.ufpe.br/~abab/monitoria/es/ESSJUnit.ppt>,
acessado
em
13/04/2006.
USP, University of Sao Paulo: Institute of Mathematics and Statistics, mar.
2004.
Disponível
em
<http://malariadb.ime.usp.br/labd/downloads/Junit.ppt>,
acessado em: 13/04/2006.
JUNIT. JUnit, Testing Resources for Extreme Programming. Disponível em:
<www.junit.org>, acessado em: 28/02/2006.
MARIATH, Fabiano Mariath D’Oliveira, UNICEUB – Centro Universitário de
Brasília.
Engenharia
de
Programas,
2004.
Disponível
<http://www.projettus.com/uniceub/ep/aula/teste_fundamentos.html>,
em: 08/03/2006.
em
acessado
Download

Fase de Testes - Inicio