En el comentario a esta gran publicación , Roy Osherove mencionó el proyecto OAPT que está diseñado para ejecutar cada afirmación en una sola prueba.
Lo siguiente está escrito en la página de inicio del proyecto:
Las pruebas unitarias adecuadas deberían fallar exactamente por una razón, es por eso que debería usar una afirmación por prueba unitaria.
Y, también, Roy escribió en comentarios:
Mi pauta generalmente es que pruebe un CONCEPTO lógico por prueba. puede tener múltiples afirmaciones en el mismo objeto . generalmente serán el mismo concepto que se está probando.
Creo que hay algunos casos en los que se necesitan múltiples afirmaciones (por ejemplo , la Afirmación de la Guardia ), pero en general trato de evitar esto. ¿Cuál es tu opinión? Proporcione un ejemplo del mundo real donde realmente se necesitan múltiples afirmaciones .
fuente
RowTest
afirmaciones en lugar de (MbUnit) /TestCase
(NUnit) para probar una variedad de comportamientos de casos extremos. ¡Use las herramientas adecuadas para el trabajo! (Desafortunadamente, MSTest no parece tener una capacidad de prueba de fila todavía.)RowTest
yTestCase
utilizar fuentes de datos de prueba . Estoy usando un archivo CSV simple con gran éxito.Respuestas:
No creo que sea necesariamente algo malo , pero sí creo que deberíamos esforzarnos por tener solo afirmaciones únicas en nuestras pruebas. Esto significa que usted escribe muchas más pruebas y nuestras pruebas terminarían probando solo una cosa a la vez.
Dicho esto, diría que quizás la mitad de mis pruebas en realidad solo tienen una afirmación. Creo que solo se convierte en un olor a código (¿prueba?) Cuando tienes alrededor de cinco o más afirmaciones en tu prueba.
¿Cómo resuelves múltiples afirmaciones?
fuente
Las pruebas deben fallar por una sola razón, pero eso no siempre significa que solo debe haber una
Assert
declaración. En mi humilde opinión, es más importante mantener el patrón " Organizar, Actuar, Afirmar ".La clave es que solo tiene una acción, y luego inspecciona los resultados de esa acción utilizando afirmaciones. Pero es "Organizar, Actuar, Afirmar, Fin de la prueba ". Si tiene la tentación de continuar con las pruebas realizando otra acción y más afirmaciones después, realice una prueba por separado.
Estoy feliz de ver múltiples declaraciones de afirmación que forman parte de probar la misma acción. p.ej
o
Usted podría combinar estos en un afirman, pero eso es una cosa diferente a insistir en que se debe o necesidad . No hay mejora al combinarlos.
por ejemplo, el primero podría ser
Pero esto no es mejor: el mensaje de error es menos específico y no tiene otras ventajas. Estoy seguro de que puede pensar en otros ejemplos en los que la combinación de dos o tres (o más) afirmaciones en una gran condición booleana hace que sea más difícil de leer, más difícil de alterar y más difícil averiguar por qué falló. ¿Por qué hacer esto solo por el bien de una regla?
NB : El código que estoy escribiendo aquí es C # con NUnit, pero los principios se mantendrán con otros lenguajes y marcos. La sintaxis también puede ser muy similar.
fuente
Assert.IsBetween(10, 100, value)
impresionesExpected 8 to be between 10 and 100
son mejores que dos afirmaciones separadas. Ciertamente puede argumentar que no es necesario, pero generalmente vale la pena considerar si es fácil reducirlo a una sola afirmación antes de hacer un conjunto completo de ellos.Nunca pensé que más de una afirmación fuera algo malo.
Lo hago todo el tiempo:
Aquí uso múltiples afirmaciones para asegurarme de que las condiciones complejas se puedan convertir en el predicado esperado.
Solo estoy probando una unidad (el
ToPredicate
método), pero estoy cubriendo todo lo que puedo pensar en la prueba.fuente
Cuando uso pruebas unitarias para validar comportamientos de alto nivel, pongo múltiples afirmaciones en una sola prueba. Aquí hay una prueba que estoy usando para algún código de notificación de emergencia. El código que se ejecuta antes de la prueba pone el sistema en un estado en el que si se ejecuta el procesador principal, se envía una alarma.
Representa las condiciones que deben existir en cada paso del proceso para que tenga la certeza de que el código se comporta de la manera que espero. Si falla una sola afirmación, no me importa que las restantes ni siquiera se ejecuten; debido a que el estado del sistema ya no es válido, esas afirmaciones posteriores no me dirían nada valioso. * Si
assertAllUnitsAlerting()
falla, entonces no sabría qué hacer conassertAllNotificationSent()
el éxito O el fracaso hasta que determine qué causó el error anterior y lo corrigió(* - Bien, posiblemente podrían ser útiles para depurar el problema. Pero la información más importante, que la prueba falló, ya se ha recibido).
fuente
Otra razón por la que creo que múltiples afirmaciones en un método no es algo malo se describe en el siguiente código:
En mi prueba, simplemente quiero probar que
service.process()
devuelve el número correcto enInner
instancias de clase.En lugar de probar ...
estoy haciendo
fuente
Assert.notNull
correos electrónicos son redundantes, su prueba fallará con un NPE si son nulos.if
) pasará sires
esnull
Assert.assertEquals(..., service.process().getInner());
, posible con variables extraídas si la línea se vuelve "demasiado larga"Creo que hay muchos casos en los que escribir afirmaciones múltiples es válido dentro de la regla de que una prueba solo debe fallar por una razón.
Por ejemplo, imagine una función que analiza una cadena de fecha:
Si la prueba falla, es por una razón, el análisis es incorrecto. Si usted argumenta que esta prueba puede fallar por tres razones diferentes, en mi humilde opinión sería demasiado fino en su definición de "una razón".
fuente
No conozco ninguna situación en la que sería una buena idea tener múltiples afirmaciones dentro del método [Prueba] en sí. La razón principal por la que a las personas les gusta tener múltiples aserciones es porque están tratando de tener una clase [TestFixture] para cada clase que se está probando. En cambio, puede dividir sus pruebas en más clases [TestFixture]. Esto le permite ver varias formas en las que el código puede no haber reaccionado de la manera que esperaba, en lugar de solo aquella en la que falló la primera aserción. La forma de lograr esto es que tiene al menos un directorio por clase siendo probado con muchas clases [TestFixture] dentro. Cada clase [TestFixture] recibiría el nombre del estado específico de un objeto que se probará. El método [SetUp] llevará el objeto al estado descrito por el nombre de la clase. Luego tiene varios métodos [Test], cada uno afirmando cosas diferentes que esperaría que fueran verdaderas, dado el estado actual del objeto. Cada método [Test] lleva el nombre de lo que está afirmando, excepto que tal vez podría llamarse después del concepto en lugar de solo una lectura en inglés del código. Luego, la implementación de cada método [Prueba] solo necesita una sola línea de código donde se afirma algo. Otra ventaja de este enfoque es que hace que las pruebas sean muy legibles ya que queda bastante claro lo que está probando y lo que espera con solo mirar los nombres de clase y método. Esto también se escalará mejor a medida que comience a darse cuenta de todos los pequeños casos límite que desea probar y a medida que encuentre errores. excepto que tal vez tenga el nombre del concepto en lugar de solo una lectura en inglés del código. Luego, la implementación de cada método [Prueba] solo necesita una sola línea de código donde se afirma algo. Otra ventaja de este enfoque es que hace que las pruebas sean muy legibles ya que queda bastante claro lo que está probando y lo que espera con solo mirar los nombres de clase y método. Esto también se escalará mejor a medida que comience a darse cuenta de todos los pequeños casos límite que desea probar y a medida que encuentre errores. excepto que tal vez tenga el nombre del concepto en lugar de solo una lectura en inglés del código. Luego, la implementación de cada método [Prueba] solo necesita una sola línea de código donde se afirma algo. Otra ventaja de este enfoque es que hace que las pruebas sean muy legibles, ya que queda bastante claro lo que está probando y lo que espera con solo mirar los nombres de clase y método. Esto también se escalará mejor a medida que comience a darse cuenta de todos los pequeños casos límite que desea probar y a medida que encuentre errores. y lo que espera con solo mirar los nombres de clase y método. Esto también se escalará mejor a medida que comience a darse cuenta de todos los pequeños casos límite que desea probar y a medida que encuentre errores. y lo que espera con solo mirar los nombres de clase y método. Esto también se escalará mejor a medida que comience a darse cuenta de todos los pequeños casos límite que desea probar y a medida que encuentre errores.
Por lo general, esto significa que la línea final de código dentro del método [SetUp] debe almacenar un valor de propiedad o un valor de retorno en una variable de instancia privada de [TestFixture]. Entonces puede afirmar varias cosas diferentes sobre esta variable de instancia a partir de diferentes métodos [Prueba]. También puede hacer afirmaciones sobre qué diferentes propiedades del objeto bajo prueba están configuradas ahora que está en el estado deseado.
A veces, es necesario hacer afirmaciones en el camino a medida que obtiene el objeto bajo prueba en el estado deseado para asegurarse de que no se equivocó antes de colocar el objeto en el estado deseado. En ese caso, esas aserciones adicionales deberían aparecer dentro del método [Configuración]. Si algo sale mal dentro del método [Configuración], quedará claro que algo estaba mal con la prueba antes de que el objeto llegara al estado deseado que pretendía probar.
Otro problema con el que se puede encontrar es que puede estar probando una excepción que esperaba que se lanzara. Esto puede tentarlo a no seguir el modelo anterior. Sin embargo, aún se puede lograr capturando la excepción dentro del método [SetUp] y almacenándola en una variable de instancia. Esto le permitirá afirmar diferentes cosas sobre la excepción, cada una en su propio método [Test]. Luego, también puede hacer valer otras cosas sobre el objeto bajo prueba para asegurarse de que no se produjeron efectos secundarios no deseados por la excepción.
Ejemplo (esto se dividiría en varios archivos):
fuente
[Test]
método, esa clase se vuelve a instanciar y el[SetUp]
método se ejecuta nuevamente. Esto mata al recolector de basura .NET y hace que las pruebas se ejecuten extremadamente lentamente: más de 5 minutos localmente, más de 20 minutos en el servidor de compilación. Las pruebas de 20K deben ejecutarse en aproximadamente 2 a 3 minutos. Yo no recomendaría este estilo prueba en absoluto, sobre todo para un gran conjunto de pruebas-ish.Tener múltiples aserciones en la misma prueba es solo un problema cuando la prueba falla. Entonces es posible que deba depurar la prueba o analizar la excepción para averiguar qué afirmación es la que falla. Con una afirmación en cada prueba, generalmente es más fácil determinar qué está mal.
No puedo pensar en un escenario en el que realmente se necesiten múltiples aserciones , ya que siempre puedes reescribirlas como condiciones múltiples en la misma aserción. Sin embargo, puede ser preferible si, por ejemplo, tiene varios pasos para verificar los datos intermedios entre los pasos en lugar de arriesgarse a que los pasos posteriores se bloqueen debido a una entrada incorrecta.
fuente
Si su prueba falla, no sabrá si las siguientes afirmaciones también se romperán. A menudo, eso significa que se perderá información valiosa para descubrir la fuente del problema. Mi solución es usar una afirmación pero con varios valores:
Eso me permite ver todas las afirmaciones fallidas a la vez. Utilizo varias líneas porque la mayoría de los IDE mostrarán diferencias de cadena en un diálogo de comparación de lado a lado.
fuente
Si tiene múltiples afirmaciones en una sola función de prueba, espero que sean directamente relevantes para la prueba que está realizando. Por ejemplo,
Tener muchas pruebas (incluso cuando sientas que probablemente sea una exageración) no es algo malo. Puede argumentar que tener las pruebas vitales y más esenciales es más importante. Por lo tanto, cuando esté afirmando, asegúrese de que sus declaraciones de afirmación estén colocadas correctamente en lugar de preocuparse demasiado por las afirmaciones múltiples. Si necesita más de uno, use más de uno.
fuente
El objetivo de la prueba unitaria es brindarle la mayor cantidad de información posible sobre lo que está fallando, pero también ayudar a identificar con precisión los problemas más fundamentales primero. Cuando sabe lógicamente que una aserción fallará dado que otra aserción falla o, en otras palabras, existe una relación de dependencia entre la prueba, entonces tiene sentido rodarlas como afirmaciones múltiples dentro de una sola prueba. Esto tiene el beneficio de no ensuciar los resultados de la prueba con fallas obvias que podrían haberse eliminado si rescatamos la primera afirmación dentro de una sola prueba. En el caso de que esta relación no exista, la preferencia sería, naturalmente, separar estas afirmaciones en pruebas individuales porque, de lo contrario, encontrar estas fallas requeriría múltiples iteraciones de pruebas para resolver todos los problemas.
Si luego diseña también las unidades / clases de tal manera que tendrían que escribirse pruebas demasiado complejas, esto genera menos carga durante las pruebas y probablemente promueva un mejor diseño.
fuente
Sí, está bien tener múltiples afirmaciones siempre que una prueba fallida le brinde suficiente información para poder diagnosticar la falla. Esto dependerá de lo que esté probando y cuáles sean los modos de falla.
Nunca he encontrado que tales formulaciones sean útiles (que una clase tenga una razón para cambiar es un ejemplo de un adagio tan inútil). Considere una afirmación de que dos cadenas son iguales, esto es semánticamente equivalente a afirmar que la longitud de las dos cadenas es la misma y que cada carácter en el índice correspondiente es igual.
Podríamos generalizar y decir que cualquier sistema de aserciones múltiples podría reescribirse como una sola aserción, y cualquier aserción individual podría descomponerse en un conjunto de aserciones más pequeñas.
Entonces, solo enfóquese en la claridad del código y la claridad de los resultados de la prueba, y deje que eso guíe la cantidad de afirmaciones que usa en lugar de viceversa.
fuente
La respuesta es muy simple: si prueba una función que cambia más de un atributo, del mismo objeto, o incluso dos objetos diferentes, y la corrección de la función depende de los resultados de todos esos cambios, entonces desea afirmar ¡Que cada uno de esos cambios se ha realizado correctamente!
Tengo la idea de un concepto lógico, pero la conclusión inversa diría que ninguna función debe cambiar más de un objeto. Pero eso es imposible de implementar en todos los casos, en mi experiencia.
Tome el concepto lógico de una transacción bancaria: retirar un monto de una cuenta bancaria en la mayoría de los casos DEBE incluir agregar ese monto a otra cuenta. NUNCA quieres separar esas dos cosas, forman una unidad atómica. Es posible que desee realizar dos funciones (retirar / agregar dinero) y, por lo tanto, escribir dos pruebas unitarias diferentes, además. Pero esas dos acciones deben llevarse a cabo dentro de una transacción y también debe asegurarse de que la transacción funcione. En ese caso, simplemente no es suficiente para asegurarse de que los pasos individuales fueron exitosos. Tienes que verificar ambas cuentas bancarias, en tu prueba.
Puede haber ejemplos más complejos que no probaría en una prueba unitaria, en primer lugar, sino en una prueba de integración o aceptación. Pero esos límites son fluidos, en mi humilde opinión! No es tan fácil decidir, es una cuestión de circunstancias y quizás de preferencia personal. Retirar dinero de uno y agregarlo a otra cuenta sigue siendo una función muy simple y definitivamente un candidato para pruebas unitarias.
fuente
Esta pregunta está relacionada con el clásico problema de equilibrio entre los problemas de spaghetti y lasaña.
Tener múltiples afirmaciones podría fácilmente meterse en el problema de los espaguetis en los que no tienes idea de qué se trata la prueba, pero tener una sola afirmación por prueba podría hacer que tu prueba sea igualmente ilegible al tener múltiples pruebas en una gran lasaña, lo que hace imposible encontrar qué prueba hace .
Hay algunas excepciones, pero en este caso mantener el péndulo en el medio es la respuesta.
fuente
Ni siquiera estoy de acuerdo con el "fracaso por una sola razón" en general. Lo que es más importante es que las pruebas son cortas y se lee claramente en la OMI.
Sin embargo, esto no siempre se puede lograr y cuando una prueba es complicada, un nombre descriptivo (largo) y probar menos cosas tiene más sentido.
fuente