Executando verificação de segurança...
1

Design de caso de teste - Estratégia (parte 1)

O texto abaixo tem por base o livro The Art of Software Testing e abordará de forma simples diversos temas apresentados com profundidade no livro.

Desenhando o Caso de Teste

What subset of all possible test cases has the highest probability of detecting the most errors?

A pergunta acima é tomada como motriz para toda discussão do capítulo 4. Apesar da complexidade da resposta, categoricamente temos a afirmação de que o subconjunto de testes que provê entradas aleatórias é definitivamente o menos eficiente dos métodos.

Nobody ever promised that software testing would be easy. To quote an old sage, “If you thought designing and coding that program was hard, you ain’t seen nothing yet.”

A ideia da citação acima não é desistimular, é apenas reconhecer nossas limitações. Quanto antes abrirmos mão da perfeição e mirarmos no possível, mais próximo estaremos de construir o melhor conjuntos de testes dadas as nossas restrições.

Então sobre quais bases construiremos nossos testes?

O procedimento recomendado é desenvolver casos de teste usando os métodos de Hidden Box e, em seguida, desenvolver casos de teste suplementares conforme necessário com os métodos de Viewed Box.

Substituí os termos White Box e Black Box por Viewed Box e Hidden Box, a ideia é utilizar termos neutros. Contudo é possível que textos mais aprofundados sobre o tema ainda utilizem a terminologia mais antiga

Hidden BoxViewed Box
Equivalence partitioningStatement coverage
Boundary ValueDecision coverage
Error GuessingCondition coverage
Cause-effect graphingDecision-condition coverage
Multiple-condition coverage

Viewed Box

Comecemos pelo mundo onde reconhecemos nossa aplicação por aquilo que nos é mais familiar, nosso código.

  1. Analisamos código;
  2. Identificamos os caminhos;
  3. Cobrimos os caminhos possíveis com um código associado;

É isso, resolvido todos os problemas de testes até a próxima...

Infelizmente essa abordagem é extremamente falha a pergunta é: Por que não percorremos todos os caminhos possíveis dentro da lógica da nossa aplicação? R.: Loops

É inviável testarmos cobrindo todos os caminhos possíveis em programas com loops.

Statement coverage

Considerando que podemos testar todos os caminhos possíveis podemos realizar todos os testes baseados nos estados possíveis da aplicação.

Tomemos por exemplo o seguinte pseudo-código abaixo:

func sample(int x, int y, int z) {
  if (x>1 and y==0) {
    z = z / x
  }

  if (x == 2 or z > 1) {
    z = z + 1
  }
}

poderíamos representar o estado possível desse programa através da seguinte imagem.

Dado que para um caso que seja verdadeiro em todos os IFs teríamos cobertura de todos os estados possíveis para a variável Z, única com alteração. Um único caso de teste seria suficiente para cobertura de todas as alterações de estado. Contudo esse tipo de cobertura é bem fraco e dificilmente usado como parâmetro para definir uma suíte de teste, caindo no desuso na maioria das vezes.

Decision coverage

Um critério muito mais forte para cobertura lógica de testes é o decision coverage ou branch coverage. Para esse tipo de estrutura temos a necessidade de escrever testes que consigam percorrer cada ramificação da estrutura de decisão ao menos uma vez.

Podemos listar como estruturas de decisão:

  • switch
  • do - while
  • if - else
  • GOTO (em linguagens como FORTRAN)

Usualmente o decision coverage pode satisfazer o statement coverage, desde que os estados estejam todos contemplados por algum bloco de estrutura de decisão. Configs, metaprogramação, geração de código (Mobx), etc... podem ser verdadeiros desafios para esse tópico.

Agora está tudo resolvido, basta cobrir todas as ramificações, correto? Infelizmente não! Temos os casos de ON-unit ou para os familiarizados com Spring, handlers de exception. Acionar um catch não me garante que estaria cobrindo todos os cenários de exception pressuposto para um determinado cenário.

O código pode sempre quebrar de maneiras inimagináveis.

Condition coverage

E se as condições de true ou false morem múltiplas? Para isso podemos observar a estratégia de condition coverage. Percorrer o true ou false é um caso de cobertura bem melhor do que o statement, porém ainda temos muitos casos possíveis para alcançar as diversas ramificações.

observemos a tabela abaixo para um simples AND

X > 1Y == 0result
VerdadeiroVerdadeiroVerdadeiro
VerdadeiroFalsoFalso
FalsoVerdadeiroFalso
FalsoFalsoFalso

Considerando que o último caso já teria sido coberto pelo caso das linhas 2 e 3(entenderemos isso melhor ao abordar o tema de classe de equivalência e testes em hidden box), observamos que as condições que levam a um mesmo resultado, FALSO, pode ser alcançado por mais de uma combinação.

Decision-Condition coverage

Parece complicado a princípio identificar esses cenários, mas podemos reescrever o nosso código de uma forma menos intuitiva e com isso utilizar os conceitos de Decision coverage para alcançar o consition coverage.

Observemos o pseudo-código abaixo:

func sample(int x, int y, int z) {
  if (x>1) {
    if(y==0){
      z = z / x
    }
  }

  if (x == 2) {
    z = z + 1
  }

  if (z > 1) {
    z = z + 1
  }
}

Nele temos as mesmas condições descritas no código que já vimos anteriormente. Agora refaçamos o gráfico que visualizamos anteriormente.

Para garantir a condition-coverage podemos isolar cada verificação num elemento de estrutura de decisão e percorrer todas as ramificações de forma a garantir a decision-coverage.

Hidden-box

Em breve no próximo artigo.

Fontes

The Art of Software Testing - Glenford J. Myers, Corey Sandler, Tom Badgett

Carregando publicação patrocinada...