Entonces, tengo un módulo de autenticación que escribí hace algún tiempo. Ahora veo los errores de mi camino y escribo pruebas unitarias para ello. Mientras escribo las pruebas unitarias, me resulta difícil encontrar buenos nombres y buenas áreas para probar. Por ejemplo, tengo cosas como
- Requiere Login_should_redirect_when_not_logged_in
- Requiere inicio de sesión debe pasar a través de cuando se registra
- Login_should_work_when_given_proper_credentials
Personalmente, creo que es un poco feo, aunque parezca "correcto". También tengo problemas para diferenciar entre pruebas simplemente escaneando sobre ellas (tengo que leer el nombre del método al menos dos veces para saber qué acaba de fallar)
Entonces, pensé que tal vez en lugar de escribir pruebas que simplemente prueben la funcionalidad, tal vez escriba un conjunto de pruebas que cubran escenarios.
Por ejemplo, este es un trozo de prueba que se me ocurrió:
public class Authentication_Bill
{
public void Bill_has_no_account()
{ //assert username "bill" not in UserStore
}
public void Bill_attempts_to_post_comment_but_is_redirected_to_login()
{ //Calls RequiredLogin and should redirect to login page
}
public void Bill_creates_account()
{ //pretend the login page doubled as registration and he made an account. Add the account here
}
public void Bill_logs_in_with_new_account()
{ //Login("bill", "password"). Assert not redirected to login page
}
public void Bill_can_now_post_comment()
{ //Calls RequiredLogin, but should not kill request or redirect to login page
}
}
¿Es esto un patrón escuchado? He visto historias de aceptación y cosas así, pero esto es fundamentalmente diferente. La gran diferencia es que estoy ideando escenarios para "forzar" las pruebas. En lugar de intentar manualmente encontrar posibles interacciones que tendré que probar. Además, sé que esto fomenta las pruebas unitarias que no prueban exactamente un método y clase. Aunque creo que esto está bien. Además, soy consciente de que esto causará problemas al menos para algunos marcos de prueba, ya que generalmente asumen que las pruebas son independientes entre sí y el orden no importa (donde sería en este caso).
De todos modos, ¿es este un patrón aconsejable? ¿O sería esto un ajuste perfecto para las pruebas de integración de mi API en lugar de las pruebas de "unidad"? Esto es solo en un proyecto personal, así que estoy abierto a experimentos que pueden o no funcionar bien.
_test
anexo y uso los comentarios para anotar qué resultados espero. Si es un proyecto personal, encuentre un estilo con el que se sienta cómodo y manténgalo.Respuestas:
Sí, es una buena idea dar a sus pruebas nombres de los escenarios de ejemplo que está probando. Y el uso de su herramienta de prueba de unidad para algo más que solo pruebas de unidad puede estar bien, también, muchas personas hacen esto con éxito (yo también).
Pero no, definitivamente no es una buena idea escribir sus pruebas de manera tal que el orden de ejecución de las pruebas sea importante. Por ejemplo, NUnit permite al usuario seleccionar interactivamente qué prueba desea ejecutar, por lo que esto ya no funcionará de la manera prevista.
Puede evitar esto fácilmente aquí separando la parte principal de la prueba de cada prueba (incluida la "afirmación") de las partes que establecen su sistema en el estado inicial correcto. Usando su ejemplo anterior: escriba métodos para crear una cuenta, iniciar sesión y publicar un comentario, sin ninguna afirmación. Luego reutilice esos métodos en diferentes pruebas. También deberá agregar algún código al
[Setup]
método de sus dispositivos de prueba para asegurarse de que el sistema esté en un estado inicial correctamente definido (por ejemplo, no hay cuentas hasta ahora en la base de datos, nadie está conectado hasta ahora, etc.).EDITAR: Por supuesto, esto parece estar en contra de la naturaleza de "historia" de sus pruebas, pero si le da nombres significativos a sus métodos auxiliares, encontrará sus historias dentro de cada prueba.
Entonces, debería verse así:
fuente
Un problema al contar una historia con las pruebas unitarias es que no hace explícito que las pruebas unitarias deben organizarse y ejecutarse de manera totalmente independiente entre sí.
Una buena prueba de unidad debe estar completamente aislada de todos los demás códigos dependientes, es la unidad de código más pequeña que se puede probar.
Esto brinda el beneficio de que, además de confirmar que el código funciona, si una prueba falla, obtienes el diagnóstico de dónde exactamente está mal el código de forma gratuita. Si una prueba no está aislada, tiene que ver de qué depende para saber exactamente qué salió mal y perder un beneficio importante de las pruebas unitarias. Tener el orden de ejecución también puede generar muchos falsos negativos, si una prueba falla, es posible que las siguientes pruebas fallen a pesar de que el código que prueban funciona perfectamente bien.
Un buen artículo con más profundidad es el clásico sobre pruebas híbridas sucias .
Para que las clases, los métodos y los resultados sean legibles, la gran prueba de Arte de la Unidad utiliza la convención de nomenclatura
Clase de prueba:
Métodos de prueba:
Para copiar el ejemplo de @Doc Brown, en lugar de usar [Configuración] que se ejecuta antes de cada prueba, escribo métodos auxiliares para construir objetos aislados para probar.
Por lo tanto, las pruebas que fallan tienen un nombre significativo que le da una narración sobre exactamente qué método falló, la condición y el resultado esperado.
Así es como siempre he escrito pruebas unitarias, pero un amigo ha tenido mucho éxito con Gerkin .
fuente
Lo que estás describiendo suena más como el Diseño Conducido por el Comportamiento (BDD) que las pruebas unitarias para mí. Eche un vistazo a SpecFlow, que es una tecnología .NET BDD basada en Gherkin DSL.
Cosas poderosas que cualquier humano puede leer / escribir sin saber nada sobre codificación. Nuestro equipo de prueba está disfrutando de un gran éxito al aprovecharlo para nuestros paquetes de pruebas de integración.
En cuanto a las convenciones para las pruebas unitarias, la respuesta de @ DocBrown parece sólida.
fuente
assert(value === expected)
BDD =value.should.equals(expected)
+ usted describe las características en capas que resuelven el problema de "independencia de prueba unitaria". Este es un gran estilo!