Outro critério para a classificação de testes é o de caixa-preta e caixa-branca. Em suma, teste de caixa-preta é aquele que observa o comportamento entrada-saída, sem ter acesso ao funcionamento interno do software (no caso de testes de integração) ou do funcionamento interno dos componentes (no caso de testes de unidade). Uma vez que os requisitos funcionais descrevem a relação entre as entradas e as saídas, eles podem ser diretamente testados por testes de caixa-preta.
Nos testes de caixa-preta, deve-se preocupar em exercitar todos os casos normais de funcionamento, ou seja, aquelas situações que fazem parte da operação habitual do produto, e que estão descritas nos requisitos. Deve-se também exercitar os casos chamados "robustez", que são as condições anormais de operação, como entradas fora da faixa de operação, combinações de entradas incoerentes, eventos inesperados para estados específicos. Caso não existam requisitos completos na descrição dos casos anormais de operação, é necessário contar com a experiência e criatividade do testador para imaginar quais seriam tais situações. Mesmo que não existam requisitos que digam qual é o comportamento esperado, é importante testar a robustez para avaliar se o comportamento não é algo inaceitável, como travar ou começar a operar de maneira incoerente.
A segunda classe de testes é o de caixa-branca, que como já vimos, não é necessária na cobertura dos requisitos. Sua utilidade, no entanto, é a cobertura estrutural do código fonte. Cobertura estrutural é a avaliação de quais foram os caminhos exercitados dentro da estrutura do software. Imaginemos a estrutura do software como um fluxograma de "if"s e "else"s de decisões.
Agora é importante esclarecer a diferença entre decisão e condição. Quando a execução de um código se depara com um comando "if", ele irá tomar uma decisão de se entrará no bloco ou se saltará para fora dele, em outras palavras, irá avaliar se a decisão é verdadeira ou falsa. A condição será aquilo que está dentro dos parêntesis, que pode ser uma condição simples, como no caso de "if(a > 10)" ou composta, por exemplo com 3 condições: "if((a > 10) && (b == 0) && (c < a))". Concluimos que cada decisão pode ser composta por uma ou mais condições.
Dada esta explicação, podemos introduzir os tipos de cobertura estrutural existentes. A mais simples delas é a cobertura de "statement", que consiste em avaliar quais linhas de código o conjunto de testes exercitou. Quando os testes passam por todas as linhas de código, dizemos que ele cobriu 100% do critério de "statement".
Outras duas medidas de cobertura que existem é a cobertura de condição que avalia o teste de verdadeiros e falsos em cada condição, e a cobertura de decisão que avalia o teste de cada decisão. Vale notar que cobertura 100% de condição não significa necessariamente cobertura 100% de decisão, nem vice-versa.
Imaginemos um caso como "if((a == 1) && (b == 1))". A cobertura de condição pode ser dada por testes de: a=1, b=0 e em seguida a=0, b=1, o que não é suficiente para que a decisão seja testada como verdadeira. A cobertura de decisão pode ser dada por: a=1, b=0 e em seguida a=1, b=1, o que não é suficiente para cobrir completamente a primeira condição. Para associar decisões e condições, existe o critério de cobertura condição-decisão, que consiste em satisfazer toda a tabela-verdade para as condições que compõem a decisão. No entanto, este critério é muito rigoroso, pois o número de testes corresponde ao número de condições elevada ao quadrado, o que pode ser muito (imagine uma decisão com 8 condições: seriam necessários 256 testes).
Para resolver este problema, mas ainda manter uma correlação entre as condições e as decisões, existe o critério de MC/DC (modified condition-decision coverage). Este critério de teste visa cobrir de maneira inteligente as condições de forma que elas garantam a cobertura das decisões. Deve-se testar uma combinação de condições de forma a mostrar que a inversão de uma condição é responsável por afetar o resultado da decisão (inverter a decisão). O conjunto de teste mostra que a decisão é completamente dependente de cada uma das suas condições. É mais simples exemplificar do que explicar na teoria.
Imaginemos a decisão: if((a > 10) && (b == 0) && (c < a)). Queremos mostrar que cada uma das condições afeta a decisão. Por se tratar de uma combinação com AND, partimos de um caso onde todos as condições são verdadeiras:
a=11, b=0, c=10 - a decisão é, portanto, verdadeira
então modificamos isoladamente (um por vez) cada uma das condições para falso:
a=10, b=0, c=10 - primeira condição falsa torna a decisão falsa
a=11, b=1, c=10 - segunda condição falsa torna a decisão falsa
a=11, b=0, c=11 - terceira condição falsa torna a decisão falsa
Estes 4 casos de teste são suficiente para garantir uma cobertura de 100% do MC/DC desta decisão. Se a decisão fosse de OR, no primeiro teste todas as condições deveriam ser falsas e então nos testes seguintes deveríamos tornar cada condição verdadeira. Existem os casos de combinações de ANDs e ORs, que também devem ser tratados imaginando pares de testes em que, alterando somente uma condição, altera-se o resultado da decisão.
Os critérios de cobertura vistos até o momento consideram o aspecto interno dos componentes de software. Quando pensamos na interface entre componentes, outro critério torna-se necessário. A cobertura de acoplamento de dados e controle (data and control coupling) tem por objetivo avaliar se os testes de integração exercitaram completamente os sinais que conectam um componente a outro. A diferenciação entre sinais de dados e de controle se dá porque existem sinais que simplesmente farão parte de um cálculo no componente que o recebe e sinais que afetam o fluxo de execução do componente que o recebe. Um sinal de velocidade é um exemplo de um sinal de dado e um sinal de inibição de uma função é um exemplo de um sinal de controle.
Como os sinais de controle são geralmente booleanos, é mais fácil estabelecer que o seu critério de cobertura é testá-lo nos valores verdadeiro e falso. Os sinais de dados são mais subjetivos, e não existindo uma padronização quanto ao seu critério de cobertura, cabe ao time de verificação a definição de quando se considera o sinal coberto. Pode-se, por exemplo, definir que o sinal estará coberto se existirem testes para valores positivos e negativos.
Independente de qual seja o critério de cobertura estrutural, a sua avaliação deve ser feita considerando o conjunto de testes executados naquele componente ou no software integrado. Não faz sentido avaliar a cobertura dada por um teste de um único requisito. A medida da cobertura estrutural pode ser feita utilizando-se ferramentas dedicadas a este fim, que instrumentam o código colocando pontos de medição e que produzem um relatório de cobertura quando o código for testado.
No início deste capítulo falamos que os testes de caixa-preta cobrem os requisitos funcionais. Os testes de caixa-branca não são senão os mesmos testes, mas aplicados de uma maneira diferente, instrumentando o código. O que quero dizer é que não se criam testes de caixa-preta e testes de caixa-branca como conjuntos separados. Todos os testes devem ser criados para satisfazer os requisitos. A cobertura estrutural é a consequência, medindo o quanto que os testes foram efetivos. Testes efetivos para requisitos bem escritos deveriam ser suficientes para dar uma alta cobertura de cada um dos critérios.
A falta de cobertura estrutural pode indicar um dos seguintes problemas: requisito com ambiguidade que não permitiu ao testador capturar todos os detalhes de funcionamento; código criado sem a existência de requisito; ou falta de testes. Em cada um destes casos existe um problema nos artefatos que precisa ser corrigido. Mas existe um caso em que a falta de cobertura não representa um problema. É quando o implementador opta por criar uma decisão para detalhar um requisito: suponha um requisito que descreve uma fórmula de cálculo entre entradas e saídas. Para garantir uma boa precisão dos cálculos e ao mesmo tempo manter um bom desempenho, o implementador pode criar um "if" e um "else" para implementar uma fórmula mais precisa para valores próximos de zero e uma fórmula mais aproximada para valores grandes. Apesar de existir a decisão no código, ele está associado ao requisito e não existe problema.

Comentários
Postar um comentário