Mais do que provável, você chegou ao seu valor de 65 bilhões de testes calculando todas as combinações possíveis de entradas no sistema em teste, ou calculando a complexidade ciclomática e assumindo que um teste deve ser escrito para cada um desses caminhos de execução exclusivos.
Não é assim que testes reais são escritos, porque, como outros pôsteres e comentaristas indicaram, o poder técnico necessário para executar testes de 65 bilhões é impressionante. Isso seria como escrever um teste que exercesse um método para adicionar dois inteiros, ligando cada possível permutação de dois valores de 32 bits e verificando o resultado. É uma insanidade total. Você precisa desenhar a linha e identificar um subconjunto de todos os possíveis casos de teste, o que entre eles garantiria que o sistema se comportaria como esperado em toda a faixa de entradas. Por exemplo. você testa a adição de alguns números "comuns", testa alguns cenários de número negativo, testa limites técnicos, como cenários de estouro, e testa todos os cenários que devam resultar em erro. Como foi mencionado, esses vários tipos de testes exercitam "classes de equivalência"; Eles permitem que você tire uma amostra representativa das possíveis entradas, junto com quaisquer "outliers" conhecidos, e diga com uma confiança extremamente alta de que, como esses cenários passam, todos os cenários semelhantes a esses passarão.
Considere um dos códigos básicos katas, o gerador numeral romano. A tarefa, a ser executada usando técnicas de TDD em um estilo "dojo", é escrever uma função que pode aceitar qualquer número de 1 a 3000 e produzir o numeral romano correto para esse valor numérico.
Você não resolve este problema escrevendo 3000 testes unitários, um de cada vez, e passando-os por vez. Isso é loucura; o exercício normalmente leva entre uma e duas horas e você estaria lá por dias testando cada valor individual. Em vez disso, você fica esperto. Você começa com o caso base mais simples (1 == "I"), implementa isso usando uma estratégia "menor código" ( return "I";
) e depois procura como o código que você tem se comportará incorretamente em outro cenário esperado (2 == "II"). Enxague e repita; mais do que provável, você substituiu sua implementação inicial por algo que repete o caractere "I" sempre que necessário (como return new String('I',number);
). Isso obviamente passará em um teste para o III, então você não se incomoda; em vez disso, você escreve o teste para 4 == "IV", que você sabe que a implementação atual não fará corretamente.
Ou, em um estilo mais analítico, você examina cada decisão condicional que é feita pelo código (ou precisa ser) e escreve um teste projetado para inserir o código para cada possível resultado de cada decisão. Se você tiver 5 instruções if (cada uma com uma ramificação true e false), cada uma delas totalmente independente da outra, codifique 10 testes, não 32. Cada teste será projetado para afirmar duas coisas sobre uma determinada decisão possível; primeiro que a decisão correta é tomada e, em seguida, que o código inserido, desde que a condição esteja correta. Você não codifica um teste para cada possível permutação de decisões independentes. Se as decisões forem dependentes, você terá que testar mais delas em combinação, mas há menos combinações desse tipo porque algumas decisões só são tomadas quando outra decisão teve um resultado específico.