¿Está bien tener múltiples afirmaciones en una prueba de una sola unidad?

337

En el comentario a esta gran publicación , Roy Osherove mencionó el proyecto OAPT que está diseñado para ejecutar cada aseveración en una sola prueba.

Lo siguiente está escrito en la página de inicio del proyecto:

  

Las pruebas unitarias apropiadas deberían fallar   exactamente una razón, es por eso que   debe estar utilizando una aserción por unidad   prueba.

Y, también, Roy escribió en los comentarios:

  

Mi guía es generalmente que tu pruebas   Un CONCEPTO lógico por prueba. usted puede   tener múltiples afirmaciones en el mismo    objeto . normalmente serán el mismo concepto que se está probando.

Creo que hay algunos casos en los que se necesitan múltiples aserciones (por ejemplo, Afirmación de guardia ), pero en general trata de evitar esto. ¿Cuál es tu opinión? Proporcione un ejemplo de palabra real en el que realmente se necesitan varias afirmaciones necesarias .

    
pregunta Restuta 02.06.2016 - 11:12

15 respuestas

207

No creo que sea necesariamente algo malo , pero sí creo que deberíamos esforzarnos por tener solo declaraciones individuales en nuestras pruebas. Esto significa que usted escribe muchas más pruebas y nuestras pruebas terminarían probando solo una cosa a la vez.

Habiendo dicho eso, diría que quizás la mitad de mis pruebas en realidad solo tienen una afirmación. Creo que solo se convierte en un código (¿prueba?) Olor cuando tienes alrededor de cinco o más afirmaciones en tu prueba.

¿Cómo resuelves varias afirmaciones?

    
respondido por el Jaco Pretorius 12.05.2015 - 15:03
239

Las pruebas deben fallar solo por una razón, pero eso no siempre significa que solo debe haber una declaración Assert . 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 mediante afirmaciones. Pero es "Organizar, Actuar, Afirmar, Fin de la prueba ". Si está tentado de continuar con las pruebas realizando otra acción y más afirmaciones después, en su lugar, haga una prueba por separado.

Me complace ver varias declaraciones de afirmación que forman parte de las pruebas de la misma acción. por ejemplo

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

o

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

Usted podría combinarlos en una afirmación, pero eso es diferente de insistir en que debería o debe . No hay ninguna mejora al combinarlos.

por ejemplo El primero podría ser

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

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 afirmaciones (o más) 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.

    
respondido por el Anthony 12.05.2015 - 15:04
83

Nunca he pensado que más de una afirmación fuera algo malo.

Lo hago todo el tiempo:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

Aquí utilizo varias afirmaciones para garantizar que las condiciones complejas puedan convertirse en el predicado esperado.

Sólo estoy probando una unidad (el método ToPredicate ), pero estoy cubriendo todo lo que puedo pensar en la prueba.

    
respondido por el Matt Ellen 23.03.2014 - 08:20
18

Cuando estoy usando la prueba unitaria para validar el comportamiento de alto nivel, absolutamente pongo múltiples aserciones en una sola prueba. Aquí hay una prueba que estoy usando para un código de notificación de emergencia. El código que se ejecuta antes de la prueba coloca al sistema en un estado donde, si se ejecuta el procesador principal, se envía una alarma.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

Representa las condiciones que deben existir en cada paso del proceso para que pueda estar seguro de que el código se está comportando de la manera que espero. Si una sola afirmación falla, no me importa que las restantes ni siquiera se ejecuten; porque el estado del sistema ya no es válido, esas aseveraciones posteriores no me dirían nada valioso. * Si assertAllUnitsAlerting() falló, entonces no sabría qué hacer con el éxito O fracaso de assertAllNotificationSent() hasta que determinara lo que estaba causando el error anterior y lo corrigió.

(* - Bien, podrían ser útiles para depurar el problema. Pero la información más importante, que la prueba falló, ya se ha recibido.)

    
respondido por el BlairHippo 12.05.2015 - 15:01
8

Otra razón por la que creo que las afirmaciones múltiples en un método no es algo malo se describe en el siguiente código:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

En mi prueba, simplemente quiero probar que service.process() devuelve el número correcto en las instancias de clase Inner .

En lugar de probar ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

Lo estoy haciendo

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}
    
respondido por el Betlista 30.10.2012 - 15:21
6

Creo que hay muchos casos en los que la escritura de afirmaciones múltiples es válida 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:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

Si la prueba falla, es por una razón, el análisis es incorrecto. Si usted argumentara que esta prueba puede fallar por tres razones diferentes, en mi opinión sería demasiado fino en su definición de "una razón".

    
respondido por el Pete 02.06.2016 - 13:00
2

Tener varias aserciones en la misma prueba es solo un problema cuando la prueba falla. Entonces es posible que tenga que depurar la prueba o analizar la excepción para averiguar qué aseveración es la que falla. Con una aserción en cada prueba, generalmente es más fácil identificar lo que está mal.

No puedo pensar en un escenario en el que realmente se necesiten varias aserciones necesarias , ya que siempre se pueden volver a escribir como múltiples condiciones 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.

    
respondido por el Guffa 28.09.2010 - 16:05
2

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 averiguar la fuente del problema. Mi solución es usar una afirmación pero con varios valores:

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

Eso me permite ver todas las afirmaciones fallidas a la vez. Uso varias líneas porque la mayoría de los IDE mostrarán las diferencias de cadena en un cuadro de diálogo de comparación lado a lado.

    
respondido por el Aaron Digulla 28.09.2010 - 17:22
2

Si tiene varias afirmaciones en una sola función de prueba, espero que sean directamente relevantes para la prueba que está realizando. Por ejemplo,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

Tener muchas pruebas (incluso cuando sientes que es probablemente una exageración) no es algo malo. Puedes argumentar que tener las pruebas vitales y más esenciales es más importante. Entonces, cuando esté afirmando, asegúrese de que sus declaraciones de afirmación estén colocadas correctamente en lugar de preocuparse por las afirmaciones múltiples. Si necesita más de uno, use más de uno.

    
respondido por el hagubear 12.05.2015 - 15:01
2

No conozco ninguna situación en la que sería una buena idea tener múltiples aserciones dentro del método [Test]. La razón principal por la que a las personas les gusta tener múltiples aserciones es que están tratando de tener una clase [TestFixture] para cada clase que se está probando. En su lugar, puede dividir sus pruebas en más clases [TestFixture]. Esto le permite ver múltiples formas en las que el código puede no haber reaccionado de la manera que esperaba, en lugar de la única en la que falló la primera afirmación. La forma de lograrlo es que al menos un directorio por clase se está probando con muchas clases de [TestFixture] dentro. Cada clase [TestFixture] se nombraría después del estado específico de un objeto que usted probará. El método [Configuración] llevará el objeto al estado descrito por el nombre de la clase. Luego, tiene múltiples métodos [de prueba], cada uno de los cuales afirma diferentes cosas que espera que sean ciertas, dado el estado actual del objeto. Cada método [Test] lleva el nombre de lo que está afirmando, excepto que quizás podría tener el nombre del concepto en lugar de solo una lectura en inglés del código. Entonces, cada implementación del método [Test] solo necesita una sola línea de código donde está afirmando algo. Otra ventaja de este enfoque es que hace que las pruebas sean muy legibles, ya que queda bastante claro lo que estás probando y lo que esperas con solo mirar los nombres de las clases y los métodos. Esto también se escalará mejor a medida que comience a darse cuenta de todos los pequeños casos de borde que desea probar y cuando 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 valor de retorno en una variable de instancia privada de [TestFixture]. Entonces puede afirmar varias cosas diferentes sobre esta variable de instancia desde diferentes métodos [Test]. También puede hacer afirmaciones sobre qué propiedades diferentes del objeto bajo prueba se configuran ahora que está en el estado deseado.

A veces necesita hacer afirmaciones en el camino mientras obtiene el objeto bajo prueba en el estado deseado para asegurarse de que no cometió ningún error antes de poner el objeto en el estado deseado. En ese caso, esas afirmaciones adicionales deberían aparecer dentro del método [Configuración]. Si algo sale mal dentro del método [Configuración], quedará claro que hubo un error en la prueba antes de que el objeto llegara al estado deseado que tenía la intención de probar.

Otro problema con el que puede encontrarse es que puede estar probando una excepción que esperaba que se lanzara. Esto puede tentarte a no seguir el modelo anterior. Sin embargo, aún se puede lograr capturando la excepción dentro del método [Configuración] y almacenándola en una variable de instancia. Esto le permitirá hacer valer cosas diferentes sobre la excepción, cada uno en su propio método [Test]. También puede afirmar otras cosas sobre el objeto bajo prueba para asegurarse de que no haya efectos secundarios no deseados de la excepción lanzada.

Ejemplo (se dividiría en varios archivos):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}
    
respondido por el INTPnerd 12.05.2015 - 15:06
1

El objetivo de la prueba de la unidad es brindarle la mayor información posible acerca de lo que está fallando, pero también ayudar a identificar con precisión los problemas más fundamentales primero. Cuando se sabe lógicamente que una afirmación fallará dado que otra aseveració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 la ventaja 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 no exista esta relación, la preferencia sería naturalmente luego separar estas aserciones en pruebas individuales, ya que de lo contrario, encontrar estas fallas requeriría múltiples iteraciones de ejecuciones de prueba para resolver todos los problemas.

Si luego también diseñas las unidades / clases de tal manera que sea necesario escribir pruebas demasiado complejas, será menos difícil durante las pruebas y probablemente promueva un mejor diseño.

    
respondido por el jpierson 15.03.2013 - 23:00
0

Sí, está bien tener múltiples aserciones siempre y cuando una prueba que falla le proporcione suficiente información para poder diagnosticar la falla. Esto dependerá de lo que esté probando y de cuáles sean los modos de falla.

  

Las pruebas unitarias correctas deben fallar por exactamente una razón, por eso debería usar una prueba de afirmación por unidad.

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 aserción única, y cualquier aseveración única podría descomponerse en un conjunto de aserciones más pequeñas.

Entonces, solo concéntrese en la claridad del código y en la claridad de los resultados de las pruebas, y deje que eso guíe el número de aserciones que usa en lugar de al revés.

    
respondido por el CurtainDog 19.04.2018 - 06:48
0

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 ¡Quiero 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 TIENE QUE 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 realizarse 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. Debe verificar ambas cuentas bancarias, en su 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, IMHO! No es tan fácil de decidir, es una cuestión de circunstancias y quizás de preferencia personal. Retirar dinero de una y agregarlo a otra cuenta sigue siendo una función muy simple y definitivamente un candidato para la prueba de unidad.

    
respondido por el cslotty 12.12.2018 - 16:45
-1

Esta pregunta está relacionada con el problema clásico de equilibrio entre los problemas de los códigos de espagueti y lasaña.

Tener múltiples aserciones podría fácilmente entrar en el problema de los espaguetis en los que no se tiene una idea de lo que se trata la prueba, pero tener una aseveración única por prueba podría hacer que su prueba sea igualmente ilegible al tener múltiples pruebas en una gran lasaña. hace lo imposible.

Hay algunas excepciones, pero en este caso, mantener el péndulo en el medio es la respuesta.

    
respondido por el gsf 11.06.2016 - 00:22
-3

Ni siquiera estoy de acuerdo con el "fallo por una sola razón" en general. Lo que es más importante es que las pruebas son cortas y de lectura clara.

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.

    
respondido por el Johan Larsson 13.04.2014 - 00:10

Lea otras preguntas en las etiquetas