Ao criar um sistema como uma IA, que pode seguir muitos caminhos diferentes muito rapidamente, ou realmente qualquer algoritmo que possua várias entradas diferentes, o conjunto de resultados possível pode conter um grande número de permutações.
Que abordagem deve ser adotada para usar o TDD ao criar um sistema que produz muitas e diferentes permutações de resultados?
Respostas:
Adotando uma abordagem mais prática da resposta do pdr . TDD é tudo sobre design de software, e não testes. Você usa testes de unidade para verificar seu trabalho à medida que avança.
Portanto, em um nível de teste de unidade, é necessário projetar as unidades para que possam ser testadas de maneira completamente determinística. Você pode fazer isso pegando qualquer coisa que torne a unidade não determinística (como um gerador de números aleatórios) e abstraindo-a. Digamos que temos um exemplo ingênuo de um método que decide se uma jogada é boa ou não:
Esse método é muito difícil de testar e a única coisa que você realmente pode verificar nos testes de unidade são seus limites ... mas isso exige muitas tentativas para chegar aos limites. Então, em vez disso, vamos abstrair a parte aleatória criando uma interface e uma classe concreta que agrupa a funcionalidade:
A
Decider
classe agora precisa usar a classe concreta através de sua abstração, ou seja, a Interface. Essa maneira de fazer as coisas é chamada injeção de dependência (o exemplo abaixo é um exemplo de injeção de construtor, mas você pode fazer isso com um setter também):Você pode se perguntar por que esse "inchaço do código" é necessário. Bem, para iniciantes, agora você pode zombar do comportamento da parte aleatória do algoritmo porque o
Decider
agora possui uma dependência que segue oIRandom
"contrato" do s. Você pode usar uma estrutura de simulação para isso, mas este exemplo é simples o suficiente para se codificar:A melhor parte é que isso pode substituir completamente a implementação concreta "real". O código se torna fácil de testar assim:
Espero que isso lhe dê idéias sobre como projetar seu aplicativo para que as permutações possam ser forçadas, para que você possa testar todos os casos extremos e outros enfeites.
fonte
O TDD estrito tende a quebrar um pouco em sistemas mais complexos, mas isso não importa muito em termos práticos - uma vez que você vai além de poder isolar entradas individuais, basta escolher alguns casos de teste que fornecem cobertura razoável e os utilizam.
Isso requer algum conhecimento do que a implementação fará bem, mas isso é mais uma preocupação teórica - é altamente improvável que você construa uma IA que foi especificada em detalhes por usuários não técnicos. Está na mesma categoria que passar os testes codificados para os casos de teste - oficialmente o teste é a especificação e a implementação é a solução correta e a mais rápida possível, mas na verdade nunca acontece.
fonte
TDD não é sobre testes, é sobre design.
Longe de desmoronar com a complexidade, é excelente nessas circunstâncias. Isso levará você a considerar o problema maior em peças menores, o que levará a um design melhor.
Não tente testar todas as permutações do seu algoritmo. Apenas crie teste após teste, escreva o código mais simples para fazer o teste funcionar, até que você tenha suas bases cobertas. Você deve entender o que quero dizer sobre resolver o problema, porque você será incentivado a fingir partes do problema enquanto estiver testando outras partes, para evitar ter que escrever 10 bilhões de testes para 10 bilhões de permutações.
Editar: eu queria adicionar um exemplo, mas não tinha tempo antes.
Vamos considerar um algoritmo de classificação no local. Poderíamos ir em frente e escrever testes que cobrem a extremidade superior da matriz, a extremidade inferior da matriz e todos os tipos de combinações estranhas no meio. Para cada um, teríamos que construir uma matriz completa de algum tipo de objeto. Isso levaria tempo.
Ou podemos resolver o problema em quatro partes:
A primeira é a única parte complicada do problema, mas, abstraindo-o do resto, você o tornou muito, muito mais simples.
O segundo é quase certamente tratado pelo próprio objeto, pelo menos opcionalmente, em muitas estruturas de tipo estático, haverá uma interface para mostrar se essa funcionalidade está implementada. Então você não precisa testar isso.
O terceiro é incrivelmente fácil de testar.
O quarto apenas lida com dois ponteiros, solicita à classe de passagem que os mova, pede uma comparação e, com base no resultado dessa comparação, pede que os itens sejam trocados. Se você falsificou os três primeiros problemas, pode testá-lo com muita facilidade.
Como levamos a um design melhor aqui? Digamos que você tenha simplificado e implementado uma classificação de bolha. Funciona, mas, quando você vai para a produção e precisa lidar com um milhão de objetos, é muito lento. Tudo o que você precisa fazer é escrever uma nova funcionalidade transversal e trocá-la. Você não precisa lidar com a complexidade de lidar com os outros três problemas.
Você encontrará a diferença entre testes de unidade e TDD. O testador de unidade dirá que isso tornou seus testes frágeis, que se você tivesse testado entradas e saídas simples, agora não precisaria escrever mais testes para sua nova funcionalidade. O TDDer dirá que eu separei as preocupações adequadamente para que cada classe que eu faça faça uma coisa e uma coisa bem.
fonte
Não é possível testar todas as permutações de um cálculo com muitas variáveis. Mas isso não é novidade, sempre foi verdade para qualquer programa acima da complexidade dos brinquedos. O objetivo dos testes é verificar a propriedade da computação. Por exemplo, classificar uma lista com 1000 números exige algum esforço, mas qualquer solução individual pode ser verificada com muita facilidade. Agora, embora existam 1000! possíveis (classes de) entradas para esse programa e você não pode testá-las todas, é completamente suficiente gerar apenas 1000 entradas aleatoriamente e verificar se a saída está realmente classificada. Por quê? Como é quase impossível escrever um programa que classifique de forma confiável 1000 vetores gerados aleatoriamente sem também estar correto em geral (a menos que você o monte deliberadamente para manipular determinadas entradas mágicas ...)
Agora, em geral, as coisas são um pouco mais complicadas. Há realmente ter sido erros onde um mailer não iria entregar e-mails aos usuários se eles têm um 'f' em seu nome de usuário e o dia da semana é sexta-feira. Mas considero um esforço desperdiçado tentar antecipar tal estranheza. Seu conjunto de testes deve fornecer uma confiança constante de que o sistema faz o que você espera nas entradas que você espera. Se funcionar em certos casos, você notará logo depois de experimentar o primeiro caso e poderá escrever um teste especificamente contra esse caso (que geralmente também abrange uma classe inteira de casos semelhantes).
fonte
Veja os casos extremos mais alguma entrada aleatória.
Para pegar o exemplo de classificação:
Se funcionar rápido para eles, você pode ter certeza de que funcionará para todas as entradas.
fonte