En mis pruebas unitarias, a menudo arrojo valores arbitrarios a mi código para ver qué hace. Por ejemplo, si sé que foo(1, 2, 3)
se supone que devuelve 17, podría escribir esto:
assertEqual(foo(1, 2, 3), 17)
Estos números son puramente arbitrarios y no tienen un significado más amplio (no son, por ejemplo, condiciones de contorno, aunque también las pruebo). Me gustaría encontrar buenos nombres para estos números, y escribir algo así const int TWO = 2;
obviamente no es útil. ¿Está bien escribir las pruebas de esta manera, o debo factorizar los números en constantes?
En es Todos los números mágicos crean de la misma? , aprendimos que los números mágicos están bien si el significado es obvio por el contexto, pero en este caso los números en realidad no tienen ningún significado.
fuente
1, 2, 3
hay índices de matriz 3D donde previamente almacenó el valor17
, entonces creo que esta prueba sería excelente (siempre y cuando también tenga algunas pruebas negativas). Pero si es el resultado de un cálculo, debe asegurarse de que cualquiera que lea esta prueba entienda por quéfoo(1, 2, 3)
debería serlo17
, y los números mágicos probablemente no logren ese objetivo.const int TWO = 2;
es incluso peor que solo usarlo2
. Se ajusta a la redacción de la regla con la intención de violar su espíritu.foo
, no significaría nada y, por lo tanto, los parámetros. Pero, en realidad, estoy bastante seguro de que la función no tiene ese nombre, y los parámetros no tienen nombresbar1
,bar2
ybar3
. Haga un ejemplo más realista donde los nombres tengan un significado, entonces tiene mucho más sentido discutir si los valores de los datos de prueba también necesitan un nombre.Respuestas:
¿Cuándo realmente tienes números que no tienen ningún significado?
Por lo general, cuando los números tienen algún significado, debe asignarlos a las variables locales del método de prueba para que el código sea más legible y fácil de explicar. Los nombres de las variables deben al menos reflejar lo que significa la variable, no necesariamente su valor.
Ejemplo:
Tenga en cuenta que la primera variable no tiene nombre
HUNDRED_DOLLARS_ZERO_CENT
, perostartBalance
para indicar cuál es el significado de la variable, pero no que su valor sea de ninguna manera especial.fuente
0.05f
a unint
. :)const
variable.calculateCompoundInterest
? Si es así, la escritura adicional es una prueba de trabajo de que ha leído la documentación de la función que está probando, o al menos copió los nombres que le dio su IDE. No estoy seguro de cuánto le dice esto al lector sobre la intención del código, pero si pasa los parámetros en el orden incorrecto, al menos pueden decir lo que se pretendía.Si usa números arbitrarios solo para ver lo que hacen, entonces lo que realmente está buscando es probablemente datos de prueba generados aleatoriamente o pruebas basadas en propiedades.
Por ejemplo, Hipótesis es una excelente biblioteca de Python para este tipo de pruebas, y se basa en QuickCheck .
La idea es no limitarse a sus propios valores, sino elegir valores aleatorios que se puedan usar para verificar que sus funciones coincidan con sus especificaciones. Como nota importante, estos sistemas generalmente recordarán cualquier entrada que falle y luego se asegurarán de que esas entradas siempre se prueben en el futuro.
El punto 3 puede ser confuso para algunas personas, así que aclaremos. No significa que esté afirmando la respuesta exacta; esto es obviamente imposible de hacer para una entrada arbitraria. En cambio, afirma algo sobre una propiedad del resultado. Por ejemplo, puede afirmar que después de agregar algo a una lista, se vuelve no vacío, o que un árbol de búsqueda binario autoequilibrado está realmente equilibrado (utilizando cualquier criterio que tenga esa estructura de datos en particular).
En general, elegir números arbitrarios usted mismo probablemente sea bastante malo: realmente no agrega un montón de valor y es confuso para cualquier otra persona que lo lea. Generar automáticamente un montón de datos de prueba aleatorios y usarlos efectivamente es bueno. Encontrar una hipótesis o una biblioteca similar a QuickCheck para su idioma de elección es probablemente una mejor manera de lograr sus objetivos sin dejar de ser comprensible para otros.
fuente
foo
está computando) ...? Si estuvieras 100% seguro de que tu código da la respuesta correcta, simplemente pondrías ese código en el programa y no lo probarías. Si no lo está, entonces necesita probar la prueba, y creo que todos ven a dónde va esto.d
días, el cálculo end
días + 1 mes debe ser una tasa de porcentaje mensual conocida más alta), etc.El nombre de la prueba de su unidad debe proporcionar la mayor parte del contexto. No de los valores de las constantes. El nombre / documentación para una prueba debe dar el contexto apropiado y la explicación de los números mágicos presentes en la prueba.
Si eso no es suficiente, un poco de documentación debería poder proporcionarlo (ya sea a través del nombre de la variable o una cadena de documentación). Tenga en cuenta que la función en sí tiene parámetros que, con suerte, tienen nombres significativos. Copiarlos en su prueba para nombrar los argumentos es bastante inútil.
Y por último, si sus pruebas de unidad son lo suficientemente complicadas como para que esto sea difícil / no práctico, probablemente tenga funciones demasiado complicadas y podría considerar por qué es así.
Cuanto más descuidadamente escriba las pruebas, peor será su código real. Si siente la necesidad de nombrar sus valores de prueba para aclarar la prueba, sugiere que su método real necesita mejores nombres y / o documentación. Si encuentra la necesidad de nombrar constantes en las pruebas, investigaría por qué necesita esto: probablemente el problema no sea la prueba en sí sino la implementación
fuente
Esto depende en gran medida de la función que está probando. Conozco muchos casos en los que los números individuales no tienen un significado especial por sí mismos, pero el caso de prueba en su conjunto se construye cuidadosamente y, por lo tanto, tiene un significado específico. Eso es lo que uno debe documentar de alguna manera. Por ejemplo, si
foo
realmente es un métodotestForTriangle
que decide si los tres números pueden ser longitudes válidas de los bordes de un triángulo, sus pruebas podrían verse así:y así. Puede mejorar esto y convertir los comentarios en un parámetro de mensaje
assertEqual
que se mostrará cuando la prueba falle. Luego puede mejorar esto aún más y refactorizarlo en una prueba basada en datos (si su marco de prueba lo admite). Sin embargo, te haces un favor si pones una nota en el código de por qué elegiste estos números y cuál de los diversos comportamientos que estás probando con el caso individual.Por supuesto, para otras funciones, los valores individuales de los parámetros pueden ser más importantes, por
foo
lo que probablemente no sea la mejor idea usar un nombre de función sin sentido como cuando se pregunta cómo manejar el significado de los parámetros.fuente
¿Por qué queremos usar constantes con nombre en lugar de números?
Si escribe varias pruebas unitarias, cada una con un surtido de 3 Números (balance inicial, interés, años), simplemente empaquetaría los valores en la prueba unitaria como variables locales. El alcance más pequeño al que pertenecen.
Si utiliza un lenguaje que permite parámetros con nombre, esto es, por supuesto, superfluo. Allí simplemente empacaría los valores sin procesar en la llamada al método. No puedo imaginar ninguna refactorización haciendo esta declaración más concisa:
O utilice un marco de prueba, que le permitirá definir los casos de prueba en algún formato de matriz o mapa:
fuente
Los números se utilizan para llamar a un método, por lo que seguramente la premisa anterior es incorrecta. Puede que no te importe cuáles son los números, pero eso no viene al caso. Sí, podría inferir para qué se usan los números por parte de la magia de IDE, pero sería mucho mejor si solo diera los nombres de los valores, incluso si solo coinciden con los parámetros.
fuente
assertEqual "Returned value" (makeKindInt 42) (runTest "lvalue_operators")
). En este ejemplo,42
es solo un valor de marcador de posición producido por el código en el script de prueba nombradolvalue_operators
y luego verificado cuando es devuelto por el script. No tiene ningún significado, aparte de que el mismo valor se produce en dos lugares diferentes. ¿Cuál sería un nombre apropiado aquí que real da algún significado útil?Si desea probar una función pura en un conjunto de entradas que no son condiciones límite, entonces casi con certeza desea probarla en un conjunto completo de conjuntos de entradas que no son (y son) condiciones límite. Y para mí eso significa que debería haber una tabla de valores para llamar a la función y un bucle:
Las herramientas como las sugeridas en la respuesta de Dannnno pueden ayudarlo a construir la tabla de valores para probar.
bar
,baz
Yblurf
debe ser sustituida por nombres significativos como se explica en la respuesta de Philipp .(Principio general discutible aquí: los números no siempre son "números mágicos" que necesitan nombres; en cambio, los números podrían ser datos . Si tuviera sentido poner sus números en una matriz, tal vez una matriz de registros, entonces probablemente sean datos Por el contrario, si sospecha que podría tener datos en sus manos, considere ponerlos en una matriz y adquirir más.)
fuente
Las pruebas son diferentes del código de producción y, al menos en las pruebas de unidades escritas en Spock, que son breves y al punto, no tengo ningún problema al usar constantes mágicas.
Si una prueba tiene 5 líneas de largo y sigue el esquema básico dado / cuándo / luego, extraer dichos valores en constantes solo haría que el código sea más largo y difícil de leer. Si la lógica es "Cuando agrego un usuario llamado Smith, veo que el usuario Smith regresó a la lista de usuarios", no tiene sentido extraer "Smith" a una constante.
Por supuesto, esto se aplica si puede hacer coincidir fácilmente los valores utilizados en el bloque "dado" (configuración) con los encontrados en los bloques "cuándo" y "luego". Si su configuración de prueba está separada (en código) del lugar donde se usan los datos, podría ser mejor usar constantes. Pero dado que las pruebas son mejor autónomas, la configuración suele estar cerca del lugar de uso y se aplica el primer caso, lo que significa que las constantes mágicas son bastante aceptables en este caso.
fuente
En primer lugar, aceptemos que la "prueba unitaria" se usa a menudo para cubrir todas las pruebas automatizadas que escribe un programador, y que no tiene sentido debatir cómo debe llamarse cada prueba ...
He trabajado en un sistema en el que el software tomó muchas entradas y creó una "solución" que tenía que cumplir algunas restricciones, al tiempo que optimizaba otros números. No hubo respuestas correctas, por lo que el software solo tuvo que dar una respuesta razonable.
Lo hizo mediante el uso de muchos números aleatorios para obtener un punto de partida, y luego con un "escalador" para mejorar el resultado. Esto se ejecutó muchas veces, eligiendo el mejor resultado. Se puede sembrar un generador de números aleatorios, de modo que siempre dé los mismos números en el mismo orden, por lo tanto, si la prueba establece una semilla, sabemos que el resultado sería el mismo en cada ejecución.
Tuvimos muchas pruebas que hicieron lo anterior, y verificamos que los resultados fueran los mismos, esto nos dijo que no habíamos cambiado lo que esa parte del sistema hizo por error al refactorizar, etc. No nos dijo nada sobre la corrección de lo que hizo esa parte del sistema.
Estas pruebas fueron costosas de mantener, ya que cualquier cambio en el código de optimización rompería las pruebas, pero también encontraron algunos errores en el código mucho más grande que preprocesó los datos y procesó los resultados.
Cuando "burlamos" la base de datos, podría llamar a estas pruebas "pruebas unitarias", pero la "unidad" era bastante grande.
A menudo, cuando trabajas en un sistema sin pruebas, haces algo como lo anterior, para que puedas confirmar que tu refactorización no cambia la salida; ¡espero que se escriban mejores pruebas para el nuevo código!
fuente
Creo que en este caso los números deberían denominarse Números Arbitrarios, en lugar de Números Mágicos, y simplemente comentar la línea como "caso de prueba arbitrario".
Claro, algunos números mágicos también pueden ser arbitrarios, en cuanto a valores únicos de "manejo" (que deberían reemplazarse con constantes con nombre, por supuesto), pero también pueden ser constantes precalculadas como "velocidad aérea de un gorrión europeo sin carga en furlongs por quincena", donde el valor numérico se conecta sin comentarios o contexto útil.
fuente
No me aventuraré a decir un sí / no definitivo, pero aquí hay algunas preguntas que debe hacerse al decidir si está bien o no.
Si los números no significan nada, ¿por qué están allí en primer lugar? ¿Pueden ser reemplazados por otra cosa? ¿Se puede hacer una verificación basada en llamadas a métodos y flujo en lugar de afirmaciones de valor? Considere algo como el
verify()
método de Mockito que verifica si ciertas llamadas al método se realizaron para simular objetos en lugar de afirmar un valor.Si los números hacen significar algo, entonces deben ser asignados a las variables que se denominan de manera apropiada.
Escribir el número
2
queTWO
podría ser útil en ciertos contextos, y no tanto en otros contextos.assertEquals(TWO, half_of(FOUR))
tiene sentido para alguien que lee el código. Está claro de inmediato lo que está probando.assertEquals(numCustomersInBank(BANK_1), TWO)
, entonces esto no hace que mucho sentido. ¿ Por quéBANK_1
contiene dos clientes? ¿Para qué estamos probando?fuente