En el campo de las pruebas automáticas, puede encontrar diferentes herramientas, por ejemplo, py.test es una de las soluciones más populares para escribir pruebas automáticas en Python.
Después de haber revisado muchos recursos relacionados con pytest y haber estudiado la documentación del sitio web oficial del proyecto, no pude encontrar una descripción directa de la solución para una de las tareas principales: ejecutar pruebas con datos de prueba almacenados en un archivo separado. De lo contrario, se puede decir, la carga de parámetros en funciones de prueba desde el archivo (s) o la parametrización desde el archivo directamente. Tal procedimiento no se describe en ninguna parte de las complejidades y la única mención de esta característica está en solo una línea de la documentación de pytest.
En este artículo hablaré sobre mi solución a este problema.
Desafío
La tarea principal es generar casos de prueba en forma de los parámetros test_input
y test_input
en cada función de prueba individual a partir de los nombres de función de archivo correspondientes.
Tareas adicionales:
- elija un formato de archivos legible para humanos con casos de prueba;
- dejar la capacidad de soportar casos de prueba codificados;
- mostrar identificadores claros para cada caso.
Kit de herramientas
En el artículo, uso Python 3 (2.7 también es adecuado), pyyaml y pytest
(versiones 5+ para Python 3 o 4.6 para Python 2.7) sin usar complementos de terceros. Además, se utilizará la biblioteca os
estándar.
El archivo mismo del que tomaremos casos de prueba debe estructurarse utilizando un lenguaje de marcado que sea conveniente para que una persona lo entienda. En mi caso, se eligió YAML (porque resuelve la tarea adicional de elegir un formato legible para humanos) . De hecho, qué tipo de lenguaje de marcado para archivos con conjuntos de datos necesita depende de los requisitos presentados en el proyecto.
Implementación
Dado que el pilar principal del universo en la programación es el acuerdo, tendremos que introducir varios de ellos para nuestra solución.
Intercepcion
Para empezar, esta solución utiliza la función de intercepción pytest_generate_tests
( wiki ), que se inicia en la etapa de generación de casos de prueba, y su argumento metafunc
, que nos permite parametrizar la función. En este punto, pytest itera sobre cada función de prueba y ejecuta el código de generación posterior para ella.
Argumentos
Debe definir una lista exhaustiva de parámetros para las funciones de prueba. En mi caso, el diccionario es test_input
y cualquier tipo de datos (más a menudo una cadena o un entero) en el resultado expected_result
. Necesitamos estos parámetros para usar en metafunc.parametrize(...)
.
Parametrización
Esta función repite completamente la operación del @pytest.mark.parametrize
parametrización @pytest.mark.parametrize
, que toma como primer argumento una cadena que enumera los argumentos de la función de prueba (en nuestro caso "test_input, expected_result"
) y una lista de datos mediante los cuales iterará para crear nuestros casos de prueba (por ejemplo, [(1, 2), (2, 4), (3, 6)]
).
En la batalla, se verá así:
@pytest.mark.parametrize("test_input, expected_result", [(1, 2), (2, 4), (3, 6)]) def test_multiplication(test_input, expected_result): assert test_input * 2 == expected_result
Y en nuestro caso, indicaremos esto de antemano:
Filtrado
A partir de aquí también se sigue la asignación de esas funciones de prueba donde se requieren datos de un archivo, de aquellos que usan datos estáticos / dinámicos. Aplicaremos este filtrado antes de analizar la información del archivo.
Los filtros en sí pueden ser cualquiera, por ejemplo:
- Marcador de función llamado
yaml
:
De lo contrario, el mismo filtro puede implementarse así:
- El argumento de la función
test_input
:
Esta opción me convenía más.
Resultado
Necesitamos agregar solo la parte donde analizamos los datos del archivo. Esto no será difícil en el caso de yaml (así como json, xml, etc.) , por lo que recopilamos todo en el montón.
Escribimos un script de prueba como este:
Un archivo de datos:
# test_multiplication.yaml - !!python/tuple [1,2] - !!python/tuple [1,3] - !!python/tuple [1,5] - !!python/tuple [2,4] - !!python/tuple [3,4] - !!python/tuple [5,4]
Obtenemos la siguiente lista de casos de prueba:
pytest /test_script.py --collect-only ======================== test session starts ======================== platform linux -- Python 3.7.4, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 rootdir: /pytest_habr collected 6 items <Module test_script.py> <Function test_multiplication[1-2]> <Function test_multiplication[1-3]> <Function test_multiplication[1-5]> <Function test_multiplication[2-4]> <Function test_multiplication[3-4]> <Function test_multiplication[5-4]> ======================== no tests ran in 0.04s ========================
Y al ejecutar el script, este resultado: 4 failed, 2 passed, 1 warnings in 0.11s
Añadir asignaciones
Esto podría terminar el artículo, pero en aras de la complejidad, agregaré identificadores más convenientes a nuestra función, otro análisis y marcado de datos de cada caso de prueba individual.
Entonces, de inmediato, el código:
En consecuencia, cambiamos la apariencia de nuestro archivo YAML:
# test_multiplication.yaml - test_data: [1, 2] id: 'one_two' - test_data: [1,3] marks: ['xfail'] - test_data: [1,5] marks: ['skip'] - test_data: [2,4] id: "it's good" marks: ['xfail'] - test_data: [3,4] marks: ['negative'] - test_data: [5,4] marks: ['more_than']
Entonces la descripción cambiará a:
<Module test_script.py> <Function test_multiplication[one_two]> <Function test_multiplication[1_3]> <Function test_multiplication[1_5]> <Function test_multiplication[it's good]> <Function test_multiplication[3_4]> <Function test_multiplication[5_4]>
Y el lanzamiento será: 2 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 2 warnings in 0.12s
PD: advertencias, porque los marcadores autoescritos no se registran en pytest.ini
En desarrollo del tema
Listo para discutir en los comentarios preguntas sobre el tipo:
- ¿Cuál es la mejor manera de escribir un archivo yaml?
- ¿En qué formato es más conveniente almacenar datos de prueba?
- ¿Qué caso de prueba adicional se necesita en la etapa de generación?
- ¿Necesito identificadores para cada caso?