• Nenhum resultado encontrado

Aula 11: Análise Dinâmica - 2a. parte

N/A
N/A
Protected

Academic year: 2021

Share "Aula 11: Análise Dinâmica - 2a. parte"

Copied!
7
0
0

Texto

(1)

Aula 11: Análise Dinâmica - 2a. parte

Nesta aula, continuaremos nossa discussão a respeito da análise dinâmica, focando na atividade de teste. Iremos dar uma breve olhada em algumas das noções básicas sobre as quais a atividade de teste está baseada, iremos analisar também as técnicas mais utilizadas na prática. No final, iremos reunir algumas diretrizes para lhe ajudar em seu próprio trabalho de testes.

11.1 Testando

Os testes são muito mais efetivos, e menos dolorosos, se você utilizar uma abordagem sistemática. Antes de você começar, pense a respeito dos seguintes itens:

·quais propriedades você quer testar e para quê;

·quais módulos você quer testar, e em qual ordem você os irá testar;

·como você vai gerar casos de teste;

·como você irá checar os resultados;

·quando você vai saber que o trabalho está terminado.

Para se decidir quais propriedades testar, e quais testes executar sobre estas propriedades, será necessário conhecimento a respeito do domínio do problema, visando compreender quais tipos de falhas serão consideradas as mais sérias, e será necessário também, conhecimento do programa, visando entender qual será a dificuldade em se detectar a variedade dos erros.

A escolha dos módulos é direta. Você deve testar, especialmente, os módulos que são críticos, que são complexos, ou que foram escritos pelo pior dos seus programadores (ou por aquele que mais adora utilizar truquezinhos dentro do código). Ou, talvez, o módulo que foi escrito tarde da noite, ou em cima da data de lançamento...

O diagrama de dependência modular ajuda a determinar a ordem. Se o seu módulo depende de um módulo que ainda não foi implementado, você precisará escrever um stub (o esqueleto de um módulo, que no máximo simula seu funcionamento) que irá fazer o papel do módulo que está faltando durante os testes. O stub fornece comportamento suficiente para os testes a serem realizados. Ele pode, por exemplo, procurar respostas em uma tabela ao invés de realizar a computação verdadeira.

A checagem dos resultados pode ser difícil. Alguns programas - como o Foliotracker que você estará construindo nos exercícios 5 e 6 - nem mesmo possuem comportamento repetitivo. Para outros programas, os resultados são apenas a ponta do iceberg, e para se certificar que as coisas estão realmente funcionando, você terá que checar estruturas internas. Mais adiante, iremos discutir

(2)

questões sobre como gerar casos de teste e como saber quando o trabalho está completo.

11.2 Testes de Regressão

É muito importante ser capaz de reexecutar seus testes quando você modificar seu código. Por esta razão, não é uma idéia muito boa realizar testes específicos que não podem ser repetidos. Pode parecer um trabalho árduo, mas em longo prazo, é muito menos trabalhoso construir um conjunto prático de testes, isto é, um suíte de testes que podem ser reexecutados a partir de um arquivo. Tais testes são conhecidos como testes de regressão.

Uma abordagem de testes conhecida pelo nome de 'test first programming', e que é parte do novo tratado de desenvolvimento conhecido como 'extreme programming', encoraja a construção de testes de regressão antes mesmo de se escrever qualquer código da aplicação. JUnit, o framework de testes que você tem utilizado, foi projetado para a isto.

A construção de testes de regressão para um sistema grande é um ótimo negócio. É possível que a execução dos scripts de testes leve até uma semana. Por conta disto, uma área de pesquisas de muito interesse atualmente é a que tenta descobrir quais testes de regressão podem ser omitidos. Se você souber quais casos de teste testam quais partes do código, você pode ser capaz de determinar que uma alteração local em uma parte do código não exige que todos os casos sejam reexecutados.

11.3 Normas

Para se compreender como os testes são gerados e avaliados, podemos pensar de maneira abstrata a respeito do propósito e da natureza das atividades de teste.

Suponha que temos um programa P que, espera-se, satisfaça a uma especificação S. Vamos assumir, por simplicidade, que P é uma função que transforma entradas de dados em saídas de dados, e S é uma função que recebe uma entrada de dados e uma saída de dados, retornando um tipo boolean. Nosso objetivo na realização dos testes é encontrar um caso de teste t tal que

S (t, P(t))

é falso: ou seja, P produz um resultado para a entrada t que não é permitido por S. Iremos denominar t como um 'caso de teste falho', apesar de se tratar, na verdade, de um caso de teste de sucesso, pois nosso objetivo é encontrar erros!

Um suíte de testes T é um conjunto de casos de teste. Fazemos, então, a seguinte pergunta: quando um suíte pode ser considerado bom o suficiente? Ao invés de tentamos avaliar cada suíte de testes

(3)

de uma forma dependente da situação, podemos aplicar uma norma geral.

Você pode pensar em uma norma como uma função:

C: Suite, Program, Spec → Boolean

que recebe um suíte de testes, um programa, e uma especificação, retornando true ou false de acordo com o fato do suíte de testes ser bom o suficiente para avaliar o programa e a especificação fornecidos, o que deve ser feito de maneira sistemática.

A maioria das normas não envolve, ambos, o programa e a especificação. Uma norma que envolve apenas o programa é denominada norma program-based (baseada em programa). Outros termos também são usados como 'whitebox', 'clearbox', 'glassbox', ou 'structural testing' para se descrever testes que utilizam as normas do tipo program-based.

Uma norma que envolve apenas a especificação é denominada norma specification-based (baseada em especificação). O termo 'blackbox testing' é utilizado em associação com o termo specification-based, para sugerir que os testes são julgados sem que se possa analisar a parte interna do programa. Você pode escutar também o termo 'functional testing'.

11.4 Subdomínios

Normas práticas tendem a ter uma estrutura particular e propriedades. Por exemplo, elas podem aceitar um suíte de testes T, mas rejeitar um suíte de testes T' que é igual a T, possuindo apenas alguns casos de teste extras. As normas práticas também tendem a não ser sensíveis a quais combinações de testes são escolhidas. Estas características não são, necessariamente, boas propriedades; elas apenas surgiram a partir da maneira simples a partir da qual as normas são definidas.

O domínio de dados de entrada é dividido em regiões normalmente chamadas de subdomínios, cada um dos quais contendo um conjunto de entradas. Os subdomínios juntos englobam todo o domínio de dados de entrada - ou seja, toda entrada está em pelo menos um subdomínio. Uma divisão do domínio de dados de entrada em subdomínios define uma norma implícita: que define que deva existir pelo menos um caso de teste para cada subdomínio. Subdomínios, normalmente, não são disjuntos, portanto, um único caso de teste pode estar em vários subdomínios.

A intuição por trás dos subdomínios pode ser compreendida em duas partes. Primeiro, é uma maneira fácil (pelo menos conceitualmente) de se determinar se um suíte de testes é bom o suficiente. Segundo, esperamos que, ao exigir um caso de teste de cada subdomínio, faremos com

(4)

que os testes sejam orientados às regiões mais propensas a revelar bugs. Intuitivamente, cada subdomínio representa um conjunto de casos de teste similares; desejamos maximizar o benefício de nossa atividade de teste escolhendo casos de teste que não sejam similares - ou seja, casos de teste de diferentes subdomínios.

No melhor caso, um subdomínio pode ser considerado revelador. Isto significa que todo caso de teste pertencente a este subdomínio faz com que o programa ou falhe ou tenha sucesso. O subdomínio, portanto, agrupa casos de teste verdadeiramente equivalentes. Se todos os subdomínios são reveladores, um suíte de testes que satisfaz à norma implícita estará completo, pois teremos a certeza de que o suíte irá encontrar qualquer bug. Na prática, no entanto, é muito difícil conseguir subdomínios reveladores, mas através de uma escolha cuidadosa dos subdomínios é possível ter-se, no mínimo, alguns subdomínios cuja taxa de erros - a proporção de entradas de dados que levam a saídas de dados erradas - é bem maior que a média de erros do domínio de dados de entrada como um todo.

11.5 Norma de Subdomínio

A norma padrão e mais amplamente utilizada para a atividade de teste do tipo program-based é denominada 'statement coverage', ou 'cobertura das sentenças': que define que toda a sentença de um programa deve ser executada pelo menos uma vez. Você pode perceber, através da definição da norma, por que ela é considerada uma norma de subdomínio: defina para cada sentença do programa um conjunto de entradas que fazem com que esta sentença seja executada, e escolha no mínimo um caso de teste para cada subdomínio. Obviamente, o subdomínio nunca é, explicitamente, construído; trata-se de uma noção conceitual. Ao invés disso, tipicamente, executa-se uma versão instrumentada do programa que registra cada executa-sentença executada. Deve-executa-se continuar acrescentando casos de teste até que todas as sentenças sejam executadas.

Existem outras normas ainda mais trabalhosas do que o statement coverage. A norma denominada 'decision coverage', por exemplo, requer que todas as arestas do gráfico do fluxo de controle do programa sejam executadas - é como exigir que todos os ramos de um programa sejam executados. Não é tão óbvio o porquê desta abordagem ser considerada mais trabalhosa do que o statement coverage. Pois, então, considere aplicar esta norma a um procedimento que retorna o menor de dois números:

static int minimum (int a, int b) { if (a ≤b)

return a; else

(5)

Para este código, statement coverage, irá exigir entradas com a menor do que b e vice-versa. No entanto, para o código

static int minimum (int a, int b) { int result = b;

if (b ≤a)

result = b; return result;

um único caso de teste com b menor do que a irá satisfazer a norma statement coverage, mas não poderíamos identificar o bug que está presente no código. Para satisfazer à norma decision coverage seria exigido um caso (um ramo) no qual o comando if não seja executado, expondo, desta forma, o bug.

Existem muitas formas de 'condition coverage' que exigem, de diversas formas, que as expressões boolenas testadas como condição sejam avaliadas tanto para true como para false. Uma forma particular de condition coverage conhecida como MCDC, é exigida por um padrão denominado DoD específico para softwares de segurança crítica, como os da aviação. Este padrão, o DO-178B, classifica as falhas em três níveis, e requer um diferente nível de cobertura para cada nível:

Nível C: a falha reduz a margem de segurança Exemplo: link de dados via rádio

Requer: statement coverage

Nível B: a falha reduz a capacidade da aeronave ou da tripulação Exemplo: GPS

Requer: decision coverage

Nível A: a falha causa a perda da aeronave Exemplo: sistema de gerenciamento de vôo Requer: MCDC coverage

Uma outra forma de norma de subdomínio do tipo program-based é utilizada em testes denominados boundary testing, ou testes limite. Estes tipos de testes exigem que os casos extremos para toda sentença condicional sejam avaliados. Por exemplo, se o seu programa testa x < n, seriam necessários casos de teste que produzissem x = n, x = n - 1, e x = n + 1.

(6)

Normas do tipo specification-based são, também, muitas vezes moldadas em termos de subdomínios. Pelo fato de que as especificações são, normalmente, informais - ou seja, não são escritas em nenhuma notação muito precisa - as normas tendem a ser um tanto vagas. A abordagem mais comum é definir subdomínios de acordo com a estrutura da especificação e de acordo com os valores dos tipos de dados subjacentes. Por exemplo, os subdomínios de um método que insere um elemento em um conjunto podem ser:

·o conjunto é vazio

·o conjunto é não vazio e o elemento não está no conjunto

·o conjunto é não vazio e o elemento está no conjunto.

Você pode, também, utilizar na especificação qualquer estrutura condicional para guiá-lo na divisão do domínio de dados de entrada em subdomínios. Além disso, na prática, as pessoas incumbidas de realizar os testes utilizam seu conhecimento a respeito dos tipos de erros que muitas vezes surgem no código. Por exemplo, se você estiver testando um procedimento que encontra um elemento em um array, você, provavelmente, irá colocar o elemento de teste no início, no meio e no fim, simplesmente porque estes casos são propensos a ser manipulados diferentemente no código.

11.6 Viabilidade

Cobertura total é raramente possível. De fato, mesmo que se alcance 100% das sentenças do código, é normalmente impossível alcançar a cobertura total. No mínimo, esta impossibilidade ocorre em razão de código decorrente de programação defensiva, código que, em grande parte, nunca é executado. As operações de um tipo abstrato de dado, que não possuem nenhum cliente, também não serão testadas pelos casos de teste, independente do rigor aplicado; no entanto, estes testes podem ser executados por testes de unidade.

Uma norma é considerada viável se for possível satisfazê-la. Na prática, as normas normalmente não são viáveis. Em termos de subdomínio, elas contêm subdomínios vazios. A questão prática é determinar se um subdomínio particular é vazio ou não; se for vazio, não há razão em se procurar um caso de teste que o satisfaça.

Falando de forma geral, quanto mais elaborada a norma, mais difícil é a sua determinação. Por exemplo, a norma denominada path coverage exige que todos os caminhos do programa sejam executados.

(7)

if C1 then S1; if C2 then S2;

então, para se determinar se o caminho S1;S2 é viável, precisamos determinar se as condições C1 e C2 podem, ambas, serem verdadeiras. Para um programa complexo, esta não é uma tarefa trivial e, no pior caso, não é mais fácil do que determinar seu programa está correto, ou não, através do raciocínio!

A despeito destes problemas, a idéia de se cobrir as possibilidades do programa é muito importante na prática. Se existe um número significante de partes do seu programa que nunca foram executadas, não há como se ter muita certeza que este programa está correto!

11.7 Diretrizes Práticas

Deve estar claro porque nem as normas program-based, nem as normas specification-based são, se utilizadas isoladamente, boas o suficiente. Se você apenas olhar o programa, você não identificará erros por ser omisso. Se você olhar apenas a especificação, você não irá identificar os erros que surgem em decorrência de problemas de implementação, como, por exemplo, quando os limites de um determinado recurso computacional são alcançados, ocasião quando um procedimento de compensação é necessário. Na implementação da classe ArrayList do Java, por exemplo, o array da representação é substituído quando fica cheio. Para se testar este comportamento, será necessário inserir uma quantidade suficiente de elementos para que o array fique cheio.

A experiência sugere que a melhor forma de se desenvolver um suíte de testes é utilizando-se normas do tipo specification-based para guiar o desenvolvimento do suíte e, para testar o suíte, é melhor que se utilizem normas do tipo program-based. Assim, você será capaz de examinar a especificação, e capaz de definir subdomínios de entrada. Baseando-se nessas premissas, você pode escrever os casos de teste. Então, você executa os casos de teste, e mede a cobertura dos testes em relação ao código. Se a cobertura não for adequada, basta que se adicionem novos casos de teste.

Nos padrões industriais, você utilizaria uma ferramenta especial para medir a cobertura dos testes em relação ao código. No curso 6170, não exigiremos que você aprenda a utilizar qualquer ferramenta. Ao invés disso, você deve apenas escolher casos de teste suficientemente elaborados para que possa argumentar que alcançou uma cobertura considerável do código.

As certificações em tempo de execução, especialmente as que representam checagens de invariante, irão dramaticamente aumentar o poder dos seus testes. Você irá encontrar mais bugs e poderá solucioná-los mais facilmente.

Referências

Documentos relacionados

O processo de gerenciamento da capacidade foi desenhado para assegurar que a capacidade da infraestrutura de TI esteja alinhada com as necessidades do negócio. O

O capítulo 3 compreende o desenvolvimento da pesquisa e apresenta de forma detalhada a descrição dos processos e subprocessos de implementação e manutenção de segurança

22 Em pesquisa no Sistemas de Informações do Arquivo Nacional, utilizando a palavra “aborto” para o período que vai de 1830 a 1942, foram encontrados apenas 45

que a população tem direito, como Transportes da Secretaria de Saúde o Ambulâncias Novas, UTI Móvel, Veículos senhor Francisco Belinho de Almeida, o para o Transporte de

NASF (Núcleo de Apoio à Saúde da Família)  Os NASF fazem parte da atenção básica, mas não se constituem como serviços com unidades físicas independentes ou especiais, e não

O ARRAIAL DO IPL 2013 é um evento académico de cariz lúdico, organizado pelas Associações de Estudantes do Instituto Politécnico de Lisboa e que terá lugar na Escola Superior

Já um tanque com a torneira meio aberta por 15 minutos pode chegar a gastar 279 litros de água.. Por isso, o melhor é deixar acumular roupa, colocar a água no tanque

• O exame citológico pode indicar o tipo e quantidade de bactérias envolvidas nas infecções de pele (Figura 3) • Bactérias fagocitadas no interior de neutrófilos são