División de pruebas de unidades por requisito o método

16

Primero, disculpas por el título, ¡no podría pensar en la forma más fácil de explicarlo!

Tengo un método para el que quiero escribir pruebas unitarias. Lo mantendré bastante genérico, ya que no quiero discutir la implementación del método, solo probarlo. El metodo es:

public void HandleItem(item a)
{         
     CreateNewItem();
     UpdateStatusOnPreviousItem();
     SetNextRunDate();
}

Entonces, esta clase tiene un método público que luego llama a algunos métodos privados para realizar la lógica.

Entonces, al escribir la prueba de la unidad, quiero verificar que se hayan hecho las tres cosas. Como todos se llaman en la misma ejecución, pensé que podría hacerlo como una prueba:

public void GivenItem_WhenRun_Thenxxxxx
{
     HandleItem(item);
     // Assert item has been created
     // Assert status has been set on the previous item
     // Assert run date has been set
}

Pero pensé que también podría escribirlo como tres pruebas separadas:

public void GivenItem_WhenRun_ThenItemIsCreated()
{
    HandleItem(item);
}

public void GivenItem_WhenRun_ThenStatusIsUpdatedOnPreviousItem()
{
   HandleItem(item);
}

public void GivenItem_WhenRun_ThenRunDateIsSet()
{
     HandleItem(item);
}

Entonces, para mí, esto parece más agradable, ya que esencialmente enumera los requisitos, pero los tres están relacionados y requieren exactamente el mismo trabajo realizado en el método probado, por lo que estoy ejecutando el mismo código 3 veces.

¿Hay un enfoque recomendado para tomar con esto?

Gracias

ADringer
fuente

Respuestas:

29

Hay una sutil diferencia entre ambos enfoques. En el primer caso, cuando el primero Assertfalla, los otros dos ya no se ejecutan. En el segundo caso, las tres pruebas siempre se ejecutan, incluso si una falla. Dependiendo de la naturaleza de la funcionalidad probada, esto podría ajustarse o no a su caso:

  • si tiene sentido ejecutar las tres afirmaciones independientemente de la otra, porque cuando una falla, las otras dos aún pueden no fallar, entonces el segundo enfoque tiene la ventaja de obtener los resultados completos de las 3 pruebas en una sola ejecución. Esto puede ser beneficioso si tiene tiempos de compilación notables, ya que le da la oportunidad de corregir hasta 3 errores a la vez antes de realizar la próxima compilación.

  • sin embargo, si una falla en la primera prueba siempre implicará que las otras dos pruebas también fallarán, entonces probablemente sea mejor usar el primer enfoque (ya que no tiene mucho sentido ejecutar una prueba si ya sabe de antemano que lo hará) fallar).

Doc Brown
fuente
2
+1, buen punto. No se me ocurrió que los tiempos de construcción también pueden ser un cuello de botella.
Kilian Foth el
1
@KilianFoth: No estás trabajando en C ++ con la frecuencia suficiente :(
Matthieu M.
1
@MatthieuM .: para ser justos, la pregunta está etiquetada "C #"
Doc Brown
10

Respuesta corta: es mucho más importante que sus pruebas cubran toda la funcionalidad que cómo lo hacen.

Respuesta más larga: si aún desea elegir entre estas soluciones en gran medida equivalentes, puede utilizar criterios auxiliares para lo que es mejor. Por ejemplo,

  • legibilidad: si el método hace muchas cosas que no están estrechamente relacionadas, una prueba combinada puede ser difícil de entender. Sin embargo, el método en sí también puede ser difícil de entender, ¡así que tal vez debería refactorizar el método en lugar de la prueba!)
  • eficiencia: si la ejecución del método lleva mucho tiempo, esa podría ser una razón débil para combinar las tres comprobaciones para ahorrar tiempo
  • eficiencia2: si la ejecución del código de configuración de su marco lleva mucho tiempo, eso también podría ser una razón débil para evitar múltiples métodos de prueba. (Sin embargo, si esto es realmente un problema, probablemente deba arreglar o cambiar la configuración de su prueba; las pruebas de regresión pierden gran parte de su valor si no puede ejecutarlas a la velocidad del rayo).
Kilian Foth
fuente
2

Use una llamada de método con múltiples afirmaciones. Este es el por qué:

Cuando prueba HandleItem (a), está probando que el método ha llevado el elemento al estado correcto. En lugar de "una afirmación por prueba", piense en "un concepto lógico por prueba".

Pregunta: Si un CreateNewItem falla, pero los otros dos métodos tienen éxito, ¿significa esto que HandleItem se completó con éxito? Supongo que no.

Con múltiples afirmaciones (con mensajes apropiados) sabrá exactamente qué ha fallado. Por lo general, prueba un método varias veces para múltiples entradas o estados de entrada, no para evitar múltiples afirmaciones.

En mi opinión, estas preguntas suelen ser un signo de otra cosa. Es una señal de que HandleItem no es realmente algo que pueda "probar unitariamente", ya que parece delegarlo en otros métodos. Cuando simplemente está validando que HandleItem llama a otros métodos correctamente, se convierte en un candidato de prueba de integración (en cuyo caso aún tendría 3 afirmaciones).

Es posible que desee considerar hacer públicos los otros 3 métodos y probarlos de forma independiente. O incluso extraerlos a otra clase.

Kevin
fuente
0

Usa el segundo enfoque. Con su primer enfoque, si la prueba falla, no sabrá por qué de inmediato, porque podría ser una de las 3 funciones que fallaron. Con el segundo enfoque, sabrá de inmediato dónde ocurrió el problema. Puede poner código duplicado dentro de su función de configuración de prueba.

Eterno21
fuente
-1

En mi humilde opinión, debe probar las tres partes de este método por separado para que sepa más específicamente dónde van las cosas mal cuando lo hacen, evitando al mismo tiempo pasar la misma parte de su código dos veces.

Superusuario
fuente
-2

No creo que haya un caso sólido para escribir métodos de prueba separados para su caso de uso. Si desea obtener los resultados de las tres condiciones variables, puede probar las tres e imprimir sus fallas concatenando en una stringy afirmando si la cadena aún está vacía una vez que haya completado la prueba. Al mantenerlos a todos en el mismo método, sus condiciones y mensajes de falla documentan la condición posterior esperada del método en un lugar, en lugar de dividirlo en tres métodos que podrían separarse más adelante.

Esto significa que su única prueba tendrá más código, pero si tiene tantas condiciones posteriores que es un problema, probablemente quiera refactorizar sus métodos de prueba para probar métodos individuales dentro de HandleItemtodos modos.

IllusiveBrian
fuente