Unidad que prueba múltiples condiciones en una declaración IF

26

Tengo un fragmento de código que se parece a esto:

function bool PassesBusinessRules()
{
    bool meetsBusinessRules = false;

    if (PassesBusinessRule1 
         && PassesBusinessRule2
         && PassesBusinessRule3)
    {
         meetsBusinessRules= true;
    }

    return meetsBusinessRules;
}

Creo que debería haber cuatro pruebas unitarias para esta función en particular. Tres para probar cada una de las condiciones en la declaración if y asegurarse de que devuelva falso. Y otra prueba que asegura que la función devuelve verdadero.

Pregunta: ¿Debería haber diez pruebas unitarias en su lugar? Nueve que verifican cada una de las posibles rutas de falla. ES DECIR:

  • Falso Falso Falso
  • Falso Falso Verdadero
  • Falso Verdadero Falso

Y así sucesivamente para cada combinación posible.

Creo que es exagerado, pero algunos de los otros miembros de mi equipo no. La forma en que lo veo es que si BusinessRule1 falla, siempre debe devolver falso, no importa si se verificó primero o último.

bwalk2895
fuente
¿El compilador utiliza una evaluación codiciosa para el operador &&?
suszterpatt
12
Si escribiste 10 pruebas unitarias, estarías probando el operador &&, no tus métodos.
Mert Akcakaya
2
¿No habría solo ocho pruebas si probaras todas las combinaciones posibles? Tres parámetros booleanos activados o desactivados.
Kris Harper
3
@Mert: solo si puede garantizar que && siempre estará allí.
Misko
Hickey: Si lo pasamos escribiendo pruebas, ese es el tiempo que no estamos gastando haciendo otra cosa. Cada uno de nosotros necesita evaluar la mejor manera de pasar nuestro tiempo para maximizar nuestros resultados, tanto en cantidad como en calidad. Si la gente piensa que pasar el cincuenta por ciento de su tiempo escribiendo pruebas maximiza sus resultados, está bien para ellos. Estoy seguro de que eso no es cierto para mí, prefiero pasar ese tiempo pensando en mi problema. Estoy seguro de que, para mí, esto produce mejores soluciones, con menos defectos, que cualquier otro uso de mi tiempo. Un mal diseño con un conjunto de pruebas completo sigue siendo un mal diseño.
Trabajo

Respuestas:

29

Formalmente, esos tipos de cobertura tienen nombres.

Primero, hay cobertura de predicado : desea tener un caso de prueba que haga que la declaración if sea verdadera y una que la haga falsa. Tener esta cobertura cumplida es probablemente un requisito básico para un buen conjunto de pruebas.

Luego está la cobertura de la condición : aquí desea probar que cada subcondición en el if tiene el valor verdadero y falso. Obviamente, esto crea más pruebas, pero generalmente detecta más errores, por lo que a menudo es una buena idea incluir en su conjunto de pruebas si tiene tiempo.

El criterio de cobertura más avanzado generalmente se denomina Cobertura de condición combinatoria : aquí el objetivo es tener un caso de prueba que atraviese todas las combinaciones posibles de valores booleanos en su prueba.

¿Es esto mejor que la cobertura de predicado o condición simple? En términos de cobertura, por supuesto. Pero no es gratis. Tiene un costo muy alto en mantenimiento de prueba. Por esta razón, la mayoría de las personas no se molestan con una cobertura combinatoria completa. Por lo general, probar todas las ramas (o todas las condiciones) será lo suficientemente bueno para atrapar errores. Agregar las pruebas adicionales para las pruebas combinatorias generalmente no detectará más errores, pero requiere mucho esfuerzo para crearlos y mantenerlos. El esfuerzo adicional generalmente hace que esto no valga la pena muy poco, por lo que no recomendaría esto.

Parte de esta decisión debe basarse en cuán riesgoso cree que será el código. Si tiene mucho espacio para fallar, vale la pena probarlo. Si es algo estable y no cambiará mucho, debería considerar enfocar sus esfuerzos de prueba en otra parte.

Oleksi
fuente
2
Si los valores booleanos se pasan de fuentes externas (lo que significa que no siempre se validan), a menudo es necesaria una cobertura condicional combinatoria. Primero haz una tabla de las combinaciones. Luego, para cada entrada, decida si esa entrada representa un caso de uso significativo. De lo contrario, debe haber código en alguna parte (ya sea aserciones de software o cláusula de validación) para evitar que se ejecute esa combinación. Es importante no agrupar todos los parámetros en una sola prueba combinatoria: intente dividir los parámetros en grupos que interactúen entre sí, es decir, comparta la misma expresión booleana.
rwong
¿Qué tan seguro está de los términos en negrita? Su respuesta parece ser la única aparición de "Cobertura de condición combinatoria", y algunos recursos dicen que "cobertura predicada" y "cobertura condicional" son lo mismo.
Stijn
8

En última instancia, depende de usted (equipo), el código y el entorno específico del proyecto. No hay una regla universal. Usted (equipo de r) debe escribir tantas pruebas como sea necesario para sentirse cómodo de que el código sea correcto . Entonces, si sus compañeros de equipo no están convencidos por 4 pruebas, tal vez necesite más.

OTOH el tiempo para escribir pruebas unitarias suele ser un recurso escaso. Así que esforzarse por encontrar la mejor manera de pasar el tiempo limitado que tiene . Por ejemplo, si tiene otro método importante con una cobertura del 0%, puede ser mejor escribir un par de pruebas unitarias para cubrirlo, en lugar de agregar pruebas adicionales para este método. Por supuesto, también depende de cuán frágil sea la implementación de cada uno. La planificación de muchos cambios a este método en particular en el futuro previsible puede justificar una cobertura de prueba de unidad adicional. Por lo tanto, puede estar en una ruta crítica dentro del programa. Todos estos son factores que solo usted (el equipo) puede evaluar.

Personalmente, normalmente estaría contento con las 4 pruebas que usted describe, es decir:

  • verdadero falso falso
  • falso verdadero falso
  • falso falso verdadero
  • verdad verdad verdad

más quizás uno:

  • verdadero verdadero falso

para garantizar que la única forma de obtener un valor de retorno truees satisfacer las 3 reglas comerciales. Pero al final, si sus compañeros de equipo insisten en tener caminos combinatorios cubiertos, puede ser más barato agregar esas pruebas adicionales que continuar la discusión mucho más tiempo :-)

Péter Török
fuente
3

Si desea estar seguro, necesitaría ocho pruebas unitarias utilizando las condiciones representadas por una tabla de verdad de tres variables ( http://teach.valdosta.edu/plmoch/MATH4161/Spring%202004/and_or_if_files/image006.gif ).

Nunca puede estar seguro de que la lógica de negocios siempre estipulará que las verificaciones se realicen en ese orden y desea que la prueba sepa lo menos posible sobre la implementación real.

smp7d
fuente
2
La unidad de pruebas es pruebas de caja blanca.
Péter Török
Bueno, el orden no debería importar, y&& es comunicativo, o al menos debería serlo
Zachary K
2

Sí, debería haber la combinación completa en un mundo ideal.

Al hacer la prueba unitaria, realmente deberías tratar de ignorar cómo funciona el método. Simplemente proporcione las 3 entradas y verifique que la salida sea correcta.

Telastyn
fuente
1
La unidad de pruebas es pruebas de caja blanca. Y no vivimos en un mundo ideal.
Péter Török
@ PéterTörök: no vivimos en un mundo ideal para estar seguros, pero stackexchange no está de acuerdo con usted en el otro punto. Especialmente para TDD, las pruebas se escriben según las especificaciones, no la implementación. Personalmente, tomo 'especificación' para incluir todas las entradas (incluidas las variables miembro) y todas las salidas (incluidos los efectos secundarios).
Telastyn el
1
Es solo un hilo específico en StackOverflow, sobre un caso específico, que no debe generalizarse en exceso. Especialmente porque esta publicación actual obviamente trata sobre probar el código que ya está escrito.
Péter Török
1

El estado es malo. La siguiente función no necesita una prueba unitaria porque no tiene efectos secundarios y se entiende bien lo que hace y lo que no hace. ¿Por qué probarlo? ¿No confías en tu propio cerebro? ¡Las funciones estáticas son geniales!

static function bool Foo(bool a, bool b, bool c)
{
    return a && b && c;
}
Trabajo
fuente
2
No, no confío en mi propio cerebro. Aprendí la manera difícil de verificar siempre lo que hago :-) Así que aún necesitaría pruebas unitarias para asegurarme de que no he escrito mal, por ejemplo, y que nadie va a ir. para romper el código en el futuro. Y más pruebas unitarias para verificar el método de llamada que calcula el estado representado por a, by c. Puede mover la lógica de negocios de la forma que desee, al final aún necesita probarlo en algún lugar.
Péter Török
@ Péter Török, también puede hacer errores tipográficos en sus pruebas y, por lo tanto, terminar con falsos positivos, entonces, ¿dónde se detiene? ¿Escribes pruebas unitarias para tus pruebas unitarias? Tampoco confío en mi cerebro al 100%, pero al final del día escribir código es lo que hago para vivir. Es posible tener un error dentro de esta función, pero es importante escribir el código de tal manera que sea fácil rastrear el error hasta el origen y, de este modo, una vez que haya aislado el problema y haya solucionado el problema, esté mejor . El código bien escrito puede basarse en las pruebas de integración principalmente infoq.com/presentations/Simple-Made-Easy
Job
2
De hecho, las pruebas también pueden ser defectuosas. (TDD aborda esto haciendo que las pruebas fallen primero). Sin embargo, cometer el mismo tipo de error dos veces (y pasarlo por alto) tiene una probabilidad mucho menor. En general, ninguna cantidad y tipo de pruebas pueden probar que el software no tiene errores, solo reduzca la probabilidad de errores a un nivel aceptable. Y en la velocidad de rastreo de errores a la fuente, la OMI nada puede superar las pruebas unitarias - rulez respuesta rápida :-)
Péter Török
"La siguiente función no necesita una prueba unitaria" Creo que estás siendo sarcástico aquí, pero no está claro. ¿Confío en mi propio cerebro? ¡NO! ¿Confío en el cerebro del próximo tipo que toca el código? ¡AÚN MÁS NO! ¿Confío en que todas las suposiciones detrás del código serán ciertas dentro de un año? ... me entiendes. Además, las funciones estáticas matan a OO ... si quieres hacer FP, entonces usa un lenguaje FP.
Rob
1

Sé que esta pregunta es bastante antigua. Pero quiero dar otra perspectiva al problema.

Primero, sus pruebas unitarias deben tener dos propósitos:

  1. Cree documentación para usted y sus compañeros de equipo, de modo que después de un período de tiempo determinado pueda leer la prueba de la unidad y asegurarse de comprender what's the class' intentionyhow the class is doing its work
  2. Durante el desarrollo, la prueba unitaria se asegura de que el código que estamos escribiendo esté funcionando como estaba previsto en nuestra mente.

Entonces, recapitulando el problema, queremos probar a complex if statement, para el ejemplo dado, hay 2 ^ 3 posibilidades, que es una cantidad importante de pruebas que podemos escribir.

  • Puede adaptarse a este hecho y escribir 8 pruebas o hacer uso de pruebas parametrizadas
  • También puede seguir las otras respuestas y recordar que las pruebas deben ser claras con la intención, de esta manera no vamos a meternos con demasiados detalles que en el futuro cercano pueden dificultar la comprensión. what is doing the code

Por otro lado, si está en la posición de que sus pruebas son aún más complejas que la implementación, es porque la implementación debe ser rediseñada (más o menos dependiendo del caso) en lugar de la prueba en sí.

Para las declaraciones if complejas, por ejemplo, podría pensar en el patrón de responsabilidad de la cadena , implementando cada controlador de esta manera:

If some simple business rule apply, derive to the next handler

¿Qué tan simple sería probar varias reglas simples, en lugar de una regla compleja?

Espero eso ayude,

cnexans
fuente
0

Este es uno de esos casos en los que algo como Quickcheck ( http://en.wikipedia.org/wiki/QuickCheck ) será tu amigo. En lugar de escribir todos los N casos a mano, haga que la computadora genere todos (o al menos un gran número) de posibles casos de prueba y valide que todos devuelvan un resultado razonable.

Programamos computadoras para vivir aquí, ¿por qué no programar la computadora para generar sus casos de prueba para usted?

Zachary K
fuente
0

Puede refactorizar las condiciones en condiciones de guardia:

if (! PassesBusinessRule1) {
    return false;
}

if (! PassesBusinessRule2) {
    return false;
}

if (! PassesBusinessRule3) {
    return false;
}

No creo que eso reduzca el número de casos, pero mi experiencia es que es más fácil separarlos de esta manera.

(Tenga en cuenta que soy un gran fanático del "punto de salida único", pero hago una excepción para las condiciones de guardia. Pero hay otras formas de estructurar el código para que no tenga devoluciones separadas).

Robar
fuente