¿Debería uno probar los valores de una enumeración usando pruebas unitarias?

15

Si tiene una enumeración solo con valores (no hay métodos como uno podría hacer en Java), y esta enumeración es parte de la definición comercial del sistema, ¿debería uno escribir pruebas unitarias para ello?

Estaba pensando que deberían escribirse, incluso si pudieran parecer simples y redundantes. Considero que lo que concierne a la especificación de negocios debe escribirse explícitamente en una prueba, ya sea con unit / Integration / ui / etc. pruebas o mediante el uso del sistema de tipos del lenguaje como método de prueba. Dado que los valores que debe tener una enumeración (por ejemplo, en Java), desde el punto de vista de la empresa, no pueden probarse utilizando el sistema de tipos, creo que debería haber una prueba unitaria para eso.

Esta pregunta no es similar a esta, ya que no aborda el mismo problema que el mío. En esa pregunta hay una función comercial (savePeople) y la persona está preguntando sobre la implementación interna (forEach). Allí, hay una capa intermedia de negocios (la función de salvar personas) que encapsula la construcción del lenguaje (para cada uno). Aquí la construcción del lenguaje (enum) es la que se usa para especificar el comportamiento desde el punto de vista empresarial.

En este caso, el detalle de implementación coincide con la "naturaleza verdadera" de los datos, es decir: un conjunto (en el sentido matemático) de valores. Podría decirse que podría usar un conjunto inmutable, pero los mismos valores aún deberían estar presentes allí. Si usa una matriz, se debe hacer lo mismo para probar la lógica empresarial. Creo que el enigma aquí es el hecho de que la construcción del lenguaje coincide muy bien con la naturaleza de los datos. No estoy seguro de haberme explicado correctamente

IS1_SO
fuente
19
¿Cómo sería exactamente una prueba unitaria de una enumeración?
jonrsharpe
@jonrsharpe Afirmaría que los valores que están dentro de la enumeración son los que usted espera. Lo haría iterando sobre los valores de la enumeración, agregándolos a un conjunto, por ejemplo, como cadenas. Ordene ese conjunto. La comparación que se compara con una lista ordenada de valores escritos a mano en la prueba. Deberían coincidir.
IS1_SO
1
@jonrsharpe, me gusta pensar en las pruebas unitarias también como "definiciones" o "requisitos" escritos en código. Una prueba unitaria de una enumeración sería tan simple como verificar el número de elementos en la enumeración y sus valores. Especialmente en C #, donde las enumeraciones no son clases, pero pueden asignarse directamente a enteros, garantizando sus valores y no programar por coincidencia puede resultar útil para fines de serialización.
Machado
2
En mi humilde opinión, no es mucho más útil que probar si 2 + 2 = 4 para verificar si el universo es correcto. Usted prueba el código usando esa enumeración, no la enumeración en sí.
Agent_L

Respuestas:

39

Si tiene una enumeración solo con valores (no hay métodos como uno podría hacer en Java), y esta enumeración es parte de la definición comercial del sistema, ¿debería uno escribir pruebas unitarias para ello?

No, son solo estado.

Básicamente, el hecho de que esté utilizando una enumeración es un detalle de implementación ; ese es el tipo de cosas que es posible que desee refactorizar en un diseño diferente.

La prueba de integridad de las enumeraciones es análoga a la prueba de que todos los enteros representables están presentes.

Sin embargo, probar los comportamientos que admiten las enumeraciones es una buena idea. En otras palabras, si comienza desde un conjunto de pruebas aprobadas y comenta cualquier valor de enumeración único, entonces al menos una prueba debe fallar (los errores de compilación se consideran fallas).

VoiceOfUnreason
fuente
55
Pero en este caso, el detalle de implementación coincide con la "verdadera naturaleza" de los datos, es decir: un conjunto (en el sentido matemático) de valores. Podría decirse que podría usar un conjunto inmutable, pero los mismos valores aún deberían estar presentes allí. Si usa una matriz, se debe hacer lo mismo para probar la lógica empresarial. Creo que el enigma aquí es el hecho de que la construcción del lenguaje coincide muy bien con la naturaleza de los datos. No estoy seguro de haberme explicado correctamente.
IS1_SO
44
@ IS1_SO - al punto de VOU que una prueba debería fallar: ¿lo hizo? En cuyo caso, no fue necesario probar el Enum específicamente. ¿No? Tal vez eso es una señal de que usted podría modelar el código sea más simple y crear una abstracción sobre la 'verdadera naturaleza' de los datos - por ejemplo, independientemente de las cartas de una baraja, es lo que realmente necesita tener una representación de [ Hearts, Spades, Diamonds, Clubs] si ¿Alguna vez solo cardas si una carta es roja / negra?
otradave
1
@ IS1_SO, digamos que tiene una enumeración de códigos de error y desea arrojar un null_ptrerror. Ahora que tiene un código de error a través de la enumeración. El código que busca un null_ptrerror también busca el código a través de la enumeración. Por lo tanto, puede tener un valor de 5(por ej.). Ahora necesita agregar otro código de error. Se cambia la enumeración (digamos que agregamos una nueva a la parte superior de la enumeración) El valor de null_ptres ahora 6. ¿Es esto un problema? ahora devuelve un código de error 6y prueba 6. Mientras todo sea lógicamente coherente, está bien, a pesar de que este cambio rompa su prueba teórica.
Baldrickk
17

No prueba una declaración de enumeración . Puede probar si la entrada / salida de la función tiene los valores de enumeración esperados. Ejemplo:

enum Parity {
    Even,
    Odd
}

Parity GetParity(int x) { ... }

Usted no escribir pruebas que verifican entonces enumeración Paritydefine los nombres de las Eveny Odd. Tal prueba no tendría sentido ya que simplemente estaría repitiendo lo que ya está indicado por el código. Decir lo mismo dos veces no lo hace más correcto.

Usted hace las pruebas de verificación de escritura GetParitydigamos volverán Evena 0, Oddde 1 y así sucesivamente. Esto es valioso porque no está repitiendo el código, está verificando el comportamiento del código, independientemente de la implementación. Si el código dentro GetParityfuera completamente reescrito, las pruebas seguirían siendo válidas. De hecho, los principales beneficios de las pruebas unitarias son que le dan la libertad de reescribir y refactorizar el código de manera segura, al garantizar que el código siga funcionando como se esperaba.

Pero si tiene una prueba que garantiza que una declaración enum define los nombres esperados, cualquier cambio que realice en la enumeración en el futuro requerirá que también cambie la prueba. Esto significa que no es solo el doble de trabajo, también significa que se pierde cualquier beneficio de la prueba unitaria. Si tiene que cambiar el código y probarlo al mismo tiempo , entonces no hay protección contra la introducción de errores.

JacquesB
fuente
He actualizado mi pregunta en un esfuerzo por abordar esta respuesta, échale un vistazo para ver si eso ayuda.
IS1_SO
@ IS1_SO: OK, eso me confunde: ¿está generando dinámicamente los valores de enumeración o qué está sucediendo?
JacquesB
No. Lo que quise decir es que en este caso la construcción del lenguaje seleccionada para representar los valores es una enumeración. Pero como sabemos, es un detalle de implementación. ¿Qué sucede si uno selecciona una matriz, o un Set <> (en Java) o una cadena con algunos tokens de separación para representar los valores? Si ese es el caso, entonces tendría sentido probar que los valores contenidos son los que interesan al negocio. Ese es mi punto. ¿Ayuda esta explicación?
IS1_SO
33
@ IS1_SO: ¿Está hablando de probar que una instancia de enumeración devuelta por una función tiene un cierto valor esperado? Porque sí, podrías probar eso. Simplemente no necesita probar la declaración de enumeración en sí.
JacquesB
11

Si existe el riesgo de que el cambio de la enumeración rompa su código, entonces seguro, cualquier cosa con el atributo [Flags] en C # sería un buen caso porque agregar un valor entre 2 y 4 (3) sería un bit 1 y 2 en lugar de un Artículo discreto.

Es una capa de protección.

Debería considerar tener un código de práctica de enumeración con el que todos los desarrolladores estén familiarizados. No confíe en que las representaciones textuales de la enumeración son comunes, pero esto podría entrar en conflicto con sus pautas de serialización.

He visto a personas "corregir" el uso de mayúsculas en las entradas de enumeración, ordenarlas alfabéticamente o por algún otro grupo lógico que rompió otros bits de código incorrecto.

Ian
fuente
55
Si los valores numéricos de la enumeración se usan en cualquier lugar, por ejemplo, cuando se almacenan en una base de datos, la reordenación (incluida la eliminación o inserción antes del último valor) podría hacer que los registros existentes no sean válidos.
stannius
33
+1, esta respuesta está subestimada. Si sus enumeraciones son parte de la serialización, la interfaz de entrada con una palabra externa o información componible a nivel de bits, definitivamente será necesario comprobar su coherencia en cada versión del sistema. Al menos si te preocupa la compatibilidad con versiones anteriores, lo que generalmente es algo bueno.
Machado
11

No, una prueba que comprueba que una enumeración contiene todos los valores válidos y nada más esencialmente está repitiendo la declaración de la enumeración. Solo estaría probando que el lenguaje implementa correctamente la construcción enum, que es una prueba sin sentido.

Dicho esto, debe probar el comportamiento que depende de los valores de enumeración. Por ejemplo, si está utilizando los valores de enumeración para serializar entidades a json o lo que sea, o almacenando los valores en una base de datos, debe probar el comportamiento de todos los valores de la enumeración. De esa manera, si se modifica la enumeración, al menos una de las pruebas debería fallar. En cualquier caso, lo que estaría probando es el comportamiento en torno a su enumeración, no la declaración de enumeración en sí.

jesm00
fuente
3

Su código debería funcionar correctamente independientemente de los valores reales de una enumeración. Si ese es el caso, entonces no se necesitan pruebas unitarias.

Pero es posible que tenga un código donde cambiar un valor de enumeración romperá las cosas. Por ejemplo, si un valor de enumeración se almacena en un archivo externo, y después de cambiar la lectura del valor de enumeración, el archivo externo dará el resultado incorrecto. En ese caso, tendrá un GRAN comentario cerca de la enumeración advirtiendo a cualquiera que no modifique ningún valor, y puede escribir una prueba unitaria que verifique los valores numéricos.

gnasher729
fuente
1

En general, simplemente comprobar que una enumeración tiene una lista codificada de valores no es de mucho valor, como dicen otras respuestas, porque entonces solo necesita actualizar la prueba y la enumeración juntas.

Una vez tuve el caso de que un módulo usaba tipos de enumeraciones de otros dos módulos y se asignaba entre ellos. (Una de las enumeraciones tenía lógica adicional, la otra era para el acceso a la base de datos, ambas tenían dependencias que debían aislarse entre sí).

En este caso, agregué una prueba (en el módulo de mapeo) que verificó que todas las entradas de enumeración en la enumeración de origen también existen en la enumeración de destino (y, por lo tanto, que la asignación siempre funcionaría). (En algunos casos, también revisé al revés).

De esta manera, cuando alguien agrega una entrada de enumeración a una de las enumeraciones y se olvida de agregar la entrada correspondiente a la otra, una prueba comienza a fallar.

Paŭlo Ebermann
fuente
1

Las enumeraciones son simplemente tipos finitos, con nombres personalizados (con suerte significativos). Una enumeración solo puede tener un valor, como el voidque contiene solo null(algunos idiomas lo llaman unity usan el nombre voidde una enumeración sin elementos). Puede tener dos valores, como boolcual tiene falsey true. Puede tener tres, como colourChannelcon red, greeny 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, resultcontener win/ lose/ drawes isomorfo a lo anterior colourChannel, ya que podemos reemplazar, por ejemplo , colourChannelcon result, redcon win, greencon losey bluecon 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 " colourChannelprueba" que hayamos escrito pasará, ¡aunque ya no colourChannelhaya 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 greeny blueque 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.
Warbo
fuente
1

No. Las pruebas unitarias son para unidades de prueba.

En la programación orientada a objetos, una unidad es a menudo una interfaz completa, como una clase, pero podría ser un método individual.

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

Una prueba automatizada para una enumeración declarada probaría la integridad del lenguaje y la plataforma en la que se ejecuta, en lugar de la lógica en el código creado por el desarrollador. No serviría para ningún propósito útil: la documentación se incluye ya que el código que declara la enumeración sirve como documentación, así como también el código que lo probaría.

digimunk
fuente
0

Se espera que pruebe el comportamiento observable de su código, los efectos de las llamadas a métodos / funciones en el estado observable. Mientras el código haga lo correcto, está bien, no necesita probar nada más.

No necesita afirmar explícitamente que un tipo de enumeración tiene las entradas que espera, al igual que no afirma explícitamente que una clase realmente existe o que tiene los métodos y atributos que espera.

En realidad, al probar el comportamiento, usted afirma implícitamente que las clases, los métodos y los valores involucrados en la prueba existen, por lo que no es necesario que lo haga explícitamente.

Tenga en cuenta que no necesita nombres significativos para que su código haga lo correcto, eso es solo una conveniencia para las personas que leen su código. Se podría hacer su trabajo de código con los valores de enumeración como foo, bar... y métodos como frobnicate().

Deja de dañar a Monica
fuente