python - software - técnicas de prueba de caja blanca
¿Cómo deben documentarse las pruebas unitarias? (5)
Estoy tratando de mejorar el número y la calidad de las pruebas en mis proyectos de Python. Una de las dificultades que he encontrado a medida que aumenta la cantidad de pruebas es saber qué hace cada prueba y cómo se supone que ayuda a detectar problemas. Sé que parte del seguimiento de las pruebas son mejores nombres de pruebas unitarias (que se han abordado en elsewhere ), pero también me interesa entender cómo la documentación y las pruebas unitarias se unen.
¿Cómo pueden documentarse las pruebas unitarias para mejorar su utilidad cuando esas pruebas fallan en el futuro? Específicamente, ¿qué hace una buena docstring de prueba unitaria?
Apreciaría tanto las respuestas descriptivas como los ejemplos de pruebas unitarias con excelente documentación. Aunque estoy trabajando exclusivamente con Python, estoy abierto a prácticas de otros idiomas.
Cuando la prueba falla (que debería ser antes de que pase), debería ver el mensaje de error y saber qué está pasando. Eso solo pasa si lo planeas así.
Es totalmente una cuestión de la denominación de la clase de prueba, el método de prueba y el mensaje de afirmación. Cuando una prueba falla, y no puedes decir qué hay de estas tres pistas, entonces renombra algunas cosas o divide algunas clases de pruebas.
No sucede si el nombre del aparato es ClassXTests y el nombre de la prueba es TestMethodX y el mensaje de error es "verdadero esperado, devuelto falso". Eso es un signo de escritura de prueba descuidada.
La mayoría de las veces no debería tener que leer la prueba o cualquier comentario para saber qué ha sucedido.
Debe usar una combinación de nombres de métodos descriptivos y comentarios en la cadena de documentos. Una buena manera de hacerlo es incluir un procedimiento básico y pasos de verificación en la cadena de documentos. Luego, si ejecuta estas pruebas desde algún tipo de marco de prueba que automatiza la ejecución de las pruebas y la recopilación de resultados, puede hacer que el marco registre el contenido de la cadena de documentos para cada método de prueba junto con su stdout + stderr.
Aquí hay un ejemplo básico:
class SimpelTestCase(unittest.TestCase):
def testSomething(self):
""" Procedure:
1. Print something
2. Print something else
---------
Verification:
3. Verify no errors occurred
"""
print "something"
print "something else"
Tener el procedimiento con la prueba hace que sea mucho más fácil averiguar qué está haciendo la prueba. Y si incluye la cadena de documentos con el resultado de la prueba, será mucho más fácil averiguar qué fue lo que salió mal al analizar los resultados. El lugar anterior donde trabajé hizo algo como esto y funcionó muy bien cuando se produjeron fallas. Ejecutamos las pruebas unitarias en cada registro de forma automática, utilizando CruiseControl.
Documenté la mayoría de mis pruebas de unidad con el nombre del método exclusivamente:
testInitializeSetsUpChessBoardCorrectly()
testSuccessfulPromotionAddsCorrectPiece()
Para casi el 100% de mis casos de prueba, esto explica claramente lo que la prueba de unidad está validando y eso es todo lo que uso. Sin embargo, en algunos de los casos de prueba más complicados, agregaré algunos comentarios a lo largo del método para explicar lo que están haciendo varias líneas.
He visto una herramienta antes (creo que fue para Ruby) que generaría archivos de documentación al analizar los nombres de todos los casos de prueba en un proyecto, pero no recuerdo el nombre. Si tuviste casos de prueba para una clase de reina de ajedrez:
testCanMoveStraightUpWhenNotBlocked()
testCanMoveStraightLeftWhenNotBlocked()
La herramienta generaría un documento HTML con contenidos como este:
Queen requirements:
- can move straight up when not blocked.
- can move straight left when not blocked.
El nombre del método de prueba debe describir exactamente lo que está probando. La documentación debe decir qué hace que la prueba falle.
Quizás el problema no está en la mejor manera de escribir las cadenas de documentación de prueba, sino en cómo escribir las pruebas por sí mismas. Las pruebas de refactorización de tal manera que se autodocumentan pueden recorrer un largo camino, y su cadena de documentos no se desactiva cuando cambia el código.
Hay algunas cosas que puede hacer para aclarar las pruebas:
- Nombres de métodos de prueba claros y descriptivos (ya mencionados)
- el cuerpo de la prueba debe ser claro y conciso (autodocumentación)
- Resumen de configuración complicada / desmontaje, etc. en métodos
- ¿Más?
Por ejemplo, si tiene una prueba como esta:
def test_widget_run_returns_0():
widget = Widget(param1, param2, "another param")
widget.set_option(true)
widget.set_temp_dir("/tmp/widget_tmp")
widget.destination_ip = "10.10.10.99"
return_value = widget.run()
assert return_value == 0
assert widget.response == "My expected response"
assert widget.errors == None
Puede reemplazar las instrucciones de configuración con una llamada al método:
def test_widget_run_returns_0():
widget = create_basic_widget()
return_value = widget.run()
assert return_value == 0
assert_basic_widget(widget)
def create_basic_widget():
widget = Widget(param1, param2, "another param")
widget.set_option(true)
widget.set_temp_dir("/tmp/widget_tmp")
widget.destination_ip = "10.10.10.99"
return widget
def assert_basic_widget():
assert widget.response == "My expected response"
assert widget.errors == None
Tenga en cuenta que su método de prueba ahora se compone de una serie de llamadas a métodos con nombres que revelan la intención, una especie de DSL específica para sus pruebas. ¿Una prueba como esa todavía necesita documentación?
Otra cosa a tener en cuenta es que su método de prueba se encuentra principalmente en un nivel de abstracción. Alguien que lea el método de prueba verá que el algoritmo es:
- creando un widget
- llamando a ejecutar en el widget
- Afirmando el código hicimos lo que esperamos.
Su comprensión del método de prueba no se confunde con los detalles de la configuración del widget, que es un nivel de abstracción más bajo que el método de prueba.
La primera versión del método de prueba sigue el patrón de configuración en línea . La segunda versión sigue los métodos de creación y configuración delegada .
Generalmente estoy en contra de los comentarios, excepto cuando explican el "por qué" del código. Leer el Código limpio del tío Bob Martin me convenció de esto. Hay un capítulo sobre comentarios, y hay un capítulo sobre pruebas. Lo recomiendo.
Para obtener más información sobre las mejores prácticas de pruebas automatizadas, consulte xUnit Patterns .