Quando utilizar: Em testes de algoritmos que trabalham com intervalos de valores de dados de entrada, saída ou, até mesmo, de variáveis locais. O uso típico é em algoritmos com cálculos puramente matemáticos, laços complexos, vetores e matrizes, conjuntos de dados ordenaveis, pontos flutu- antes etc.
Intenção: Verificar erros de programação causados pelos valores limites dos intervalos de dados que o algoritmo trabalha.
Motivação: Estudos mostram que erros em valores limites são frequentes [95]. Erros de validação de dados, falhas de segmentação, laços infinitos, divisão por zero etc. Além disso, erros de valores limites podem ser acarretados devido à interpretação incorreta de requisitos definidos de forma ambígua ou não clara pelos clientes.
Solução: Testar as funcionalidades de acordo com os dados extremos de entrada e saída. Consequências: É feito a prevenção contra erros causados por valores extremos.
Implementação: A implementação dos testes de valores limites não necessitam de soluções especi- ais, apenas os valores de entrada e saída são escolhidos cuidadosamente para forçar cálculos e comparações específicos.
Exemplo - C/CUnit: A Figura 6.28 mostra uma funcionalidade escrita em C que faz a multiplicação de matrizes de inteiros. A implementação, apesar de curta, possui muitos detalhes sutis, o que aumenta as chances do programador cometer erros conceituais ou por distração, tais como trocas de variáveis e erros de precedência de operadores. Além do mais, essa é uma função que aceita uma combinação infinita de dados de entrada, o que torna impossível provar sua correção através de testes automatizados, embora é possível criar uma boa bateria de testes que dá segurança na funcionalidade.
1 /*
2 Matrizes de inteiros A, B e C 3 C(m x o) = A(m x n) * B(n x o)
4 Retorna 0 se sucesso, negativo caso contrário 5 */
6 int matrizXmatriz(int **A, int m, int n, int **B, int o, int **C){
7 int i, j, k; 8 if(m < 1 || n < 1 || o < 1) return -1; 9 for(i = 0; i < m; i++) { 10 for(j = 0; j < o; j++) { 11 C[i][j] = 0; 12 for(k = 0; k < n; k++) 13 C[i][j] = C[i][j] + (A[i][k] * B[k][j]) ; 14 } 15 } 16 }
Figura 6.28: Função escrita em C que calcula a multiplicação de matrizes.
Para essa funcionalidade, problemas de valores limites podem ocorrer tanto com os dados de entrada como os de saída. A Figura 6.29 mostra alguns dos testes que podem ser feitos para
verificar esse tipo de erro. O exemplo utiliza a ferramenta CUnit, mas mostra apenas os cenários de teste. No Apêndice B há mais informações sobre a ferramenta, em particular, como executar os testes com o arcabouço.
1 /* Referências do CUnit, outras referências foram ocultas */ 2 #include < CUnit / CUnit .h >
3
4 /* Funções auxiliares: Implementação oculta para simplificar o exemplo */ 5 /* A = B = C|0 ... 0|
6 |0 ... 0| ... */
7 void inicializaMatrizesZeradas(int** A, int m, int n, int** B, int o, int** C) {}
8
9 /* A|a b| B|e f| C|0 0| 10 |c d| |g h| |0 0| */
11 void inicializaMatrizesDoisPorDoisComValores(int** A, int a, int b, int c, int d,
int** B, int e, int f, int g, int h, int** C) {}
12 13 void verificaMatrizDoisPorDois(A, a, b, c, d) { 14 CU_ASSERT_EQUAL(A[0][0] , a); 15 CU_ASSERT_EQUAL(A[0][1] , b); 16 CU_ASSERT_EQUAL(A[1][0] , c); 17 CU_ASSERT_EQUAL(A[1][1] , d); 18 } 19 20 /* TESTES */ 21
22 /* Função se comporta bem com zeros? */
23 void test_ValoresLimitesDosCalculos(void) {
24 int **A, **B, **C;
25 inicializaMatrizesDoisPorDois(A, 0, 1, 0, 1, B, 1, 0, 1, 0, C);
26 int conseguiuCalcular = matrizXmatriz(A, 2, 2, B, 2, C);
27 CU_ASSERT_TRUE(conseguiuCalcular == 0) ;
28 verificaMatrizDoisPorDois(C, 1, 1, 1, 1) ;
29 }
30
31 /* E com números negativos? */
32 void test_ValoresLimitesDosCalculos(void) {
33 int **A, **B, **C;
34 inicializaMatrizesDoisPorDois(A, 1, -1, 1, -1, B, -1, 1, -1, 1, C);
35 int conseguiuCalcular = matrizXmatriz(A, 2, 2, B, 2, C);
36 CU_ASSERT_TRUE(conseguiuCalcular == 0) ;
37 verificaMatrizDoisPorDois(C, 1, 1, 1, 1) ;
38 }
39
40 /* Resultados (dados de saída) devem pertencer ao intervalo dos inteiros */
41 void test_ValoresLimitesDosResultados(void) {
42 int **A, **B, **C;
43 inicializaMatrizesDoisPorDois(A, INT_MAX, INT_MAX, INT_MAX, INT_MAX, B, 2, 2, 2,
2, C);
44 int conseguiuCalcular = matrizXmatriz(A, 2, 2, B, 2, C);
45 CU_ASSERT_TRUE(conseguiuCalcular < 0) ;
46 }
Figura 6.29: Teste da multiplicação de matrizes usando a biblioteca CUnit.
Exemplo - Scala/JUnit/TestNG: Contudo, esses tipos de erros podem acontecer até com algoritmos mais simples e que utilizam linguagens de programação de mais alto nível. A Figura 6.30 mostra
um exemplo de testes escritos em Scala com JUnit e TestNG para o jogo de carta Poker. As regras do jogo definem uma hierarquia simples de combinações de cartas, mas que programaticamente pode conter erros nos valores limites das regras.
1 // Referências do TestNG e JUnit 2 import org.testng.annotations._
3 import org.junit.Assert._
4 // Imports das classes do sistema foram ocultos 5
6 // Exemplos de testes de valores limites para o jogo de cartas Poker 7 // Obs: Nomes dos métodos de testes definem parte das regras do jogo 8 class ComparacaoEntreCombinacoesLimitesTests {
9
10 @Test def menorParGanhaDeMaiorCartaMaisAlta() {
11 assertTrue(menorPar() > maiorCartaMaisAlta() )
12 assertTrue(maiorCartaMaisAlta() < menorPar() )
13 } 14
15 @Test def menorDoisParesGanhaDeMaiorPar() {
16 assertTrue(menorDoisPares() > maiorPar() )
17 assertTrue(maiorPar() < menorDoisPares() )
18 }
19
20 @Test def menorTrincaGanhaDeDeMaiorDoisPares() {
21 assertTrue(menorTrinca() > maiorDoisPares() )
22 assertTrue(maiorDoisPares() < menorTrinca() )
23 }
24
25 @Test def menorSequenciaGanhaDeDeMaiorTrinca() {
26 assertTrue(menorSequencia() > maiorTrinca() )
27 assertTrue(maiorTrinca() < menorSequencia() )
28 } 29
30 @Test def menorTodasMesmoNaipeGanhaDeDeMaiorSequencia() {
31 assertTrue(menorTodasMesmoNaipe() > maiorSequencia() )
32 assertTrue(maiorSequencia() < menorTodasMesmoNaipe() )
33 } 34
35 @Test def menorFullHouseGanhaDeDeMaiorTodasMesmoNaipe() {
36 assertTrue(menorFullHouse() > maiorTodasMesmoNaipe() )
37 assertTrue(maiorTodasMesmoNaipe() < menorFullHouse() )
38 } 39
40 @Test def menorQuadraGanhaDeDeMaiorFullHouse() {
41 assertTrue(menorQuadra() > maiorFullHouse() )
42 assertTrue(maiorFullHouse() < menorQuadra() )
43 } 44
45 @Test def menorSequenciaTodasMesmoNaipeGanhaDeDeMaiorQuadra() {
46 assertTrue(menorSequenciaTodasMesmoNaipe() > maiorQuadra() )
47 assertTrue(maiorQuadra() < menorSequenciaTodasMesmoNaipe() )
48 }
49 }
Figura 6.30: Teste escrito em Scala dos valores limites das regras do Poker.
Exemplo - Python/UnitTest/QAssertions: Ainda, existem ferramentas que geram testes automatiza- dos para verificar valores limites em regras de validação de dados de entrada. É o caso da
Python-QAssertions, que fornece o método de verificaçao assertValidation. Esse método recebe como argumentos o próprio método em teste e os parâmetros que serão utilizados para sua execução. Se os parâmetros forem objetos herdados da classe ValidationTest (Min, Max, Positive, Negative, Range, InList, NotInList, Blank e NonBlank), então o algoritmo iden- tifica que é necessário verificar valores limites para determinado parâmetro, de acordo com o tipo de validação.
Por exemplo, se um parâmetro for Range(56, 790) (Figura 6.31), serão gerados os testes para valores os 56 e 790, onde é esperado sucesso, ou seja, nenhuma exceção é lançada. Também são feito testes para os valores 55 e 791, para os quais são esperadas exceções. Não obstante, ainda são feitos testes com outros valores menos significativos, tais como 423 (esperado sucesso), 46 e 800 (esperado falha).
1 # Referências do UnitTest 2 import unittest
3 # Referências do Python-QAssertions 4 import qassertions as qa
5 from qassertions import Range
6
7 class UmaClasseDoSistemaTests(unittest.TestCase):
8
9 def setUp(self):
10 self.sistema = UmaClasseDoSistema()
11
12 def testValorTemQueSerMaiorOuIgualQue56MenorOuIgualQue790(self):
13 qa.assertValidation(self.sistema.metodoEmTeste, Range(56 , 790) )
Figura 6.31: Exemplo de verificação de validação com casos limites com geração de casos de teste. A Figura 6.32 mostra como poderiam ficar a implementação dos testes sem usar a geração de casos de teste de validação. Mesmo para os testes de validação de apenas um parâmetro a implementação fica mais extensa. Consequentemente, quanto mais parâmetros precisam ser validados, maior a vantagem do uso da ferramenta. A Figura 6.33 mostra um exemplo de um teste de validação de vários argumentos. É imporante notar que os valores limites são definidos subtraindo e somando 1 dos limites do intervalo, mas também pode-se alterar a precisão para outros valores, como mostra a validação Max no exemplo da figura.
Usos Conhecidos: A teoria de testes de software incentiva o uso desse padrão [95]. A ferramenta Python-QAssertions que fornece um método de asserção que gera testes de valores limites. API para criação de jogos de cartas Card Game Engine, que realiza esses testes para verificar erros de implementação.
1 # Referências do UnitTest 2 import unittest
3 # Referências do Python-QAssertions 4 import qassertions as qa
5
6 class UmaClasseDoSistemaTests(unittest.TestCase):
7
8 def setUp(self):
9 self.sistema = UmaClasseDoSistema()
10
11 def testValorTemQueSerMaiorOuIgualQue56MenorOuIgualQue790(self):
12 self.assertRaises(Exception, self.sistema.metodoEmTeste, 46)
13 qa.assertDontRaiseAnException(self.sistema.metodoEmTeste, 56)
14 self.assertRaises(Exception, self.sistema.metodoEmTeste, 55)
15 qa.assertDontRaiseAnException(self.sistema.metodoEmTeste, 423)
16 qa.assertDontRaiseAnException(self.sistema.metodoEmTeste, 790)
17 self.assertRaises(Exception, self.sistema.metodoEmTeste, 791)
18 self.assertRaises(Exception, self.sistema.metodoEmTeste, 800)
Figura 6.32: Exemplo de verificação de validação com casos limites sem geração dos casos de teste.
1 # Referências do UnitTest 2 import unittest
3 # Referências do Python-QAssertions 4 import qassertions as qa
5 # Tipos de validação:
6 # Números: Min, Max, Positive, Negative, Range 7 # Listas: InList, NotInList
8 # Strings: Blank, NonBlank
9 from qassertions import Min, Max, Range, InList, NotInList, Blank, NonBlank
10
11 class UmaClasseDoSistemaTests(unittest.TestCase):
12
13 def setUp(self):
14 self.sistema = UmaClasseDoSistema()
15
16 def testValidacaoDeVariosDadosDeEntrada(self):
17 qa.assertValidation(self.sistema.metodoEmTeste2,
18 Min(5) , Max(7 , 0.1) , ’valor sem regra de validação’,
19 Range(5 , 10) , InList([1 , 5, 7]) )