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

397

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 .

Restuta
fuente
2
¿Cómo te burlas sin tener múltiples afirmaciones? Cada expectativa en el simulacro es una afirmación en sí misma, que incluye cualquier orden de llamadas que imponga.
Christopher Creutzig
15
He visto la filosofía de una afirmación por método abusada en el pasado. Un viejo compañero de trabajo utilizó un mecanismo de herencia funky para hacer esto posible. Condujo a muchas subclases (una por rama) y muchas pruebas que hicieron el mismo proceso de configuración / desmontaje solo para verificar los diferentes resultados. Fue lento, difícil de leer y un grave problema de mantenimiento. Nunca lo convencí para volver a un enfoque más clásico. El libro de Gerard Meszaros habla sobre este tema en detalle.
Travis Parks
3
Creo que, como regla general, debería intentar minimizar el número de afirmaciones por prueba. Sin embargo, mientras la prueba reduzca suficientemente el problema a un lugar específico en el código, entonces es una prueba útil.
ConditionRacer
2
He visto casos en los que se usaron múltiples RowTestafirmaciones 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.)
GalacticCowboy
@GalacticCowboy Puede obtener una funcionalidad similar RowTesty TestCaseutilizar fuentes de datos de prueba . Estoy usando un archivo CSV simple con gran éxito.
julealgon

Respuestas:

236

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?

Jaco Pretorius
fuente
99
Como esta respuesta, es decir, está bien, pero no es, en general, bueno (-:
Murph
55
Ehh? ¿Por qué harías eso? la ejecución del método es exactamente la misma?
jgauffin
198
Una sola afirmación por prueba de unidad es una excelente manera de probar la capacidad del lector para desplazarse hacia arriba y hacia abajo.
Tom
3
¿Algún razonamiento detrás de esto? Como es, esta respuesta actual solo establece lo que debería ser, pero no por qué.
Steven Jeuris
37
Muy en desacuerdo. La respuesta no enumera ninguna ventaja de tener una sola afirmación, y la desventaja obvia es que necesita copiar y pegar las pruebas solo porque una respuesta en Internet lo dice. Si necesita probar múltiples campos de un resultado o múltiples resultados de una sola operación, debe afirmarlos todos en afirmaciones independientes, ya que proporciona mucha más información útil que probarlos en una gran afirmación de blob. En una buena prueba, prueba una sola operación, no un solo resultado de una operación.
Peter
296

Las pruebas deben fallar por una sola razón, pero eso no siempre significa que solo debe haber una Assertdeclaració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

[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 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

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 (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.

Anthony
fuente
32
La clave es que solo tiene una acción, y luego inspecciona los resultados de esa acción utilizando afirmaciones.
Amitābha
1
Creo que también es interesante tener más como una Afirmación, si Arrange es costoso.
Rekshino
1
@Rekshino, si organizar es costoso, podemos compartir el código de organización, por ejemplo, colocando el código de organización en la rutina de inicialización de la prueba.
Shaun Luttin
2
Entonces, si comparo con la respuesta de Jaco, el "único aserto" se convierte en "el único grupo de afirmaciones" que tiene más sentido para mí.
Walfrat
2
Esta es una buena respuesta, pero no estoy de acuerdo con que una sola afirmación no sea mejor. El mal ejemplo de afirmación no es mejor, pero eso no significa que una sola afirmación no sería mejor si se hace correctamente. Muchas bibliotecas permiten afirmaciones / coincidencias personalizadas para que se pueda crear algo si aún no está presente. Por ejemplo, en mi opinión, las Assert.IsBetween(10, 100, value)impresiones Expected 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.
Thor84no
85

Nunca pensé 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í uso múltiples afirmaciones para asegurarme de que las condiciones complejas se puedan convertir en el predicado esperado.

Solo estoy probando una unidad (el ToPredicatemétodo), pero estoy cubriendo todo lo que puedo pensar en la prueba.

Matt Ellen
fuente
46
Múltiples afirmaciones son malas debido a la detección de errores. Si ha fallado su primer Assert.IsTrue, no se ejecutarán otros asertivos y no obtendrá ninguna información de ellos. Por otro lado, si tuviera 5 pruebas en lugar de 1 con 5 afirmaciones, podría obtener algo útil
Sly
66
¿Considera que todavía es malo si todas las afirmaciones prueban el mismo tipo de funcionalidad? Como en el ejemplo anterior, el ejemplo prueba los condicionales y si algo de esto falla, debe solucionarlo. ¿Le importa que pueda perder las 2 últimas afirmaciones si falla una anterior?
encogerse
106
Arreglo mis problemas uno a la vez. Entonces, el hecho de que la prueba pueda fallar más de una vez no me molesta. Si los separo, aparecerán los mismos errores, pero todos a la vez. Me resulta más fácil arreglar las cosas paso a paso. Admito que en este caso, las dos últimas afirmaciones probablemente podrían ser refactorizadas en su propia prueba.
Matt Ellen
19
Su caso es muy representativo, es por eso que NUnit tiene un atributo adicional TestCase - nunit.org/?p=testCase&r=2.5
Restuta
10
El marco de prueba de google C ++ tiene ASSERT () y EXPECT (). ASSERT () se detiene al fallar mientras EXPECT () continúa. Eso es muy útil cuando quieres validar más de una cosa en la prueba.
ratkok
21

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.

@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 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 con assertAllNotificationSent()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).

BlairHippo
fuente
Cuando lo haga, debería usar mejor los marcos de prueba con pruebas dependientes, es mejor (por ejemplo, testng admite esta función)
Kemoda
8
Escribo pruebas como esta también para que pueda estar seguro de lo que está haciendo el código y los cambios de estado, no creo que sea una prueba unitaria, sino una prueba de integración.
mdma
¿Cuáles son sus opiniones sobre la refactorización en un estado de alarma (int numberOfAlarmMessages)?
Borjab
1
Sus afirmaciones serían muy buenos nombres de prueba.
Bjorn
Es mejor dejar que la prueba se ejecute, incluso en entradas no válidas. Le brinda más información de esa manera (y especialmente si todavía pasa cuando no lo esperaba).
CurtainDog
8

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:

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 Innerinstancias de clase.

En lugar de probar ...

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

estoy haciendo

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}
Betlista
fuente
2
Y eso es bueno, no deberías tener ninguna lógica condicional en tus pruebas. Hace que la prueba sea más compleja y menos legible. Y supongo que Roy destacó en su blog que varias afirmaciones sobre un objeto están bien la mayor parte del tiempo. Entonces, los que tienes son solo afirmaciones de guardia y está bien tenerlos.
Restuta
2
Sus Assert.notNullcorreos electrónicos son redundantes, su prueba fallará con un NPE si son nulos.
sara
1
Además, su primer ejemplo (con el if) pasará si resesnull
sara
1
@kai notNull's son redundantes, estoy de acuerdo, pero siento que es más limpio tener la afirmación (y si no soy vago también con el mensaje adecuado) en lugar de excepción ...
Betlista
1
Bueno, una afirmación fallida también arroja una excepción, y en ambos casos obtienes un enlace directo a la fila exacta que lo arrojó con un seguimiento de la pila que lo acompaña, por lo que personalmente prefiero no saturar la prueba con condiciones previas que se comprobarán de todos modos. Prefiero un oneliner à la Assert.assertEquals(..., service.process().getInner());, posible con variables extraídas si la línea se vuelve "demasiado larga"
sara
6

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:

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 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".

Pete
fuente
3

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):

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));
        }
    }
}
INTPnerd
fuente
¿Cómo maneja la entrada de TestCase en este caso?
Simon Gillbee
Entonces, en mi organización actual, tenemos más de 20,000 pruebas unitarias muy similares a lo que muestra. Esto es una pesadilla. Gran parte del código de configuración de prueba se copia / pega, lo que da como resultado una configuración de prueba incorrecta y pruebas no válidas que pasan. Para cada [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.
fourpastmidnight
@fourpastmidnight la mayor parte de lo que dijo parece una crítica válida, pero el punto sobre copiar y pegar el código de configuración y, por lo tanto, estar equivocado, no es un problema de estructura sino de programadores irresponsables (que podría ser el resultado de administradores irresponsables o un entorno negativo más que malos programadores). Si las personas simplemente copian y pegan el código y esperan que sea correcto y no se molesten en comprender el código, en cualquier contexto y por cualquier motivo, deben estar capacitados para no hacerlo o dejarlos ir si no pueden entrenado. Eso va en contra de todo buen principio de programación.
still_dreaming_1
Pero, en general, estoy de acuerdo, esta es una exageración loca que resultará en mucha hinchazón / equipaje / duplicación que conducirá a todo tipo de problemas. Solía ​​estar loco y recomendar cosas como esta. Eso es lo que espero poder decir todos los días sobre mí el día anterior porque eso significa que nunca dejo de encontrar mejores formas de hacer las cosas.
still_dreaming_1
@ still_dreaming_1 wrt "programadores irresponsables": estoy de acuerdo en que este comportamiento es un problema importante. Sin embargo, este tipo de estructura de prueba realmente invita a este tipo de comportamiento, para bien o para mal. Dejando a un lado las malas prácticas de desarrollo, mi principal objeción a este formulario es que realmente mata el rendimiento de las pruebas. No hay nada peor que un conjunto de pruebas de ejecución lenta. Un conjunto de pruebas de ejecución lenta significa que las personas no ejecutarán pruebas localmente e incluso se sienten tentadas a omitirlas en compilaciones intermedias, una vez más, un problema de las personas, pero sucede, lo que se puede evitar asegurando que tenga pruebas de ejecución rápida para comenzar con.
fourpastmidnight
2

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.

Guffa
fuente
1
Si está combinando múltiples condiciones en una sola afirmación, en caso de falla, todo lo que sabe es que falló. Con múltiples afirmaciones, usted conoce específicamente algunas de ellas (las que incluyen la falla). Considere verificar que una matriz devuelta contenga un solo valor: verifique que no sea nulo, que tenga exactamente un elemento y luego el valor de ese elemento. (Dependiendo de la plataforma) solo verificar el valor de inmediato podría dar una anulación de referencia nula (menos útil que el error de aserción nula) y no verifica la longitud de la matriz.
Richard
@ Richard: Obtener un resultado y luego extraer algo de ese resultado sería un proceso en varios pasos, así que lo cubrí en el segundo párrafo de la respuesta.
Guffa
2
Regla general: si tiene varias afirmaciones en una prueba, cada una debe tener un mensaje diferente. Entonces no tienes este problema.
Anthony
Y el uso de un corredor de prueba de calidad como NCrunch le mostrará exactamente en qué línea falló la prueba, tanto en el código de prueba como en el código bajo prueba.
fourpastmidnight
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 descubrir 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. 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.

Aaron Digulla
fuente
2

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,

@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 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.

hagubear
fuente
1

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.

revs jpierson
fuente
1

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.

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.

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.

CortinaPerro
fuente
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 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.

cslotty
fuente
-1

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.

gsf
fuente
-3

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.

Johan Larsson
fuente