Deve-se testar os valores de uma enum usando testes de unidade?

15

Se você possui uma enumeração apenas com valores (nenhum método como se poderia fazer em Java) e essa enumeração faz parte da definição de negócios do sistema, deve-se escrever testes de unidade para isso?

Eu estava pensando que eles deveriam ser escritos, mesmo que pareçam simples e redundantes. Considero que o que diz respeito à especificação de negócios deve ser explicitamente escrito em um teste, seja com unit / integration / ui / etc. testes ou usando o sistema de tipos da linguagem como método de teste. Como os valores que uma enumeração (por exemplo, em Java) deve ter, do ponto de vista dos negócios, não podem ser testados usando o sistema de tipos, acho que deve haver um teste de unidade para isso.

Esta pergunta não é semelhante a esta, pois não trata do mesmo problema que o meu. Nessa pergunta, há uma função comercial (savePeople) e a pessoa está perguntando sobre a implementação interna (forEach). Lá, há uma camada de negócios intermediária (a função salvar pessoas) que encapsula a construção da linguagem (forEach). Aqui, o construto de linguagem (enum) é aquele usado para especificar o comportamento do ponto de vista comercial.

Nesse caso, o detalhe da implementação coincide com a "natureza verdadeira" dos dados, ou seja: um conjunto (no sentido matemático) de valores. É possível usar um conjunto imutável, mas os mesmos valores ainda devem estar presentes lá. Se você usar uma matriz, o mesmo deverá ser feito para testar a lógica de negócios. Eu acho que o enigma aqui é o fato de que a construção da linguagem coincide muito bem com a natureza dos dados. Não sei se me expliquei corretamente

IS1_SO
fonte
19
Como seria exatamente um teste de unidade de um enum?
precisa saber é o seguinte
@jonrsharpe Afirma que os valores que estão dentro da enumeração são aqueles que você espera. Eu faria isso iterando sobre os valores da enumeração, adicionando-os a um conjunto, por exemplo, como strings. Peça esse conjunto. A comparação que compara com uma lista ordenada de valores escritos manualmente no teste. Eles devem combinar.
IS1_SO 27/09/17
1
@ Jonrsharpe, eu gosto de pensar em testes de unidade também como "definições" ou "requisitos" escritos no código. Um teste de unidade de uma enumeração seria tão simples quanto verificar o número de itens na enumeração e seus valores. Especialmente em C #, onde as enumerações não são classes, mas podem ser mapeadas diretamente para números inteiros, garantindo que seus valores e a não programação por coincidência possam ser úteis para fins de serialização.
Machado
2
IMHO não é muito mais útil do que testar se 2 + 2 = 4 para verificar se o Universo está certo. Você testa o código usando essa enumeração, não a enumeração em si.
Agent_L 28/09

Respostas:

39

Se você possui uma enumeração apenas com valores (nenhum método como se poderia fazer em Java) e essa enumeração faz parte da definição de negócios do sistema, deve-se escrever testes de unidade para isso?

Não, eles são apenas estado.

Fundamentalmente, o fato de você estar usando uma enumeração é um detalhe de implementação ; esse é o tipo de coisa que você pode querer refatorar em um design diferente.

Enums de teste para completude é análogo ao teste de que todos os números inteiros representáveis ​​estão presentes.

Testar os comportamentos que as enumerações suportam, no entanto, é uma boa ideia. Em outras palavras, se você iniciar a partir de um conjunto de testes aprovado e comentar qualquer valor de enumeração única, pelo menos um teste deverá falhar (os erros de compilação são considerados falhas).

VoiceOfUnreason
fonte
5
Mas, neste caso, o detalhe da implementação coincide com a "verdadeira natureza" dos dados, ou seja: um conjunto (no sentido matemático) de valores. É possível usar um conjunto imutável, mas os mesmos valores ainda devem estar presentes lá. Se você usar uma matriz, o mesmo deverá ser feito para testar a lógica de negócios. Eu acho que o enigma aqui é o fato de que a construção da linguagem coincide muito bem com a natureza dos dados. Não tenho certeza se me expliquei corretamente.
IS1_SO
4
@ IS1_SO - ao ponto de VOU que um teste deve falhar: foi? Nesse caso, você não precisou testar o Enum especificamente. Não foi? Talvez isso seja um sinal de que você pode modelar seu código mais simples e criar uma abstração sobre a 'verdadeira natureza' dos dados - por exemplo, independentemente das cartas em um baralho, você realmente precisa ter uma representação [ Hearts, Spades, Diamonds, Clubs] se você só cartão se um cartão é vermelho / preto?
anotherdave
1
@ IS1_SO digamos que você tenha uma enumeração de códigos de erro e deseja gerar um null_ptrerro. Agora que possui um código de erro via enum. O código que verifica um null_ptrerro também consulta o código através da enumeração. Portanto, pode ter um valor de 5(para ex). Agora você precisa adicionar outro código de erro. A enumeração é alterada (digamos que adicionamos uma nova na parte superior da enumeração) O valor de null_ptré agora 6. Isso é um problema? agora você retorna um código de erro 6e faz o teste 6. Desde que tudo seja logicamente consistente, você estará bem, apesar dessa mudança ter quebrado seu teste teórico.
precisa saber é o seguinte
17

Você não testa uma declaração enum . Você pode testar se a entrada / saída da função tem os valores de enumeração esperados. Exemplo:

enum Parity {
    Even,
    Odd
}

Parity GetParity(int x) { ... }

Você não escreve testes verificando, em seguida, enum Paritydefine os nomes Evene Odd. Esse teste seria inútil, pois você apenas repetiria o que já é declarado pelo código. Dizer a mesma coisa duas vezes não a torna mais correta.

Você fazer testes de escrita verificando GetParitydigamos retornará Evenpara 0, Oddpara 1 e assim por diante. Isso é valioso porque você não está repetindo o código, está verificando o comportamento do código, independentemente da implementação. Se o código interno GetParityfosse completamente reescrito, os testes ainda seriam válidos. De fato, os principais benefícios dos testes de unidade são que eles oferecem liberdade para reescrever e refatorar o código com segurança, garantindo que o código ainda funcione conforme o esperado.

Mas se você tiver um teste que garanta que uma declaração de enum defina os nomes esperados, qualquer alteração feita no enum no futuro exigirá que você altere também o teste. Isso significa que não é apenas o dobro do trabalho, também significa que qualquer benefício para o teste de unidade é perdido. Se você precisar alterar o código e testar ao mesmo tempo , não há proteção contra a introdução de bugs.

JacquesB
fonte
Atualizei minha pergunta em um esforço para abordar esta resposta. Confira se isso ajuda.
IS1_SO
@ IS1_SO: OK, isso me confunde - você está gerando dinamicamente os valores enum ou o que está acontecendo?
JacquesB
Não. O que eu quis dizer é que, neste caso, o construto de linguagem selecionado para representar os valores é um enum. Mas, como sabemos, esse é um detalhe de implementação. O que acontece se alguém selecionar uma matriz, um conjunto <> (em Java) ou uma sequência com alguns tokens de separação para representar os valores? Se for esse o caso, faria sentido testar se os valores contidos são os de interesse dos negócios. Esse é o meu ponto. Essa explicação ajuda?
IS1_SO
3
@ IS1_SO: Você está falando sobre o teste de uma instância enum retornada de uma função com um certo valor esperado? Porque sim, você pode testar isso. Você simplesmente não precisa testar a própria declaração de enum.
precisa saber é o seguinte
11

Se houver um risco de que a alteração da enum interrompa seu código, com certeza, qualquer coisa com o atributo [Flags] em C # seria um bom caso, pois adicionar um valor entre 2 e 4 (3) seria 1 e 2 bit a bit em vez de um item discreto.

É uma camada de proteção.

Você deve ter um código de prática enum com o qual todos os desenvolvedores estejam familiarizados. Não confie nas representações textuais da enumeração comum, mas isso pode entrar em conflito com suas diretrizes de serialização.

Vi pessoas "corrigir" a capitalização de entradas enum, classificá-las em ordem alfabética ou por algum outro agrupamento lógico, que quebrou outros bits do código incorreto.

Ian
fonte
5
Se os valores numéricos da enumeração forem usados ​​em qualquer lugar, por exemplo, quando armazenados em um banco de dados, a reordenação (incluindo a remoção ou inserção antes do último valor) poderá fazer com que os registros existentes se tornem inválidos.
Stannius
3
+1, esta resposta é subestimada. Se suas enumerações fazem parte da serialização, da interface de entrada com palavras externas ou informações composíveis bit a bit, elas definitivamente precisam ser testadas quanto à consistência em cada versão do sistema. Pelo menos se você estiver preocupado com a compatibilidade com versões anteriores, o que geralmente é uma coisa boa.
Machado
11

Não, um teste que verifica se um enum contém todos os valores válidos e nada mais está basicamente repetindo a declaração do enum. Você só testaria se a linguagem implementa corretamente a construção enum, que é um teste sem sentido.

Dito isto, você deve testar o comportamento que depende dos valores da enumeração. Por exemplo, se você estiver usando os valores da enumeração para serializar entidades para json ou o que quer que seja, ou armazenando os valores em um banco de dados, deverá testar o comportamento de todos os valores da enumeração. Dessa forma, se o enum for modificado, pelo menos um dos testes falhará. De qualquer forma, o que você estaria testando é o comportamento em torno de sua enumeração, não a própria declaração de enumeração.

jesm00
fonte
3

Seu código deve estar funcionando corretamente independentemente dos valores reais de uma enumeração. Se for esse o caso, não serão necessários testes de unidade.

Mas você pode ter um código onde a alteração de um valor de enumeração quebrará as coisas. Por exemplo, se um valor de enum for armazenado em um arquivo externo, e após alterar o valor de enum, a leitura do arquivo externo dará o resultado errado. Nesse caso, você terá um comentário GRANDE próximo ao enum alertando qualquer um para não modificar nenhum valor e poderá muito bem escrever um teste de unidade que verifique os valores numéricos.

gnasher729
fonte
1

Em geral, apenas verificar se uma enumeração possui uma lista de valores embutida não tem muito valor, como disseram outras respostas, porque é necessário atualizar o teste e a enumeração juntos.

Uma vez tive um caso de que um módulo usava tipos de enumerações de dois outros módulos e mapeava entre eles. (Um dos enums tinha lógica adicional, o outro era para acesso ao banco de dados, ambos tinham dependências que deveriam ser isoladas um do outro.)

Nesse caso, adicionei um teste (no módulo de mapeamento) que verificou que todas as entradas de enum na enum de origem também existem na enum de destino (e, portanto, que o mapeamento sempre funcionaria). (Em alguns casos, também verifiquei o contrário.)

Dessa forma, quando alguém adicionava uma entrada de enumeração a uma das enumerações e esquecia de adicionar a entrada correspondente à outra, um teste começava a falhar.

Paŭlo Ebermann
fonte
1

Enums são simplesmente tipos finitos, com nomes personalizados (espero que sejam significativos). Um enum pode ter apenas um valor, como o voidque contém apenas null(alguns idiomas chamam isso unite usam o nome voidde um enum sem elementos!). Pode ter dois valores, como boolqual tem falsee true. Pode ter três, como colourChannelcom red, greene blue. E assim por diante.

Se duas enumerações têm o mesmo número de valores, elas são "isomórficas"; ou seja, se mudarmos todos os nomes sistematicamente, podemos usar um no lugar do outro e nosso programa não se comportará de maneira diferente. Em particular, nossos testes não se comportarão de maneira diferente!

Por exemplo, resultcontendo win/ lose/ drawé isomorfo ao acima colourChannel, uma vez que pode substituir por exemplo, colourChannelcom result, redcom win, greencom losee bluecom draw, e enquanto fazemos isso em todos os lugares (produtores e consumidores, analisadores e serializadores, entradas de banco de dados, arquivos de log, etc. ), não haverá alterações em nosso programa. Qualquer " colourChannelteste" que escrevemos ainda será aprovado, mesmo que não exista colourChannelmais!

Além disso, se uma enumeração contiver mais de um valor, sempre podemos reorganizá- los para obter uma nova enumeração com o mesmo número de valores. Como o número de valores não mudou, o novo arranjo é isomórfico ao antigo, e, portanto, poderíamos mudar todos os nomes e nossos testes ainda passariam (observe que não podemos simplesmente mudar a definição; devemos ainda mudar todos os sites de uso também).

O que isso significa é que, no que diz respeito à máquina, enumerações são "nomes distinguíveis" e nada mais . A única coisa que podemos fazer com uma enumeração é ramificar se dois valores são iguais (por exemplo, red/ red) ou diferentes (por exemplo, red/ blue). Portanto, essa é a única coisa que um 'teste de unidade' pode fazer, por exemplo

(  red == red  ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
(  red != green) || throw TestFailure;
(  red != blue ) || throw TestFailure;
...

Como @ jesm00 diz, esse teste está verificando a implementação da linguagem, e não o seu programa. Esses testes nunca são uma boa idéia: mesmo que você não confie na implementação do idioma, você deve testá-lo de fora , pois não é possível executar os testes corretamente!

Então essa é a teoria; e a prática? O principal problema com essa caracterização das enumerações é que os programas do mundo real raramente são independentes: temos versões herdadas, implantações remotas / incorporadas, dados históricos, backups, bancos de dados ativos etc. para que nunca possamos realmente 'mudar' todas as ocorrências de um nome sem perder alguns usos.

No entanto, essas coisas não são de responsabilidade do próprio enum: alterar um enum pode interromper a comunicação com um sistema remoto, mas, inversamente, podemos resolver esse problema alterando um enum!

Em tais cenários, a enumeração é uma pista falsa: que se um sistema necessita que seja esse caminho, e outro precisa que ele seja de que maneira? Não pode ser os dois, não importa quantos testes escrevamos! O verdadeiro culpado aqui é a interface de entrada / saída, que deve produzir / consumir formatos bem definidos, em vez de "qualquer número inteiro escolhido pela interpretação". Portanto, a solução real é testar as interfaces de E / S : com testes de unidade para verificar se está analisando / imprimindo o formato esperado e com testes de integração para verificar se o formato é realmente aceito pelo outro lado.

Ainda podemos nos perguntar se o enum está sendo "exercitado completamente o suficiente", mas nesse caso o enum é novamente um arenque vermelho. Na verdade, estamos preocupados com o próprio conjunto de testes . Podemos ganhar confiança aqui de duas maneiras:

  • A cobertura do código pode nos dizer se a variedade de valores de enumeração provenientes do conjunto de testes é suficiente para acionar as várias ramificações no código. Caso contrário, podemos adicionar testes que acionam as ramificações descobertas ou geram uma variedade maior de enumerações nos testes existentes.
  • A verificação de propriedade pode nos dizer se a variedade de ramificações no código é suficiente para lidar com as possibilidades de tempo de execução. Por exemplo, se o código manipular apenas rede testamos apenas red, teremos 100% de cobertura. Um verificador de propriedades (tentará) gerar contra-exemplos para nossas afirmações, como gerar os valores greene blueque esquecemos de testar.
  • O teste de mutação pode nos dizer se nossas afirmações realmente verificam o enum, em vez de apenas seguir os ramos e ignorar suas diferenças.
Warbo
fonte
1

Não. Os testes de unidade são para unidades de teste.

Na programação orientada a objetos, uma unidade geralmente é uma interface inteira, como uma classe, mas pode ser um método individual.

https://en.wikipedia.org/wiki/Unit_testing

Um teste automatizado para uma enum declarada estaria testando a integridade do idioma e a plataforma na qual ele está sendo executado, em vez da lógica no código criado pelo desenvolvedor. Seria inútil - a documentação incluída, pois o código que declara a enum serve como documentação, assim como o código que a testaria.

digimunk
fonte
0

Você deve testar o comportamento observável do seu código, os efeitos do método / função chama no estado observável. Desde que o código faça a coisa certa, você não precisa testar mais nada.

Você não precisa afirmar explicitamente que um tipo de enum possui as entradas esperadas, assim como não afirma explicitamente que uma classe realmente existe ou que possui os métodos e atributos que você espera.

Na verdade, ao testar o comportamento, você está implicitamente afirmando que as classes, métodos e valores envolvidos no teste existem, portanto, não é necessário explicá-lo explicitamente.

Observe que você não precisa de nomes significativos para que seu código faça a coisa certa; isso é apenas uma conveniência para as pessoas que leem seu código. Você pode fazer seu código funcionar com valores de enumeração como foo, bar... e métodos como frobnicate().

Pare de prejudicar Monica
fonte