Enums são simplesmente tipos finitos, com nomes personalizados (espero que sejam significativos). Um enum pode ter apenas um valor, como o void
que contém apenas null
(alguns idiomas chamam isso unit
e usam o nome void
de um enum sem elementos!). Pode ter dois valores, como bool
qual tem false
e true
. Pode ter três, como colourChannel
com red
, green
e 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, result
contendo win
/ lose
/ draw
é isomorfo ao acima colourChannel
, uma vez que pode substituir por exemplo, colourChannel
com result
, red
com win
, green
com lose
e blue
com 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 " colourChannel
teste" que escrevemos ainda será aprovado, mesmo que não exista colourChannel
mais!
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
red
e testamos apenas red
, teremos 100% de cobertura. Um verificador de propriedades (tentará) gerar contra-exemplos para nossas afirmações, como gerar os valores green
e blue
que 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.