Eu tenho uma classe que visa gerar uma senha aleatória de um comprimento que também é aleatório, mas limitada a estar entre um comprimento mínimo e máximo definido.
Estou construindo testes de unidade e me deparei com um pequeno problema interessante com essa classe. A idéia por trás de um teste de unidade é que ele deve ser repetível. Se você executar o teste cem vezes, ele deverá fornecer os mesmos resultados cem vezes. Se você estiver dependendo de algum recurso que pode ou não estar lá ou que esteja ou não no estado inicial que você espera, deverá zombar do recurso em questão para garantir que seu teste seja sempre sempre repetível.
Mas e nos casos em que o SUT deve gerar uma saída indeterminada?
Se eu fixar o comprimento mínimo e máximo no mesmo valor, posso verificar facilmente se a senha gerada tem o comprimento esperado. Mas se eu especificar um intervalo de comprimentos aceitáveis (digamos 15 - 20 caracteres), agora você terá o problema de poder executar o teste centenas de vezes e obter 100 passes, mas na 101ª execução, poderá recuperar uma sequência de 9 caracteres.
No caso da classe de senha, que é bastante simples em sua essência, ela não deve ser um grande problema. Mas isso me fez pensar no caso geral. Qual é a estratégia geralmente aceita como a melhor a ser adotada ao lidar com SUTs que estão gerando resultados indeterminados por design?
fonte
Respostas:
A saída "não determinística" deve ter uma maneira de se tornar determinística para fins de teste de unidade. Uma maneira de lidar com a aleatoriedade é permitir a substituição do mecanismo aleatório. Aqui está um exemplo (PHP 5.3+):
Você pode fazer uma versão de teste especializada da função que retorne qualquer sequência de números que desejar para garantir que o teste seja totalmente repetível. No programa real, você pode ter uma implementação padrão que pode ser o substituto, se não for substituído.
fonte
A senha de saída real pode não ser determinada sempre que o método for executado, mas ainda terá recursos determinados que podem ser testados, como comprimento mínimo, caracteres que caem dentro de um conjunto de caracteres determinado etc.
Você também pode testar se a rotina retorna um resultado determinado a cada vez, propagando seu gerador de senhas com o mesmo valor cada vez.
fonte
Teste contra "o contrato". Quando o método é definido como "gera senhas de 15 a 20 caracteres com az", teste-o desta maneira
Além disso, você pode extrair a geração, para que tudo que depende dela possa ser testado usando outra classe de gerador "estático"
fonte
Você tem um
Password generator
e precisa de uma fonte aleatória.Como você afirmou na pergunta, a
random
produz resultados não determinísticos, pois é um estado global . Isso significa que ele acessa algo fora do sistema para gerar valores.Você nunca pode se livrar de algo assim para todas as suas classes, mas pode separar a geração de senha para a criação de valores aleatórios.
Se você estruturar o código dessa maneira, poderá zombar dos
RandomSource
testes.Você não poderá testar 100%,
RandomSource
mas as sugestões obtidas para testar os valores nesta pergunta podem ser aplicadas a ele (como testes querand->(1,26);
sempre retornam um número de 1 a 26).fonte
No caso de Monte Carlo, um físico de partículas, escrevi "testes de unidade" {*} que invocam a rotina não determinística com uma semente aleatória predefinida e depois executam um número estatístico de vezes e verificam se há violações de restrições (níveis de energia acima da energia de entrada deve estar inacessível, todas as passagens devem selecionar algum nível, etc.) e regressões em relação aos resultados registrados anteriormente.
{*} Esse teste viola o princípio "tornar o teste rápido" para testes de unidade; portanto, você pode se sentir melhor caracterizando-os de alguma outra maneira: testes de aceitação ou testes de regressão, por exemplo. Ainda assim, usei minha estrutura de teste de unidade.
fonte
Eu tenho que discordar da resposta aceita , por duas razões:
(Observe que pode ser uma boa resposta em muitas circunstâncias, mas não em todas, e talvez não na maioria.)
Então, o que eu quero dizer com isso? Bem, por sobreajuste, quero dizer um problema típico de teste estatístico: a sobreajuste acontece quando você testa um algoritmo estocástico contra um conjunto de dados excessivamente restrito. Se você voltar e refinar seu algoritmo, implicitamente fará com que ele se ajuste muito bem aos dados de treinamento (você acidentalmente ajusta seu algoritmo aos dados de teste), mas todos os outros dados talvez não o sejam (porque você nunca testa contra ele) .
(Aliás, esse é sempre um problema oculto nos testes de unidade. É por isso que bons testes são completos ou, pelo menos, representativos. para uma determinada unidade, e isso geralmente é difícil.)
Se você tornar seus testes determinísticos, tornando o gerador de números aleatórios conectável, sempre teste no mesmo conjunto de dados muito pequeno e (geralmente) não representativo . Isso distorce seus dados e pode causar viés em sua função.
O segundo ponto, impraticabilidade, surge quando você não tem controle sobre a variável estocástica. Isso geralmente não acontece com geradores de números aleatórios (a menos que você precise de uma fonte aleatória "real"), mas pode acontecer quando os estocásticos se infiltram no seu problema de outras maneiras. Por exemplo, ao testar código simultâneo: as condições da corrida são sempre estocásticas, você não pode (facilmente) torná-las determinísticas.
A única maneira de aumentar a confiança nesses casos é testar muito . Espuma, enxágüe, repita. Isso aumenta a confiança, até um certo nível (quando a troca por testes adicionais se torna insignificante).
fonte
Você realmente tem várias responsabilidades aqui. O teste de unidade e, em particular, o TDD são ótimos para destacar esse tipo de coisa.
As responsabilidades são:
1) Gerador de números aleatórios. 2) Formatador de senha.
O formatador de senha usa o gerador de números aleatórios. Injete o gerador no seu formatador através do construtor como uma interface. Agora você pode testar completamente o seu gerador de números aleatórios (teste estatístico) e o formatador injetando um gerador de números aleatórios simulados.
Você não apenas obtém um código melhor, mas também obtém melhores testes.
fonte
Como os outros já mencionaram, seu teste de unidade desse código removendo a aleatoriedade.
Você também pode querer ter um teste de nível superior que deixe o gerador de números aleatórios no lugar, teste apenas o contrato (tamanho da senha, caracteres permitidos, ...) e, em caso de falha, despeja informações suficientes para permitir que você reproduza o sistema estado na única instância em que o teste aleatório falhou.
Não importa que o teste em si não seja repetível - desde que você possa encontrar a razão pela qual ele falhou desta vez.
fonte
Muitas dificuldades de teste de unidade tornam-se triviais quando você refatora seu código para separar dependências. Um banco de dados, um sistema de arquivos, o usuário ou, no seu caso, uma fonte de aleatoriedade.
Outra maneira de ver é que os testes de unidade devem responder à pergunta "esse código faz o que pretendo fazer?". No seu caso, você não sabe o que pretende que o código faça porque não é determinístico.
Com essa mente, separe sua lógica em partes pequenas, fáceis de entender e facilmente testadas em isolamento. Especificamente, você cria um método distinto (ou classe!) Que usa uma fonte de aleatoriedade como entrada e produz a senha como saída. Esse código é claramente determinístico.
No seu teste de unidade, você alimenta sempre a mesma entrada não muito aleatória. Para fluxos aleatórios muito pequenos, apenas codifique os valores em seu teste. Caso contrário, forneça uma semente constante ao RNG em seu teste.
Em um nível mais alto de teste (chame de "aceitação" ou "integração" ou o que for), você permitirá que o código seja executado com uma verdadeira fonte aleatória.
fonte
A maioria das respostas acima indica que zombar do gerador de números aleatórios é o caminho a seguir, no entanto, eu estava simplesmente usando a função mt_rand incorporada. Permitir zombaria significaria reescrever a classe para exigir que um gerador de números aleatórios fosse injetado no momento da construção.
Ou assim eu pensei!
Uma das conseqüências da adição de namespaces é que a zombaria incorporada nas funções do PHP passou de incrivelmente difícil para trivialmente simples. Se o SUT estiver em um determinado espaço para nome, tudo o que você precisa fazer é definir sua própria função mt_rand no teste de unidade nesse espaço para nome e ele será usado em vez da função PHP incorporada durante o teste.
Aqui está o conjunto de testes finalizado:
Eu pensei em mencionar isso, porque substituir as funções internas do PHP é outro uso para namespaces que simplesmente não me ocorreram. Obrigado a todos pela ajuda.
fonte
Há um teste adicional que você deve incluir nessa situação, que garante que chamadas repetidas para o gerador de senhas produzam senhas diferentes. Se você precisar de um gerador de senha com segurança para threads, também deve testar chamadas simultâneas usando vários threads.
Isso basicamente garante que você esteja usando sua função aleatória corretamente e não reproduza novamente todas as chamadas.
fonte