Las enumeraciones son simplemente tipos finitos, con nombres personalizados (con suerte significativos). Una enumeración solo puede tener un valor, como el void
que contiene solo null
(algunos idiomas lo llaman unit
y usan el nombre void
de una enumeración sin elementos). Puede tener dos valores, como bool
cual tiene false
y true
. Puede tener tres, como colourChannel
con red
, green
y blue
. Y así.
Si dos enumeraciones tienen el mismo número de valores, entonces son "isomórficas"; es decir, si cambiamos todos los nombres sistemáticamente, 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
contener win
/ lose
/ draw
es isomorfo a lo anterior colourChannel
, ya que podemos reemplazar, por ejemplo , colourChannel
con result
, red
con win
, green
con lose
y blue
con draw
, y siempre que lo hagamos en todas partes (productores y consumidores, analizadores y analizadores, entradas de bases de datos, archivos de registro, etc. ) entonces no habrá cambios en nuestro programa. Cualquier " colourChannel
prueba" que hayamos escrito pasará, ¡aunque ya no colourChannel
haya más!
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, la nueva disposición es isomorfa a la anterior, y por lo tanto podríamos cambiar todos los nombres y nuestras pruebas aún pasarían (tenga en cuenta que no podemos simplemente cambiar la definición; debemos todavía cambia todos los sitios de uso también).
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 ramificar 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 unitaria', 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, tal prueba está verificando la implementación del lenguaje en lugar de su programa. Estas pruebas nunca son una buena idea: incluso si no confía en la implementación del lenguaje, debe probarlo desde el exterior , ya que no se puede confiar en ejecutar las pruebas correctamente.
Entonces esa es la teoría; ¿Qué hay de la práctica? El problema principal con esta caracterización de las enumeraciones es que los programas del "mundo real" rara vez son autónomos: 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í: cambiar una enumeración podría interrumpir la comunicación con un sistema remoto, pero a la inversa, ¡podríamos solucionar ese problema cambiando una enumeración!
En estos escenarios, la enumeración es una pista falsa: ¿y si un sistema necesita que sea esta manera, y otro necesita que sea que 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 "cualquier número entero que elija la interpretación". Entonces, 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 otro lado acepte el formato.
Todavía podemos preguntarnos si la enumeración se está "ejercitando lo suficientemente bien", pero en este caso la enumeración es nuevamente una pista falsa. Lo que realmente nos preocupa es el conjunto de pruebas en sí . Podemos ganar confianza aquí de dos maneras:
- La cobertura del código puede decirnos si la variedad de valores de enumeración que provienen del conjunto de pruebas son suficientes para activar las diversas ramas en el código. Si no, podemos agregar pruebas que desencadenan las ramas descubiertas, o generar una variedad más amplia de enumeraciones en las pruebas existentes.
- La comprobación de propiedades puede decirnos si la variedad de ramas 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 red
, entonces tenemos una cobertura del 100%. Un verificador de propiedades (intentará) generar contraejemplos a nuestras afirmaciones, como generar los valores green
y blue
que olvidamos probar.
- Las pruebas de mutación pueden decirnos si nuestras afirmaciones realmente verifican la enumeración, en lugar de simplemente seguir las ramas e ignorar sus diferencias.