Você está na página 1de 23

Capítulo

6
Automatização de teste de software com ênfase em
teste de unidade

Misael Júnior, Stevão Andrade e Márcio Delamaro

Abstract

Software testing is the activity of run a software product in order to check whether
this product conforms to it’s specifications. In software testing scenarios, automation con-
tributes significantly to reducing costs during the development process. Thus, increasing
the quality of the final product, high levels of coverage, reducing test time, increased reli-
ability, reduced human effort are some of the benefits of the automated test. This chapter
aims to present and discuss the main automated software testing concepts with emphasis
on unit testing. In this scenario, concepts will be presented related to the main technical
and criteria and, in parallel, tools that support the testing activity.

Resumo

Teste de software é o processo de executar um produto de software com o intuito de checar


se tal produto está de acordo com suas especificações. Em cenários de teste de software,
a automatização contribui significativamente para a redução de custos e tempo de projeto
durante o processo de desenvolvimento. Dessa forma, aumento da qualidade do produto
final, alto índice de cobertura, redução do tempo de teste, aumento da confiança, redu-
ção de esforços humanos e dos custos são alguns dos benefícios do teste automatizado.
Este capítulo visa apresentar e discutir os principais conceitos de teste de software auto-
matizado com ênfase em teste de unidade. Nesse cenário, serão apresentados conceitos
relacionados às principais técnicas e critérios e, paralelamente, ferramentas que apoiam
a atividade de teste.

6.1. Introdução
O teste de software é um conjunto de atividades dinâmicas que consistem na execução
de um programa com algumas entradas específicas, visando a verificar se o comporta-
mento do programa é condizente com sua especificação [Bertolino, 2007, Delamaro et al.,
2007b]. De acordo com Myers et al. [2004], as atividades de teste de software configu-
ram o processo, ou série de processos, que têm a função de executar um sistema com
a intenção de revelar a presença de possíveis defeitos. Para a maioria dos sistemas em
desenvolvimento, o processo de teste é o meio mais importante pelo qual tais sistemas
são submetidos para terem sua conformidade com especificações verificadas [Ammann
and Offutt, 2008, Hunter and Strooper, 2001]. Negligenciar as atividades de teste, muitas
vezes, pode remeter à produção de software de má qualidade e prejuízos econômicos.
É senso comum entre engenheiros de software e projetistas que duas questões bá-
sicas de projeto são intimamente associadas à aplicação do teste de software: (1) custo;
e (2) tempo. Em relação ao custo, pode-se afirmar que o emprego do teste traz a ne-
cessidade de contratação de equipes especializadas (testadores e projetistas de teste) e
aquisição de licenças de ferramentas específicas, elevando significativamente custos de
desenvolvimento, dependendo da complexidade do sistema em desenvolvimento [Myers
et al., 2004]. Tal acréscimo do tempo de projeto é consequência do não uso de técnicas
e critérios de testes adequados, da reexecução dos dados de teste após cada correção de
inconsistências detectadas e, principalmente, a falta de automatização [Bertolino, 2007].
Nesse cenário, o emprego de testes automatizados contribue significativamente
para a redução de custos e tempo de projeto durante o processo de desenvolvimento
[Myers et al., 2004]. Segundo Rafi et al. [2012], aumento da qualidade do produto final,
alto índice de cobertura, redução do tempo de teste, aumento da confiança, diminuição
de esforços humanos e redução de custos são alguns dos benefícios do teste automati-
zado relatados na literatura. Por isso, e pela aceitação entre pesquisadores e engenheiros
de software, da ideia de que qualidade é um fator essencial no desenvolvimento de soft-
ware, muito se tem investido em pesquisas na área de teste de software [Delamaro et al.,
2007b]. Pesquisadores procuram definir técnicas, critérios e ferramentas que possibilitem
a aplicação de tais atividades de maneira sistemática, com alta qualidade e custo reduzido.
Portanto, julga-se necessário difundir princípios e conceitos relacionados à apli-
cação de técnicas e critérios de teste a fim de possibilitar o desenvolvimento de softwares
de qualidade. Nessa seção foram apresentados o contexto, motivação para o desenvolvi-
mento deste capítulo. As demais Seções abordam técnicas e critérios de software, além
de ferramentas para o seu suporte e aplicação. Assim, o restante deste capítulo está orga-
nizado da seguinte forma:

• A Seção 6.2 apresenta algumas definições e conceitos relacionadas ao teste de soft-


ware e alguns outros assuntos referentes ao projeto em questão;
• A Seção 6.3 apresenta algumas definições, conceitos e práticas relacionadas as prin-
cipais técnicas e critérios de teste de software. Além disso, a Seção 6.3 apresenta
ferramentas de apoio para enfatizar o uso de cada técnica; e
• Por fim, na Seção 6.4 são descritos os principais conceitos relacionados a automa-
tização de teste de software.

6.2. Fundamentos de Teste de Software


O desenvolvimento de sistemas de software com qualidade e produtividade é um dos
principais objetivos da ES (Engenharia de Software). Dessa forma, atividades de VV&T
(Validação, Verificação e Teste) são consideradas fundamentais [Delamaro et al., 2007b].
A Validação assegura que o produto sendo desenvolvido corresponde ao produto correto,
conforme os requisitos do usuário, enquanto a Verificação tende a assegurar consistência,
completude, corretude do produto em cada fase e entre fases consecutivas do ciclo de vida
[Myers et al., 2004]. Por sua vez, o Teste de software é um processo em que um programa
é executado com o objetivo de revelar possíveis defeitos, aumentando a confiabilidade do
sistema [Bertolino, 2003].
Nesse sentido, para garantir que o produto entregue esteja livre de defeitos, ou
seja, para que tais defeitos sejam descobertos antes da entrega final do produto, torna-
se necessário a aplicação de atividades VV&T, que são diretamente relacionadas com a
garantia de qualidade, objetivando a detecção de erros em sistemas. Para padronização
de conceitos, a IEEE 610.12 [IEEE, 1990] define os principais termos utilizados no con-
texto de teste de software, afim de que sejam evitadas interpretações incorretas destas
nomenclaturas:

• defeito (fault): passo ou processo ou definição de dados incorretos;


• engano (mistake): ação humana que produz um resultado incorreto;
• erro (error): diferença entre o valor obtido e o valor esperado, a existência de um
defeito pode ocasionar um erro; e
• falha (failure): resultado produzido na execução do programa seja diferente do
resultado esperado.

A atividade de teste é essencial no processo de desenvolvimento de software. O


teste de software consiste em executar um determinado programa com algumas entradas
específicas, visando verificar se o comportamento do programa é condizente com sua
especificação [Fabbri et al., 2007a]. Em linhas gerais, isso equivale a observar a execução
de um sistema de software para validar se ele se comporta como esperado, identificando
potenciais problemas de funcionamento [Bertolino, 2007].
Sob outra perspectiva, o teste de software é uma atividade dinâmica que consiste
em executar um SUT (do inglês System Under Test), em um ambiente controlado, para
aumentar a confiabilidade que o SUT se comporta conforme sua especificação [Bertolino,
2003].
De acordo com Myers et al. [2004], a atividade de teste de software é um pro-
cesso, ou uma série de processos, cuja função é executar um sistema com a intenção de
encontrar possíveis erros. Além disso, os testadores necessitam de estratégias adequadas
para selecionar um conjunto adequado e finito de dados de entrada a partir da especifi-
cação do domínio de entrada, aumentando as chances da propagação de falhas. Códigos
defeituosos levam o SUT para um estado inconsistente, gerando erros que são propagados
para as saídas do sistema e podendo causar diferentes danos para os usuários. A detecção
de falhas de modo prematuro permite aos desenvolvedores a realização de correções antes
da entrega do produto final, melhorando a qualidade do SUT e evitando o desperdício de
tempo e dinheiro. Segundo Wazlawick [2013], as atividades de teste podem assumir até
40% do esforço despendido no processo de desenvolvimento, haja vista que, dependendo
do projeto, o domínio de entradas e saídas é extenso, gerando vários cenários a serem
testados.
De acordo com Barbosa et al. [2000] e Delamaro et al. [2007b], a atividade de
teste de software se resume em quatro etapas: (i) planejamento de testes; (ii) projeto de
casos de teste; (iii) execução dos testes; e (iv) avaliação dos resultados. Essas atividades
devem ser desenvolvidas ao longo do próprio processo de desenvolvimento de software e,
em geral, concretizam-se em três fases de teste: de unidade, de integração e de sistema.
Um ponto crucial na atividade de teste, independentemente da fase, é o projeto e/ou a
avaliação da qualidade de um determinado conjunto de casos de teste1 utilizado para o
teste de um produto, dado que, em geral, é impraticável utilizar todo o domínio de dados
de entrada para avaliar os aspectos funcionais e operacionais de um produto em teste. O
objetivo é utilizar casos de teste que tenham alta probabilidade de encontrar a maioria dos
defeitos com um mínimo de tempo e esforço, por questões de produtividade [Fabbri et al.,
2007a].
A Figura 6.1 ilustra como as atividades associadas ao teste de software se com-
portam. Cada um dos itens apresentados na figura podem ser representados da seguinte
forma:

• I: domínio de entrada do programa P;


• T: conjunto de dados de teste selecionados a partir de I;
• P: programa em teste;
• O: domínio de saída do programa P;
• S: conjuntos de saídas correspondentes às entradas T; e
• oráculo (ator): representado pela figura do mecanismo que estabelece, definindo se
as saídas dos testes estão de acordo com o esperado.

Figura 6.1. Atividades de teste de software.

De acordo com a Figura 6.1, um domínio de entradas I é definido e um conjunto


de dados de teste T é selecionado a partir de I. O conjunto T representa a entrada para
o programa P que, consequentemente, produzirá uma respectiva saída. Além disso, um
domínio de saída O do programa P é definido e conjuntos de saídas correspondentes às
entradas T são selecionados a partir de O. Dessa forma, o oráculo humano, que também
1 Caso de teste: par ordenado contendo entrada e saída esperada.
pode ser representado por um ferramenta, define se as saídas do SUT estão de acordo com
o esperado.
A atividade de teste é comumente dividida em etapas, cada fase do teste de soft-
ware possui objetivos distintos. Nesse contexto, cada etapa da fase de teste apresenta
características únicas e serve para guiar o processo de teste de software. Em geral, as
fases podem ser definidas em [Delamaro et al., 2007b]:

• Teste de unidade: tem como objetivo testar as menores unidades que compõem
o sistema (métodos, funções, procedimentos, classes, etc.). Espera-se encontrar
com maior facilidade defeitos de programação, algoritmos incorretos ou mal im-
plementados, estruturas de dados incorretas, limitando-se a lógica interna dentro
dos limites da unidade;
• Teste de integração: o foco principal é verificar as estruturas de comunicação
entre as unidades que compõem o sistema. As técnicas de projeto de casos de teste
que exploram entradas e saídas são as mais utilizadas durante a fase de integração
[Pressman, 2010]; e
• Teste de sistema: o objetivo é verificar se os requisitos satisfazem a especificação
e se as funcionalidades do sistema foram implementadas corretamente, isto é, o
sistema é testado como um todo procurando simular um ambiente de execução real.

As fases apresentadas são aplicadas com a utilização de diferentes técnicas. As


técnicas de teste são definidas de acordo com o tipo de informação utilizada para realizar o
teste. Além das fases apresentadas, há outras fases utilizadas na realização das atividades
de teste como, por exemplo, Teste de aceitação2 e Teste de operação3 .
Esse capítulo da ênfase na aplicação do teste de software durante o desenvolvi-
mento de testes de unidade.

6.3. Técnicas e Critérios de Teste


O tempo gasto durante a realização das atividades de teste de software é um fator essen-
cial, uma vez que o mesmo está relacionado diretamente em produzir qualidade e produ-
tividade no desenvolvimento de sistemas de software. Ao se testar um programa, alguns
trechos do programa são selecionados para a execução. Nesse contexto, o programa de-
veria ser executado cobrindo todos os elementos e caminhos possíveis, garantindo que
o mesmo não contenha defeitos. Tal atividade é inviável, aumentando drasticamente o
tempo de desenvolvimento do software. Utilizar apenas um conjunto de dados de teste
pode tornar-se viável, desde que ele tenha alta probabilidade de revelar defeitos [Dela-
maro et al., 2007c].
As técnicas e critérios de teste fornecem ao desenvolvedor uma abordagem sis-
temática e teoricamente fundamentada, além de constituírem um mecanismo que pode
2 Teste de aceitação: tem por função verificar o sistema em relação aos seus requisitos originais e às
necessidades atuais do usuário;
3 Teste de operação: fase de teste em que o teste é conduzido pelos administradores do ambiente final

onde o sistema ou software entrará em ambiente produtivo.


auxiliar a avaliar a qualidade e a adequação da atividade de teste. Critérios de teste po-
dem ser utilizados tanto para auxiliar na geração de conjuntos de casos de teste, quanto
para auxiliar na avaliação da adequação desses conjuntos [Pressman, 2011]. O critério de
teste ideal capaz de avaliar de fato o grau de correção de um software deveria ser capaz
de testa-lo com todos os valores possíveis do seu domínio de entrada, tal abordagem é
definida na literatura como teste exaustivo [Delamaro et al., 2007b], contudo, é uma abor-
dagem impraticável na grande maioria dos casos, pois o domínio de entradas do software
é infinito ou, quando finito, grande o bastante para tornar tal atividade inviável.
Assim, as técnicas de teste de software existentes procuram estabelecer regras
para definir subdomínios, a fim de criar um conjunto de casos de teste que satisfaçam
os requisitos de teste pertencentes ao subdomínio abordado. Porém, nenhuma delas é
suficiente para garantir a qualidade da atividade de testes. O ideal é que as técnicas de
teste sejam complementares, considerando a forma como são utilizadas, de modo que as
a vantagens de cada uma delas possa ser mais bem explorada a fim de extrair o melhor de
cada abordagem.
A literatura dispõe de diversas técnicas de teste que auxiliam o desenvolvedor
nas atividades de teste de software. A aplicação de técnicas e critérios de teste apoiam
a cobertura do exercício de determinadas características do sistema, por essa razão, as
execuções de técnicas de teste podem ser consideradas estratégias que buscam minimizar
esforços e maximizar a eficiência na busca de defeitos. Segundo Fabbri et al. [2007a],
as técnicas de teste de software podem ser classificadas em: técnica funcional, técnica
estrutural e técnica baseada em defeitos.
Cada técnica possui seus respectivos critérios e procura explorar determinados
tipos de defeitos, estabelecendo requisitos de teste para os quais valores específicos do
domínio de entrada do programa devem ser definidos com o intuito de exercitá-los. A se-
guir são apresentadas cada uma das técnicas de teste de software com suas características
e seus principais critérios.

6.3.1. Teste Funcional


O teste funcional trata o sistema como uma caixa preta, pelo fato de não haver acesso aos
detalhes de código para a derivação dos casos de teste. Assim, os conjuntos de testes são
elaborados apenas com base nos requisitos do software e nas especificações das suas fun-
cionalidades. Na técnica funcional são avaliadas as funções do sistema sem se preocupar
com os detalhes de implementação, sendo que o software é avaliado segundo o ponto de
vista do usuário [Ammann and Offutt, 2008, Pezze and Zhang, 2014]. Esse teste ignora
os mecanismos internos do programa e foca apenas nas saídas geradas em resposta às
entradas utilizadas e às condições de execução [Delamaro et al., 2007b].
Como todos os critérios da técnica funcional baseiam-se na especificação, é fun-
damental que esta esteja correta e bem escrita para que seja possível a geração de casos
de teste efetivos em detectar os defeitos no produto de software. Vale ressaltar também
que o teste funcional é utilizado para verificar: (i) se as funcionalidades do software são
operacionais; (ii) se as entradas são adequadamente aceitas; (iii) se as saídas são produ-
zidas corretamente; e (iv) verificar se a integridade das informações externas está sendo
mantida em conformidade com oque foi definido na especificação.
Considerando o emprego das técnicas de teste funcional, os testadores examinam
as exigências da especificação do cliente para construir os casos de teste, visando garantir
que o programa seja executado conforme foi especificado. Segundo Pressman [2010], o
teste funcional tenta identificar, principalmente, erros das seguintes categorias: (i) funções
incorretas ou ausentes; (ii) erros de interface; (iii) erros nas estruturas de dados ou acesso a
base de dados externa; (iv) erros de comportamento ou de desempenho; e (v) inicialização
e encerramento de erros.
De acordo com Delamaro et al. [2007b], os principais critérios baseados na técnica
de teste funcional são: (i) Particionamento em classes de equivalência; (ii) Análise do va-
lor limite; (iii) Teste funcional sistemático; e (iv) Grafo causa-efeito. O Particionamento
em classes de equivalência, define um conjunto de condições válidas ou inválidas para
um subconjunto de entradas. Assim todos os elementos pertencentes a uma determinada
classe de equivalência possuem comportamento semelhante, ou seja, se um elemento de-
tectar um defeito, todos os outros também irão detectar, assim como se um elemento não
detectar nenhum defeito, nenhum dos elementos pertencentes a esta classe irá detectar.
Essa abordagem reduz drasticamente o número de elementos do domínio de entradas a
ser verificado durante a atividade de teste.
Outro critério bastante difundido é a Análise de valor limite, que complementa o
critério particionamento em classes, exigindo que os casos de teste exercitem os limites
de cada classe de equivalência. Tal abordagem sugere que casos de teste que exploram
condições próximas do limite das classes tem uma maior probabilidade de evidenciar
defeitos [Myers et al., 2004].
O Teste funcional sistemático combina as duas técnicas citadas anteriormente,
considerando valores numéricos, reais, vetores, intervalos de valores, valores ilegais e
etc. O objetivo desse critério é aumentar a cobertura de código em relação aos demais
critérios funcionais. Este critério requer ao menos dois casos de teste de cada partição
para minimizar o problema de defeitos coincidentes que mascaram falhas [Fabbri et al.,
2007b].
Por fim, o critério Grafo Causa-Efeito facilita a identificação de ambiguidades e
incompletudes na especificação. Embora a construção do grafo causa-efeito seja consi-
derada complexa, o grafo é uma linguagem formal na qual a especificação é traduzida e
auxilia na elaboração de casos de teste mais confiáveis e sistemáticos. Dessa forma, as
causas correspondem às entradas e os efeitos às modificações de estado ou os resultados
observáveis do programa em teste.
Ainda que possuam algumas limitações, é importante o uso de critérios funcionais
para que seja possível explorar determinados tipos de defeitos. A utilização do teste
funcional possui vários pontos fortes, dentre os quais se destacam:

• Reutilização de casos de teste: por ser um critério baseado nas especificações,


portanto independente da implementação, mesmo que haja alteração no código os
casos de teste podem permanecer os mesmos;
• Independência de paradigma: uma vez que o código-fonte não é necessário, esses
critérios podem ser aplicados em qualquer programa, seja ele procedimental ou
orientado a objetos;
• Qualidade de conjuntos de teste: os critérios funcionais resultam um conjunto de
teste com boa representação de todo domínio de entrada das variáveis em teste; e
• Tempo reduzido: a derivação dos casos de teste pode ser realizada paralelamente
à implementação, reduzindo o tempo do projeto.

A técnica funcional pode ser utilizada em todas as fases de teste e independe do


paradigma de programação utilizado. Além disso, é eficaz em detectar determinados tipos
de defeitos como, por exemplo, funcionalidade ausente. No entanto, depende de uma
boa especificação, fator cada vez mais raro. Além disso, para cobrir todos os possíveis
caminhos especificados, um exaustivo número de casos de testes deve ser criado. Myers
et al. [2004] mostra que é impossível testar todas as possibilidades de entrada de um
programa.

6.3.1.1. Ferramentas de apoio - JUnit

A literatura dispõe de diversas ferramentas que auxiliam a implementação de testes fun-


cionais, sendo uma delas o framework JUnit4 . JUnit é um framework de teste de unidade
para a linguagem de programação Java. No contexto da ES, um framework é um con-
junto de classes que colaboram para realizar uma responsabilidade para um domínio de
um subsistema da aplicação. Desenvolvido por Kent Beck (Extreme Programming (XP)
+ Test Driven Development (TDD) ) e Erich Gamma (Padrões de projeto + Eclipse), o
framework de teste de unidade JUnit se tornou uma das bibliotecas Java mais utilizadas
no mundo.
O JUnit é um framework open source de simples manipulação, que tem a funci-
onalidade de apoiar a escrita e execução de código de teste. Utiliza a arquitetura xUnit,
que introduz mecanismos eficientes para ajudar desenvolvedores a adicionar testes uni-
tários automáticos, estruturados e eficientes em atividades normais de desenvolvimento.
Sendo assim, xUnit acabou se tornando um padrão para frameworks automáticos de testes
unitários.
A família Xunit corresponde a uma série de ferramentas para automação do teste
de unidade que foi estendida para diversas linguagem de programação, como: nUnit
(.NET), pyUnit (Python), CppUnit (C++) e DUnit (Delphi).
Testes com o JUnit não requerem uma interpretação humana dos resultados, pois
o JUnit permite a escrita de código de teste de maneira simples. Dentre as suas principais
características destacam-se:

• Fornece um meio para a execução de muitos testes ao mesmo tempo;


• Junit pode ser organizado em suítes de teste, contendo casos de testes para fins
específicos;
• Útil para o apoio ao Teste de regressão; e
• Re-execução automática de testes.
4O seguinte link se refere ao framework JUnit: http://junit.org/junit4/
De maneira geral, o JUnit é um framework utilizado para escrever e executar casos
de teste e permite que anotações sejam utilizadas para identificar os métodos de teste. Para
facilitar o uso, permite a utilização de assertivas para identificar resultados esperados para
a execução dos casos de teste. Além disso, o pacote mais importante do framework JUnit
é o junit.framework que contém suas classes principais. Algumas dessas classes são
ilustradas na Figura 6.2 com seus respectivos métodos e descritas na Tabela 6.1.

Figura 6.2. Classes do JUnit.

Classe Descrição
Assert Um conjunto de métodos contendo assertivas.
TestCase Define um conjunto de acessórios para execução dos casos de teste.
TestResult Usados para coletar informações sobre a execução dos casos de teste.
TestSuite Rotinas para executar um conjunto de casos de teste.
Tabela 6.1. JUnit core.

Com base na Figura 6.2, a classe Assert provê um conjunto de métodos contendo
assertivas úteis para escrever casos de teste. Somente assertivas que falharem são grava-
das. A classe TestCase provê acessórios para a execução de múltiplos casos de teste. Por
outro lado, a classe TestResult coleta informações sobre a execução de um dado caso de
teste. O framework faz distinção entre falhas e erros. Uma falha é antecipada e checada
por meio de assertivas, enquanto erros são problemas que não são antecipados, como por
exemplo o lançamento de exceções (Exceptions).
Além disso, a Figura 6.2 apresenta a classe TestSuite que oferece apoio à execução
de conjuntos de casos de teste. Por fim, a classe Annotations no framework JUnit são
meta-tags que possibilitam identificar e aplicar rotinas para métodos e classes. Essas
Annotations oferecem informações sobre os casos de teste, como métodos que necessitam
ser executados antes da execução do caso de teste, ou após a execução, métodos que deve
ser ignorados, entre diversas outras funções.
6.3.2. Teste Estrutural
Na técnica estrutural os critérios e requisitos de teste são derivados exclusivamente a par-
tir das características do código-fonte do software em teste, permitindo que haja uma
verificação mais profunda do seu comportamento. Os casos de teste são essencialmente
derivados a partir dos detalhes da estrutura interna do código-fonte do programa. Esse
tipo de teste possibilita que uma maior atenção seja dada aos pontos mais importantes do
código [Koscianski and Soares, 2007]. A partir do código-fonte é definido um conjunto
de elementos do software que devem ser executados para que se atinja a cobertura mínima
para um determinado critério. A maioria dos critérios desta técnica utiliza uma represen-
tação do programa conhecida como grafo de fluxo de controle (GFC) [Maldonado, 1991].
O GFC é elaborado de acordo com um número que identifica cada vértice do grafo
e o identificador de vértice é apresentado à frente de cada comando. No GFC, cada vértice
representa uma execução indivisível do código que termina em uma instrução simples ou
condicional [Barbosa et al., 2007, Myers et al., 2004].
A técnica estrutural é aplicada a pequenas partes do código, como sub-rotinas, ou
operações relacionadas a um objeto. Assim, o testador pode analisar o código antes de
iniciar os testes e buscar dados que possivelmente possam ser tratados de forma inespe-
rada, além de poder garantir que o programa foi liberado tendo seus comandos executados
pelo menos uma vez por pelo menos um caso de teste.
Os critérios estruturais aplicados em programas sequenciais são, em geral, classi-
ficados em:

• Critérios baseados em fluxo de controle: utilizam características de controle da


execução do programa (comandos, desvios de execução) para determinar os requi-
sitos de teste. Alguns critérios dessa técnica são: Todos-Nós, Todas-Arestas (ou
Todos-Arcos) e Todos-Caminhos;
• Critérios baseados na complexidade: utilizam informações sobre a complexi-
dade do programa para derivar os requisitos de teste. Como exemplo, o critério de
McCabe (teste de caminho básico) [McCabe, 1976a], que utiliza a complexidade
ciclomática do GFC para derivar os requisitos de teste. A complexidade ciclomá-
tica define o número de caminhos independentes de um programa, que é qualquer
caminho no grafo em que pelo menos uma aresta ainda não tenha sido exercitada
anteriormente [Barbosa et al., 2007]. McCabe [1976b] define a complexidade ci-
clomática (C) como: C = E - N + 2, tal que E é o número de arestas e N é o número
de vértices do GFC G; e
• Critérios baseados em fluxo de dados: é utilizada a análise de fluxo de dados
como fonte de informação para derivar os requisitos de teste. Testa o uso de uma
variável em um programa, como os dados do programa são utilizados durante a
sua execução para derivar os casos de teste. Possuem como principal vantagem
o fato de garantir a possibilidade de identificar o uso incorreto de valores resul-
tante de erros de computação. Uma família de critérios bastante conhecida é a de
Rapps e Weyuker [Rapps and Weyuker, 1985], que utiliza um GFC estendido, cha-
mado grafo Def-Uso (do inglês Def-Use Graph). Tendo como base esses critérios,
Maldonado [1991] define uma família de critérios, chamada Potenciais-Usos, que
requerem associações independentemente da ocorrência explícita de uma referên-
cia ou uso a uma determinada definição. Os critérios que fazem parte da família de
critérios Potenciais-Usos são: Todos-Potenciais-Usos, Todos-Potenciais-Usos-Du e
Todos-Potenciais-Du-Caminhos.

Critérios da técnica estrutural identificam caminhos que devem ser percorridos no


código do programa. Dessa forma, o GFC é a base a partir do qual os requisitos de testes
são derivados. No entanto, essa técnica não pode garantir que o programa comporta-se
como esperado, devido ao problema da existência de possíveis caminhos ausentes.

6.3.2.1. Ferramentas de apoio - JaBUTi

A literatura dispõe de diversas ferramentas que auxiliam a implementação de testes es-


truturais, sendo uma delas a JaBUTi5 . A ferramenta JaBUTi (do inglês Java Bytecode
Understanding and Testing) [Vincenzi et al., 2003], desenvolvida pelo grupo de pesquisa
de Engenharia de Software do ICMC-USP, tem como principal característica a disponibi-
lidade de uma GUI completa, desenvolvida em Swing, para o acompanhamento das ativi-
dades de teste estrutural de programas escritos na linguagem Java. A ferramenta oferece
interfaces gráficas que apresentam a cobertura de comandos ou de desvios, informações
sobre fluxo de dados e sobre cobertura de métodos, classes ou critérios de teste. A Figura
6.3 apresenta a tela principal da ferramenta JaBUTi.

Figura 6.3. Tela principal da ferramenta JaBUTi.

Uma das principais características da JaBUTi, que a diferencia de outras ferra-


mentas, é o fato que toda análise estática necessária para realização da atividade de teste
é feita sobre o programa objeto, ou seja, sobre o bytecode Java e não sobre o programa
fonte. Dessa forma, a ferramenta JaBUTi apresenta as seguintes características:
5O seguinte link se refere à ferramenta JaBUTi: http://ccsl.icmc.usp.br/pt-br/projects/jabuti
• Projeto de teste;
• Importação de casos de teste;
• Inserção e remoção de casos de teste dinamicamente;
• Casos de teste podem ser habilitados ou desabilitados; e
• Geração de relatórios (resumo, por critério, por classe, por método, por caso de
teste).

Além disso, a JaBUTI possui integração com ferramentas para visualização do


grafo. O grafo é a representação dos nós do código. Nesse cenário, a Figura 6.4 apresenta
o código-fonte com o respectivo GFC extraído por meio da ferramenta JaBUTi.

Figura 6.4. Visualização do código fonte e do GFC por meio da JaBUTi.

6.3.2.2. Ferramentas de apoio - EclEmma

EclEmma é uma ferramenta para dar suporte à cobertura de código em projetos desenvol-
vidos na IDE Eclipse, está disponível sob a licença Eclipse Public License. O plugin traz
consigo a possibilidade de disponibilizar estatísticas de cobertura de código diretamente
dentro do Eclipse6 .
Consiste em um plugin da ferramenta Emma7 para a IDE Eclipse e pode ser ins-
talado pelo menu Help->Install New Software, na versão tradicional do Eclipse, ou pelo
Eclipse Market Place.
O plugin EclEmma pode ser executado de algumas formas distintas:
6 http://www.eclemma.org/
7 http://emma.sourceforge.net/
Figura 6.5. Resultado da execução dos casos de teste para verificação da cobertura.

• Execução direta com a instrumentação ocorrendo durante execução dos casos de


teste dentro do ambiente de desenvolvimento Eclipse;
• Execução por meio de scripts Ant8 ; e
• Execução por meio de scripts Maven9 .

Os passos básicos para uso da ferramenta na ferramenta consistem de instrumentar


as classes a serem testadas, executar as classes instrumentadas com os casos de testes e
importa casos de testes desenvolvidos com apoio do framework JUnit.
Para execução do plugin é necessário abrir ou criar um projeto alvo no Eclipse,
executar as classe de teste por meio da opção Coverage As->JUnit Test, disponível após
a instalçao do plugin e com isso, os casos de teste criados com base no framework JUnit
serão executados em classes instrumentadas e a cobertura do código poderá monitorada.
O resultado da execução é apresentado a seguir:
A ferramenta EclEmma apresenta relatórios de cobertura para os seguintes crité-
rios10 :

• Contagem de Instruções (bytecode);


• Contagem de Desvios;
• Contagem de Linhas (código-fonte);
8 http://ant.apache.org/
9 http://maven.apache.org/
10 www.eclemma.org/jacoco/trunk/doc/counters.html
Figura 6.6. Relatórios sobre a sessão de cobertura da EclEmma.

• Contagem de Métodos;
• Contagem de Tipos; e
• Contagem de Complexidade.

Para a geração de relatório de cobertura, é necessário clicar com o botão direito na


interface gráfica e escolher a opção ExportSession e selecionar o diretório onde será ge-
rado um sumario da sessão de cobertura no formato HTML com um resumo da execução
dos casos de teste executados pelo JUnit e a cobertura atingida pelos mesmos para cada
critério avaliado.

6.3.3. Teste Baseado em Defeitos


Na técnica baseada em defeitos, os requisitos de teste são derivados a partir dos enganos
mais frequentes cometidos durante o processo de desenvolvimento de software. O teste
de mutação é o critério mais conhecido da técnica baseada em defeitos [Delamaro et al.,
2007a]. O teste de mutação tem como uma das suas finalidade medir o grau de adequação
de um conjunto de casos de teste [DeMillo et al., 1978, 1980].
Segundo DeMillo et al. [1978], a aplicação do critério de mutação é baseada em
duas hipóteses:

• Hipótese do Programador Competente: assume que programas desenvolvidos


por programadores competentes estão corretos ou bem próximos do correto; e
• Hipótese do Efeito de Acoplamento: assume que um conjunto de dados de teste
capaz de evidenciar erros simples em um programa, também é capaz de detectar
erros complexos, devido ao fato de estudos empíricos terem evidenciado que erros
complexos normalmente estarem relacionados a erros simples.
Considerando tais hipóteses, o testador deve aplicar um conjunto de teste T contra
um programa P a fim de verificar se o resultado obtido é igual ao resultado esperado. Caso
eles sejam diferentes, então o programa possui um defeito que deve ser corrigido para que
possa dar continuidade ao teste de mutação.
A partir do programa P, com base em um conjunto de operadores de mutação,
é gerado um conjunto de programas (P1 , P2 , P3 , P4 , ...Pn ) denominados de mutantes, con-
forme representado na Figura 6.7. Tais programas mutantes são gerados com objetivo de
modelar possíveis enganos que podem acontecer durante o processo de desenvolvimento
de software.

Figura 6.7. Processo de geração de mutantes.

Para geração dos programas mutantes, é necessária a utilização de operadores de


mutação. Entende-se por operadores de mutação regras responsáveis por definirem altera-
ções que devem ser aplicadas ao programa original P, a fim de satisfazer duas condições:
(i) induzir mudanças sintáticas simples com base nos enganos típicos cometidos pelos
desenvolvedores de software ou (ii) forçar determinados objetivos de teste.
A Figura 6.8 apresenta o trecho de código da versão original de um programa
e um mutante gerado a partir da aplicação do operador de mutação Relational operator
replacement (ORRN).

Figura 6.8. Programa original P e Mutante gerado com uso do operador ORRN.

Não há uma maneira especifica para definição do conjunto de operadores de mu-


tação. Normalmente os operadores são determinados com base na experiência do uso da
linguagem, assim como nos enganos mais comuns que podem ser cometidos pelos progra-
madores durante a sua utilização. Nesse sentido, cada linguagem de programação possui
um conjunto único de operadores de mutação. Por exemplo, na literatura são definidos
diversos operadores de mutação para as mais diversas linguagens: Fortran [DeMilli and
Offutt, 1991] , C [Agrawal et al., 1989], Java [John et al., 1999].
Após garantir que o programa P atende às saídas esperadas para o conjunto de
casos de teste, o mesmo conjunto de teste T aplicado em P é executado contra os mu-
tantes gerados, podendo apresentar duas saídas distintas: (a) se a saída do mutante Pi
for diferente de P, ou (b) se a saída de Pi for idêntica a P, para qualquer caso de teste
executado.
No caso da saída (a) o mutante é definido como “morto”, ou seja, dentro do con-
junto de casos de teste T, há um caso de teste que foi capaz de distinguir o comportamento
do mutante Pi em relação ao programa original P. No caso da saída (b) o programa mu-
tante é definido como “vivo”, ou seja, não há no conjunto de casos de teste T um caso de
teste capaz de evidenciar a perturbação de código inserida em Pi . Nos casos em que não
é possível identificar um caso de teste capaz de distinguir Pi de P, então Pi é dito como
equivalente a P.
O processo geral envolvido no teste de mutação é representado na Figura 6.9,
sendo normalmente divido nos seguintes passos:

• geração dos mutantes;


• execução do programa em teste;
• execução dos mutantes; e
• análise dos mutantes.

O processo se encerra quando todos os mutantes que foram gerados são mortos
pelos casos de teste, ou há a identificação de mutantes que não podem ser mortos, no caso
da existência de mutantes equivalentes.
Um fator importante no teste de mutação é o escore de mutação, que fornece uma
medida de confiança do nível de adequação do conjunto de casos de teste utilizado durante
a aplicação do critério. Para tal, é necessária a realização de um cálculo que relaciona o
número de mutantes gerados, com o número e mutantes mortos.
O cálculo do escore de mutação é dado da seguinte forma:

DM(P, T )
ms(P,T) =
M(P) − EM(P)

• DM(P,T): número de mutantes mortos pelos casos de teste em T;


• M(P): número total de mutantes gerados; e
• EM(P): número de mutantes gerados equivalentes a P.

O escore de mutação varia entre 0 e 1, quanto maior o escore de mutação, melhor


avaliado é o conjunto de casos de teste. Contudo, atingir 100% do escore de mutação
Figura 6.9. Processo genérico do teste de mutação. Adaptado de [Offutt and
Untch, 2001].

exige lidar com o problema da equivalência de mutantes, que muitas vezes se torna um
grande desafio devido ao grande número de mutantes gerados, mesmo para programas
relativamente simples.
Em suma, o teste de mutação é uma técnica altamente eficaz para evidenciar defei-
tos em um produto de software e é uma ótima alternativa para medir o nível de adequação
de um conjunto de casos de teste, contudo, possui grandes desafios que precisam ser
superados. Entre as principais dificuldades estão o problema na identificação de mutan-
tes equivalentes, que na maioria das vezes acaba sendo resolvida de maneira manual e o
grande número de mutantes gerados que acaba se tornando um empecilho para a aplicação
da técnica em ambientes reais.

6.3.3.1. Ferramentas de apoio - Proteum

A literatura da área dispõe de diversas ferramentas que dão suporte à aplicação do teste de
mutação, uma delas a Proteum11 . A Proteum (do inglês Program Testing Using Mutants),
desenvolvida pelo grupo de pesquisa de Engenharia de Software do ICMC-USP, apoia
o teste de mutação para programas em C. Basicamente, ela oferece ao testador recursos
para, por meio da aplicação do critério teste de mutação, criar e avaliar a adequação de
um conjunto de casos de teste para um determinado programa. Com base nas informações
fornecidas pela Proteum, o testador pode melhorar a qualidade até obter um conjunto de
testes adequado ao critério.
A atividade de teste na Proteum é conduzido por meio de sessões de teste. Uma
sessão de testes é caracterizada por uma base de dados composta por um conjunto de
casos de testes e um conjunto de mutantes [Delamaro et al., 2000].
11 http://ccsl.icmc.usp.br/pt-br/projects/proteum
A ferramenta Proteum é composta por uma serie de módulos que atuam sobre
uma base de dados. Cada módulo é responsável por realizar uma atividade ligada ao teste
de mutação e possibilita duas formas de interação com o usuário (GUI/Script). A forma
indicada para iniciantes é a utilização da interface gráfica que tem como objetivo auxiliar
o testados a conhecer os módulos da ferramenta e como utilizar a interface por linha de
comando. Cada módulo da ferramenta é responsável por realizar uma atividade ligada ao
teste de mutação.
As Figuras 6.10, 6.11 e 6.12 ilustram a interface gráfica da ferramenta e demons-
tram respectivamente como se dá a visualização dos mutantes gerados, a visualização de
relatórios e o acompanhamento do status da sessão de teste.

Figura 6.10. Mutantes gerados por meio da ferramenta Proteum.

A seguir são descritos os principais módulos existentes na ferramenta Proteum e


suas respectivas funções:

• Test-new: cria uma sessão de testes, ou seja, cria todos os arquivos necessarios para
a aplicação do teste de mutação para o programa alvo;
• Tcase: modulo utilizado para gerenciar o conjunto de casos de teste utilizados em
uma sessão de teste na ferramenta Proteum, possibilitando adição, deleção, seleção
dos casos de teste a serem executados durante a sessão;
• Muta-gen: modulo responsável pela geração dos mutantes a serem utilizados na
sessão de testes;
• Exemuta: constrói e executa os mutantes a partir de descritores de mutação; tam-
bém é utilizado para selecionar um determinado grupo de mutantes;
• Muta: utilizado para o gerenciamento do conjunto de mutantes existente na sessão
de teste. Permite a seleção de um sub-conjunto de mutantes, além de possibilitar a
alteração do status dos mutantes individualmente;
• Muta-view: permite a visualização e analise dos mutantes criados/executados na
sessão de testes; e
• Report: é utilizado para a produção de dois tipos de relatórios: relatórios sobre os
casos de teste utilizados e relatórios sobre os mutantes gerados/executados.

Cada módulo pode ser executado de maneira independente a partir do console


do sistema operacional, ou dentro de scripts. Essa característica permite que sessões
completas de teste possam ser programadas e executadas em modo batch.

Figura 6.11. Proteum - Relatórios Figura 6.12. Proteum - Status da


da sessão de testes. sessão de testes.

Dentre as principais características presentes na ferramenta Proteum, é possível


destacar:

• Utilização de informações de fluxo de controle para evitar a execução de certos


mutantes. Durante a execução dos mutantes, a ferramenta verifica se determinado
caso de teste executam o ponto do programa que sofreu a mutação. Caso não o
execute, o caso de teste não precisa ser utilizado, pois se sabe que o resultado da
execução será o mesmo do programa original;
• Facilidade de importação de conjuntos de teste fornecidos à ferramenta, que não
precisam ser inseridos interativamente um a um pelo testador. A ferramenta pos-
sibilita a adição de casos de teste de maneira manual, utilizando sessões de teste
anteriores.
• Dois modos de execução dos mutantes: teste e pesquisa. O primeiro apresenta
o comportamento original do teste de mutação, que é a interrupção da execução
dos casos de teste quando se encontra um caso de teste que distingue o mutante
do programa original. No modo pesquisa, todos os casos de teste são aplicados
contra todos os mutantes. Essa atividade onera o tempo necessário para aplicação
do teste de mutação, contudo, permite a coleta de informações sobre a efetividade
de um dado conjunto de testes ou sobre determinados mutantes. Tais informações
se tornam importantes principalmente no ambiente de pesquisa cientifica;
• Operações para habilitar/desabilitar casos de testes de maneira flexível, garantindo
assim que o testador possa experimentar diferentes combinações de casos de teste
sem ter que efetivamente alterar o conjunto de casos de teste; e
• De maneira semelhante, também esta presente a possibilidade de selecionar os mu-
tantes utilizados durante o teste, garantindo por exemplo, que o testador possa ava-
liar o efeito do conjunto de casos de teste para um grupo especifico de mutantes.

6.4. Automatização de Teste de Software


O teste automatizado tem uma série de características particulares que o diferem do teste
manual, cuja característica principal é a figura humana conduzindo os testes [Dustin et al.,
2009]. A automatização do teste requer o desenvolvimento de recursos e ferramentas es-
pecíficas, sendo focado em substituir testes manuais complexos. O teste automatizado
pode estar associado a uma ampla variedade de contextos dentro das atividades de teste:
(1) condução, execução e geração de resultados apoiados por software; (2) suporte à ge-
ração e seleção de casos de teste; e (3) rápida reexecução e suporte ao teste de regressão.
Entretanto, o teste automatizado não evita a intervenção humana no processo, sendo as-
sim, o teste automatizado não pode ser separado do teste manual, uma vez que as duas
abordagens são complementares [Oliveira et al., 2014].
A Figura 6.13 apresenta uma estrutura genérica associada ao fluxo de tarefas do
teste de software. Tal estrutura contempla diversas etapas que podem variar dependendo
da natureza do sistema em teste. Entretanto, o fluxo do teste habitualmente se inicia a
partir da análise do “Domínio de entradas” do SUT, que representa todos os possíveis
dados de entrada. Por meio dessa análise, o projetista do teste seleciona algumas entradas
específicas que irão compor os “Dados de teste”. Em geral, um projetista de teste sele-
ciona tais dados (“ent”) cuidadosamente a partir de algum critério de alguma técnica de
teste. Essa seleção de dados de teste é fundamental para a produtividade do teste, haja
vista que muitas vezes o domínio de entradas do SUT é infinito e o teste do sistema com
todos os elementos do SUT é impraticável.

Figura 6.13. Fluxo genérico de teste com oráculos.

Em seguida, o SUT é executado a partir das entradas “ent”, produzindo uma saída
qualquer comumente chamada de “saída real”. É exatamente nessa etapa do teste que
agem os “Oráculos de teste” – responsáveis por avaliar as saídas do SUT diante de al-
guma referência.
A automatização de testes possibilita uma grande economia de custos e proces-
sos de analise, além de aumentar a produtividade e prevenir os sistemas de futuras falhas
inesperadas. Automatizar processos de teste é uma questão de destaque para a área de
ES pelo fato de promover abordagens mais sistemáticas, produtivas e confiáveis [Ivory
and Hearst, 2001]. As vantagens na execução de testes automatizados são claras em com-
paração a testes manuais. Reexecução, suporte à regressão, tempo de projeto reduzido,
maior produtividade e prevenção contra erros são algumas das vantagens de testes au-
tomatizados. Entretanto, dependendo do sistema, a figura humana é capaz de encontrar
comportamentos inesperados que os testes automatizados não são capazes de encontrar
[Oliveira et al., 2014].

6.5. Considerações Finais


Nesse capítulo foram abordados conceitos relacionados a Engenharia de Software e Teste
de Software. Além disso, nesse capítulo foram abordados os conceitos, características e
principais práticas a respeito das atividades de teste de software a partir do uso de ferra-
mentas que visam automatizar a realização desta atividade a partir da aplicação de concei-
tos e perspectivas de técnicas e critérios de teste. Dessa forma, julga-se essa abordagem
ferramenta essencial na condução das atividades de teste de software, visando garantir
mais produtividade e qualidade, além de propiciar aos profissionais de software o desen-
volvimento de produtos de melhor qualidade, oferecendo conceitos e práticas essenciais
a serem aplicadas no desenvolvimento de um produto.

Referências
H. Agrawal, R. DeMillo, R. Hathaway, W. Hsu, E. Krauser, R. J. Martin, and A. Mathur.
Design of mutant operators for the c programming language. Technical Report SERC-
TR-41-P, Software Eng. Research Center, Purdue University, 1989.
P. Ammann and J. Offutt. Introduction to software testing. Cambridge University Press,
2008.
E. F. Barbosa, J. C. Maldonado, A. M. R. Vincenzi, M. E. Delamaro, S. R. S. Souza, and
M. Jino. Introduçao ao teste de software. Minicurso apresentado no XIV Simpósio
Brasileiro de Engenharia de Software (SBES 2000), 2000.
E. F. Barbosa, M. L. Chaim, A. M. R. Vincenzi, M. E. Delamaro, M. JINO, and J. C.
Maldonado. Capítulo 4 - Teste Estrutural. In M. E. Delamaro, J. C. Maldonado, and
M. Jino, editors, Introdução ao Teste de Software, pages 47–76. Campus, Rio de Ja-
neiro, 1 edition, 2007.
A. Bertolino. Software testing research and practice. In Abstract State Machines 2003,
pages 1–21, 2003.
A. Bertolino. Software testing research: Achievements, challenges, dreams. In 2007
Future of Software Engineering, pages 85–103, 2007.
M. E. Delamaro, J. C. Maldonado, and A. M. R. Vincenzi. Proteum/im 2.0: An integrated
mutation testing environment. In Mutation 2000 - A Symposium on Mutation Testing
for the New Century, October 2000.
M. E. Delamaro, E. F. Barbosa, A. M. R Vicenzi, and J. C. Maldonado. Teste de Mutação.
In M. E. Delamaro, M. Jino, and J. C. Maldonado, editors, Introdução ao Teste de
Software, pages 77 – 117. Elsevier, 2007a.

M. E. Delamaro, J. C. Maldonado, and M. Jino. Introdução ao teste de software. In M. E.


Delamaro, M. Jino, and J. C. Maldonado, editors, Introdução ao Teste de Software,
pages 1 – 7. Elsevier, 2007b.

M. E. Delamaro, J. C. Maldonado, and M. Jino. Capítulo 1 – Conceitos Básicos. In M. E.


Delamaro, J. C. Maldonado, and M. Jino, editors, "Introdução Ao Teste de Software",
pages 1–7. Campus, Rio de Janeiro, 1 edition, 2007c.

R. A. DeMilli and A. J. Offutt. Constraint-based automatic test data generation. Software


Engineering, IEEE Transactions on, 17(9):900–910, 1991. ISSN 0098-5589. doi:
10.1109/32.92910.

R. A. DeMillo, R. J. Lipton, and F. G. Sayward. Hints on test data selection: Help for the
practicing programmer. Computer, 11(4):34–41, April 1978. ISSN 0018-9162. doi:
10.1109/C-M.1978.218136.

R. A. DeMillo, GEORGIA INST OF TECH ATLANTA SCHOOL OF INFORMATION,


and COMPUTER SCIENCE. Mutation Analysis as a Tool for Software Quality As-
surance. GIT-ICS. School of Information and Computer Science, Georgia Institute of
Technology, 1980.

E. Dustin, T. Garrett, and B. Gauf. Implementing automated software testing: How to


save time and lower costs while raising quality. Addison-Wesley Professional, 2009.

S. C. P. F. Fabbri, A. M. R. Vincenzi, and J. C. Maldonado. Teste funcional. In M. E.


Delamaro, J. C. Maldonado, and M. Jino, editors, Introdução ao Teste de Software,
pages 9 – 24. Elsevier, 2007a.

S. C. P. F. Fabbri, A. M. R. Vincenzi, and J. C. Maldonado. Capítulo 2 - Teste Funcional.


In M. E. Delamaro, J. C. Maldonado, and M Jino, editors, Introdução ao Teste de
Software, pages 9–25. Campus, Rio de Janeiro, 1 edition, 2007b.

C. Hunter and P. Strooper. Systematically deriving partial oracles for testing concurrent
programs. In Australian Computer Science Communications, pages 83–91, 2001.

IEEE. Ieee standard glossary of software engineering terminology. IEEE Standard, Sep-
tember 1990.

M. Y. Ivory and M. A. Hearst. The state of the art in automating usability evaluation of
user interfaces. ACM Computing Surveys (CSUR), pages 470–516, 2001.

S. W. K. John, J. A. Clark, and J. A. Mcdermid. Assessing test set adequacy for object-
oriented programs using class mutation. In Class Mutation, 28 JAIIO: Symposium on
Software Technology SoST‘99, pages 72–83, 1999.
A. Koscianski and M. S. Soares. Qualidade de software: aprenda as metodologias e
técnicas mais modernas para o desenvolvimento de software. Novatec Editora, 2007.
ISBN 9788575221129.

J. C. Maldonado. Critérios Potenciais Usos: Uma Contribuição ao Teste Estrutural de


Software. PhD thesis, DCA/FEEC/UNICAMP, Campinas, SP, July 1991.

T. J. McCabe. A complexity measure. IEEE Trans. Softw. Eng., 2(4):308–320, July 1976a.
ISSN 0098-5589. doi: 10.1109/TSE.1976.233837.

T. J. McCabe. A complexity measure. Software Engineering, IEEE Transactions on,


pages 308–320, 1976b.

G. J. Myers, C. Sandler, T. Badgett, and T. M. Thomas. The Art of Software Testing.


Business Data Processing: A Wiley Series. Wiley, 2004. ISBN 9780471678359.

A. J. Offutt and R. H. Untch. Mutation testing for the new century, 2001.

R. A. P Oliveira, A. M. Memon, V. N. Gil, F. L. S. Nunes, and M. E. Delamaro. An


extensible framework to implement test oracles for non-testable programs. In Proce-
edings of the 26th International Conference on Software Engineering and Knowledge
Engineering (SEKE 2014), Vancouver, Canada, pages 199–204, 2014.

M. Pezze and C. Zhang. Automated test oracles: A survey. Advances in Computers, pages
1–48, 2014.

R. S. Pressman. Software engineering: a practitioner’s approach. McGraw-Hill higher


education. McGraw-Hill Higher Education, 2010. ISBN 9780073375977.

R. S. Pressman. Engenharia de software. McGraw Hill Brasil, 2011.

D. M. Rafi, K. R. K. Moses, K. Peterson, and M. V. Mantyla. Benefits and limitations


of automated software testing: Systematic literature review and practitioner survey. In
Proceedings of the 7th International Workshop on Automation of Software Test, pages
36–42, 2012.

S. Rapps and E. J. Weyuker. Selecting software test data using data flow information.
IEEE Trans. Softw. Eng., 11(4):367–375, April 1985. ISSN 0098-5589. doi: 10.1109/
TSE.1985.232226.

A. M. R. Vincenzi, W. E. Wong, M. E. Delamaro, and J. C. Maldonado. Jabuti: A coverage


analysis tool for java programs. XVII SBES–Simpósio Brasileiro de Engenharia de
Software, Manaus, AM, Brasil, pages 79–84, 2003.

R. S. Wazlawick. Engenharia de software: conceitos e práticas. Elsevier, 2013.

Você também pode gostar