Las enumeraciones son simplemente tipos finitos, con nombres personalizados (con suerte significativos). Es posible que una enumeración solo tenga un valor, como void
que contenga solo null
(algunos idiomas lo llaman unit
y usan el nombre void
para una enumeración con elementos no ). Puede tener dos valores, como bool
que tiene false
y true
. Puede tener tres, como colourChannel
con red
, green
y blue
. Y así sucesivamente.
Si dos enumeraciones tienen el mismo número de valores, entonces son "isomorfos"; es decir, si cambiamos todos los nombres sistemáticamente, entonces podemos usar uno en lugar de otro y nuestro programa no se comportará de manera diferente. ¡En particular, nuestras pruebas no se comportarán de manera diferente!
Por ejemplo, result
que contiene win
/ lose
/ draw
es isomorfo al colourChannel
anterior, ya que podemos reemplazar, por ejemplo, colourChannel
con result
, red
con win
, green
con lose
y blue
con draw
, y siempre que lo hagamos everywhere (productores y consumidores, analizadores y serializadores, entradas de base de datos, archivos de registro, etc.) entonces no habrá cambios en nuestro programa. Cualquier " colourChannel
pruebas" que escribimos todavía pasará, ¡aunque ya no haya colourChannel
!
Además, si una enumeración contiene más de un valor, siempre podemos reorganizar esos valores para obtener una nueva enumeración con el mismo número de valores. Dado que el número de valores no ha cambiado, el nuevo acuerdo es isomorfo al anterior, y por lo tanto podríamos cambiar todos los nombres y nuestras pruebas seguirían pasando (tenga en cuenta que no podemos simplemente cambie la definición; también debemos cambiar todos los sitios de uso).
Lo que esto significa es que, en lo que respecta a la máquina, las enumeraciones son "nombres distinguibles" y nada más . Lo único que podemos hacer con una enumeración es dividir si dos valores son iguales (por ejemplo, red
/ red
) o diferentes (por ejemplo, red
/ blue
). Así que eso es lo único que puede hacer una "prueba de unidad", por ejemplo,
( red == red ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
( red != green) || throw TestFailure;
( red != blue ) || throw TestFailure;
...
Como dice @ jesm00, dicha prueba está verificando la implementación de lenguaje en lugar de su programa. Estas pruebas nunca son una buena idea: incluso si no confías en la implementación del lenguaje, deberías probarlo desde fuera , ¡ya que no se puede confiar en que las pruebas se ejecuten correctamente!
Así que esa es la teoría; ¿Qué pasa con la práctica? El principal problema con esta caracterización de enumeraciones es que los programas del "mundo real" rara vez son autocontenidos: tenemos versiones heredadas, implementaciones remotas / integradas, datos históricos, copias de seguridad, bases de datos en vivo, etc., por lo que nunca podemos realmente "cambiar" todas las apariciones de un nombre sin perder algunos usos.
Sin embargo, tales cosas no son la 'responsabilidad' de la enumeración en sí misma: cambiar una enumeración podría interrumpir la comunicación con un sistema remoto, pero a la inversa, podemos arreglar este problema al cambiar una enumeración.
En tales escenarios, la enumeración es una pista falsa: ¿qué pasa si un sistema necesita que sea de esta manera , y otro necesita que sea de esa manera ? No pueden ser ambas cosas, ¡no importa cuántas pruebas escribamos! El verdadero culpable aquí es la interfaz de entrada / salida, que debería producir / consumir formatos bien definidos en lugar de "cualquiera que sea el número entero que interprete las elecciones". Así que la solución real es probar las interfaces de E / S : con pruebas unitarias para verificar que está analizando / imprimiendo el formato esperado, y con pruebas de integración para verificar que el formato sea realmente aceptado por el otro lado .
Todavía podemos preguntarnos si la enumeración se está 'ejerciendo lo suficientemente bien', pero en este caso la enumeración es nuevamente una pista falsa. Lo que realmente nos preocupa es el propio conjunto de pruebas . Podemos ganar confianza aquí de un par de maneras:
- La cobertura del código puede decirnos si la variedad de valores de enumeración que proviene del conjunto de pruebas es suficiente para activar las diversas ramas del código. Si no, podemos agregar pruebas que activen las ramas descubiertas, o generar una variedad más amplia de enumeraciones en las pruebas existentes.
- La verificación de propiedades nos puede decir si la variedad de sucursales en el código es suficiente para manejar las posibilidades de tiempo de ejecución. Por ejemplo, si el código solo maneja
red
, y solo probamos con red
, entonces tenemos una cobertura del 100%. Un verificador de propiedades (intentará) generará contraejemplos de nuestras aserciones, como generar los valores green
y blue
que olvidamos probar.
- Las pruebas de mutación pueden indicar si nuestras afirmaciones en realidad comprueban la enumeración, en lugar de seguir las ramas e ignorar sus diferencias.