Nos meus testes de unidade, geralmente jogo valores arbitrários no meu código para ver o que ele faz. Por exemplo, se eu souber que foo(1, 2, 3)
deve retornar 17, eu poderia escrever o seguinte:
assertEqual(foo(1, 2, 3), 17)
Esses números são puramente arbitrários e não têm um significado mais amplo (eles não são, por exemplo, condições de contorno, embora eu também teste neles). Eu lutaria para criar bons nomes para esses números, e escrever algo como const int TWO = 2;
é obviamente inútil. Tudo bem escrever os testes assim ou devo fatorar os números em constantes?
Em Todos os números mágicos são criados da mesma forma? , aprendemos que os números mágicos são bons se o significado for óbvio no contexto, mas, nesse caso, os números realmente não têm significado algum.
unit-testing
Kevin
fonte
fonte
1, 2, 3
forem índices de matriz 3D em que você armazenou anteriormente o valor17
, acho que esse teste seria dândi (desde que você também tenha alguns testes negativos). Mas se é o resultado de um cálculo, você deve se certificar de que quem lê este teste vai entender por quefoo(1, 2, 3)
deve ser17
, e números mágicos provavelmente não vai alcançar essa meta.const int TWO = 2;
é ainda pior do que apenas usar2
. Está em conformidade com a redação da regra com a intenção de violar seu espírito.foo
, não significaria nada e, portanto, os parâmetros. Mas, na realidade, eu tenho certeza que a função não tem esse nome, e os parâmetros não têm nomesbar1
,bar2
ebar3
. Faça um exemplo mais realista em que os nomes tenham significado e faça muito mais sentido discutir se os valores dos dados de teste também precisam de um nome.Respostas:
Quando você realmente tem números que não têm nenhum significado?
Geralmente, quando os números têm algum significado, você deve atribuí-los a variáveis locais do método de teste para tornar o código mais legível e autoexplicativo. Os nomes das variáveis devem refletir pelo menos o que a variável significa, não necessariamente seu valor.
Exemplo:
Observe que a primeira variável não é nomeada
HUNDRED_DOLLARS_ZERO_CENT
, masstartBalance
para indicar qual é o significado da variável, mas não que seu valor seja de alguma forma especial.fonte
0.05f
umint
. :)const
variável.calculateCompoundInterest
? Nesse caso, a digitação extra é uma prova de trabalho que você leu a documentação para a função que está testando ou, pelo menos, copiou os nomes dados pelo seu IDE. Não sei ao certo o que isso diz ao leitor sobre a intenção do código, mas se você passar os parâmetros na ordem errada, pelo menos eles poderão dizer o que foi planejado.Se você estiver usando números arbitrários apenas para ver o que eles fazem, provavelmente o que você está procurando é provavelmente dados de teste gerados aleatoriamente ou testes baseados em propriedades.
Por exemplo, a Hipótese é uma biblioteca Python legal para esse tipo de teste e é baseada no QuickCheck .
A idéia é não se restringir a seus próprios valores, mas escolha valores aleatórios que possam ser usados para verificar se suas funções correspondem às especificações. Como uma observação importante, esses sistemas geralmente lembram de qualquer entrada que falhe e, em seguida, garantem que essas entradas sejam sempre testadas no futuro.
O ponto 3 pode ser confuso para algumas pessoas, então vamos esclarecer. Isso não significa que você está afirmando a resposta exata - isso é obviamente impossível de ser feito para contribuições arbitrárias. Em vez disso, você afirma algo sobre uma propriedade do resultado. Por exemplo, você pode afirmar que, depois de anexar algo a uma lista, ele se torna vazio, ou que uma árvore de pesquisa binária com balanceamento automático é realmente equilibrada (usando qualquer critério que a estrutura de dados específica tenha).
No geral, escolher números arbitrários é provavelmente muito ruim - isso realmente não agrega muito valor e é confuso para quem lê. Gerar automaticamente um monte de dados de teste aleatórios e usá-los efetivamente é bom. Encontrar uma biblioteca do tipo Hipótese ou QuickCheck para o seu idioma preferido é provavelmente a melhor maneira de atingir seus objetivos, mantendo-se compreensível para os outros.
fonte
foo
é computação) ...? Se você tivesse 100% de certeza de que seu código fornece a resposta certa, basta inserir esse código no programa e não testá-lo. Se você não estiver, precisará testar o teste, e acho que todo mundo vê para onde isso está indo.d
dias, o cálculo ded
dias + 1 mês deve ser um conhecido taxa mensal percentagem mais elevada), etc.O nome do seu teste de unidade deve fornecer a maior parte do contexto. Não a partir dos valores das constantes. O nome / documentação de um teste deve fornecer o contexto e a explicação apropriados para quaisquer números mágicos presentes no teste.
Se isso não for suficiente, um pouco de documentação deve ser capaz de fornecê-la (seja por meio do nome da variável ou de uma doutrina). Lembre-se de que a própria função possui parâmetros que esperamos ter nomes significativos. Copiar aqueles em seu teste para nomear os argumentos é inútil.
E por último, se suas unittests são complicadas o suficiente para que isso seja difícil / não prático, você provavelmente tem funções muito complicadas e pode considerar por que esse é o caso.
Quanto mais desleixado você escrever testes, pior será o seu código real. Se você sentir a necessidade de nomear seus valores de teste para torná-lo claro, isso sugere fortemente que seu método real precisa de nomes e / ou documentação melhores. Se você encontrar a necessidade de nomear constantes nos testes, examinarei por que você precisa disso - provavelmente o problema não é o teste em si, mas a implementação
fonte
Isso depende muito da função que você está testando. Conheço muitos casos em que os números individuais não têm um significado especial por si só, mas o caso de teste como um todo é construído cuidadosamente e, portanto, tem um significado específico. É isso que se deve documentar de alguma maneira. Por exemplo, se
foo
realmente for um métodotestForTriangle
que decida se os três números podem ter comprimentos válidos das arestas de um triângulo, seus testes podem ser assim:e assim por diante. Você pode melhorar isso e transformar os comentários em um parâmetro de mensagem
assertEqual
que será exibido quando o teste falhar. Você pode melhorar ainda mais e refatorá-lo em um teste orientado a dados (se sua estrutura de teste suportar isso). No entanto, você faz um favor a si mesmo se colocar uma nota no código por que escolheu esses números e qual dos vários comportamentos que está testando com o caso individual.É claro que, para outras funções, os valores individuais dos parâmetros podem ser mais importantes, portanto, usar um nome de função sem sentido, como
foo
ao perguntar como lidar com o significado dos parâmetros, provavelmente não é a melhor idéia.fonte
Por que queremos usar constantes nomeadas em vez de números?
Se você escrever vários testes de unidade, cada um com uma variedade de 3 números (startBalance, juros, anos) - eu apenas agruparia os valores no teste de unidade como variáveis locais. O menor escopo ao qual eles pertencem.
Se você usa um idioma que permite parâmetros nomeados, isso é obviamente supérfluo. Lá, eu apenas empacotaria os valores brutos na chamada do método. Não consigo imaginar nenhuma refatoração tornando essa declaração mais concisa:
Ou use uma estrutura de teste, que permitirá definir os casos de teste em algum array ou formato de mapa:
fonte
Os números estão sendo usados para chamar um método, portanto, certamente a premissa acima está incorreta. Você pode não se importar com os números, mas isso não vem ao caso. Sim, você pode deduzir para que os números são usados por algumas magias do IDE, mas seria muito melhor se você fornecesse apenas os nomes dos valores - mesmo que eles correspondam aos parâmetros.
fonte
assertEqual "Returned value" (makeKindInt 42) (runTest "lvalue_operators")
). Neste exemplo,42
é apenas um valor de espaço reservado que é produzido pelo código no script de teste nomeadolvalue_operators
e verificado quando é retornado pelo script. Não tem nenhum significado, exceto que o mesmo valor ocorre em dois lugares diferentes. Qual seria um nome apropriado aqui que real dê algum significado útil?Se você deseja testar uma função pura em um conjunto de entradas que não são condições de contorno, quase certamente deseja testá-lo em vários conjuntos de entradas que não são (e são) condições de contorno. E para mim isso significa que deve haver uma tabela de valores para chamar a função e um loop:
Ferramentas como as sugeridas na resposta de Dannnno podem ajudá-lo a construir a tabela de valores a serem testados.
bar
,,baz
eblurf
deve ser substituído por nomes significativos, conforme discutido na resposta de Philipp .(Princípio geral discutível aqui: os números nem sempre são "números mágicos" que precisam de nomes; em vez disso, os números podem ser dados . Se faria sentido colocar seus números em uma matriz, talvez uma matriz de registros, provavelmente são dados Por outro lado, se você suspeitar que possa ter dados em suas mãos, considere colocá-los em uma matriz e adquiri-los.)
fonte
Os testes são diferentes do código de produção e, pelo menos nos testes de unidades escritos em Spock, que são curtos e direto ao ponto, não tenho problemas em usar constantes mágicas.
Se um teste tiver 5 linhas e seguir o esquema básico dado / quando / então, extrair esses valores em constantes só tornaria o código mais longo e difícil de ler. Se a lógica for "Quando adiciono um usuário chamado Smith, vejo o usuário Smith retornado na lista de usuários", não faz sentido extrair "Smith" para uma constante.
É claro que isso se aplica se você puder facilmente corresponder os valores usados no bloco "fornecido" (configuração) aos encontrados nos blocos "quando" e "então". Se sua configuração de teste estiver separada (em código) do local em que os dados são usados, pode ser melhor usar constantes. Porém, como os testes são mais independentes, a instalação geralmente fica próxima ao local de uso e o primeiro caso se aplica, o que significa que constantes mágicas são bastante aceitáveis nesse caso.
fonte
Em primeiro lugar, vamos concordar que o "teste de unidade" é freqüentemente usado para cobrir todos os testes automatizados que um programador escreve e que não faz sentido debater como cada teste deve ser chamado….
Eu trabalhei em um sistema em que o software utilizou muitas entradas e elaborou uma “solução” que precisava cumprir algumas restrições, além de otimizar outros números. Como não havia respostas corretas, o software precisou dar uma resposta razoável.
Isso foi feito usando muitos números aleatórios para obter um ponto de partida e, em seguida, usando um "alpinista" para melhorar o resultado. Isso foi executado várias vezes, escolhendo o melhor resultado. Um gerador de números aleatórios pode ser semeado, para que ele sempre forneça os mesmos números na mesma ordem; portanto, se o teste definir uma semente, sabemos que o resultado seria o mesmo em cada execução.
Tivemos muitos testes que fizeram o acima, e verificamos que os resultados eram os mesmos. Isso nos dizia que não havíamos mudado o que aquela parte do sistema fazia por engano durante a refatoração, etc. Isso não nos dizia nada sobre a exatidão dos o que essa parte do sistema fez.
Esses testes eram caros de manter, pois qualquer alteração no código de otimização os interromperia, mas eles também encontraram alguns erros no código muito maior que pré-processou os dados e pós-processou os resultados.
Como “zombamos” do banco de dados, você pode chamar esses testes de “testes unitários”, mas a “unidade” era bastante grande.
Frequentemente, quando você está trabalhando em um sistema sem testes, faz algo como o descrito acima, para confirmar que sua refatoração não altera a saída; espero que melhores testes sejam escritos para o novo código!
fonte
Penso que, neste caso, os números devem ser denominados Números Arbitrários, em vez de Números Mágicos, e apenas comentar a linha como "caso de teste arbitrário".
Certamente, alguns Números Mágicos também podem ser arbitrários, como para valores únicos de "manipulação" (que devem ser substituídos por constantes nomeadas, é claro), mas também podem ser constantes pré-calculadas como "velocidade no ar de um pardal europeu sem carga em quinze dias por quinzena", onde o valor numérico é conectado sem comentários ou contexto útil.
fonte
Não vou me arriscar a dizer um sim / não definitivo, mas aqui estão algumas perguntas que você deve se perguntar ao decidir se está bem ou não.
Se os números não significam nada, por que eles estão lá em primeiro lugar? Eles podem ser substituídos por outra coisa? Você pode fazer a verificação com base em chamadas de método e fluxo, em vez de declarações de valor? Considere algo como o
verify()
método de Mockito que verifica se determinadas chamadas de método foram ou não feitas para zombar de objetos em vez de realmente afirmar um valor.Se os números de fazer significar alguma coisa, então eles devem ser atribuídos a variáveis que são nomeados de forma adequada.
Escrevendo o número
2
comoTWO
pode ser útil em determinados contextos, e não tanto em outros contextos.assertEquals(TWO, half_of(FOUR))
faz sentido para alguém que lê o código. É imediatamente claro o que você está testando.assertEquals(numCustomersInBank(BANK_1), TWO)
, então isso não faz que muito sentido. Por queBANK_1
contém dois clientes? Para que estamos testando?fonte