python - eyed3 - Pytest donde almacenar los datos esperados
id3v2 4 python (3)
Piense si todo el contenido del archivo de configuración realmente necesita ser probado.
Si solo deben verificarse varios valores o subcadenas, prepare una plantilla esperada para esa configuración. Los lugares probados se marcarán como "variables" con alguna sintaxis especial. Luego prepare una lista esperada separada de los valores para las variables en la plantilla. Esta lista esperada se puede almacenar como un archivo separado o directamente en el código fuente.
Ejemplo para la plantilla:
ALLOWED_HOSTS = [''{host}'']
DEBUG = {debug}
DEFAULT_FROM_EMAIL = ''{email}''
Aquí, las variables de la plantilla se colocan dentro de llaves.
Los valores esperados pueden verse como:
host = www.example.com
debug = False
email = [email protected]
o incluso como una simple lista separada por comas:
www.example.com, False, [email protected]
Luego, su código de prueba puede producir el archivo esperado de la plantilla reemplazando las variables con los valores esperados. Y el archivo esperado se compara con el real.
El mantenimiento de la plantilla y los valores esperados por separado tiene la ventaja de que puede tener muchos conjuntos de datos de prueba utilizando la misma plantilla.
Probando solo variables
Un enfoque aún mejor es que el método de generación de configuración produce solo los valores necesarios para el archivo de configuración. Estos valores se pueden insertar fácilmente en la plantilla por otro método. Pero la ventaja es que el código de prueba puede comparar directamente todas las variables de configuración por separado y de manera clara.
Plantillas
Si bien es fácil reemplazar las variables con los valores necesarios en la plantilla, hay bibliotecas de plantillas listas, que permiten hacerlo solo en una línea. Aquí hay algunos ejemplos: Django , Jinja , Mako
Función de prueba Necesito pasar parámetros y ver si la salida coincide con la salida esperada.
Es fácil cuando la respuesta de la función es solo una pequeña matriz o una cadena de una línea que se puede definir dentro de la función de prueba, pero supongamos que la función I prueba modifica un archivo de configuración que puede ser enorme. O la matriz resultante tiene algo de 4 líneas de longitud si la defino explícitamente. ¿Dónde guardo eso para que mis pruebas permanezcan limpias y fáciles de mantener?
Ahora mismo, si eso es una cadena, solo coloco un archivo cerca de la prueba .py
y lo open()
dentro de la prueba:
def test_if_it_works():
with open(''expected_asnwer_from_some_function.txt'') as res_file:
expected_data = res_file.read()
input_data = ... # Maybe loaded from a file as well
assert expected_data == if_it_works(input_data)
Veo muchos problemas con este enfoque, como el problema de mantener este archivo actualizado. Se ve mal también. Puedo hacer las cosas probablemente mejor moviendo esto a un accesorio:
@pytest.fixture
def expected_data()
with open(''expected_asnwer_from_some_function.txt'') as res_file:
expected_data = res_file.read()
return expected_data
@pytest.fixture
def input_data()
return ''1,2,3,4''
def test_if_it_works(input_data, expected_data):
assert expected_data == if_it_works(input_data)
Eso simplemente traslada el problema a otro lugar y, por lo general, necesito probar si la función funciona en caso de una entrada vacía, una entrada con un solo elemento o varios elementos, por lo que debería crear un dispositivo grande que incluya los tres casos o dispositivos múltiples. Al final el código se vuelve bastante desordenado.
Si una función espera un diccionario complicado como entrada o devuelve el diccionario del mismo tamaño, el código de prueba se vuelve feo:
@pytest.fixture
def input_data():
# It''s just an example
return {[''one_value'': 3, ''one_value'': 3, ''one_value'': 3,
''anotherky'': 3, ''somedata'': ''somestring''],
[''login'': 3, ''ip_address'': 32, ''value'': 53,
''one_value'': 3], [''one_vae'': 3, ''password'': 13, ''lue'': 3]}
Es bastante difícil leer las pruebas con estos dispositivos y mantenerlos actualizados.
Actualizar
Después de buscar por un tiempo, encontré una biblioteca que resolvía una parte de un problema cuando en lugar de grandes archivos de configuración tenía grandes respuestas HTML. Es betamax .
Para un uso más fácil he creado un accesorio:
from betamax import Betamax
@pytest.fixture
def session(request):
session = requests.Session()
recorder = Betamax(session)
recorder.use_cassette(os.path.join(os.path.dirname(__file__), ''fixtures'', request.function.__name__)
recorder.start()
request.addfinalizer(recorder.stop)
return session
Así que ahora, en mis pruebas, solo uso el dispositivo de session
y cada solicitud que hago se serializa automáticamente en el archivo fixtures/test_name.json
, así que la próxima vez que ejecute la prueba, en lugar de hacer una biblioteca de solicitudes HTTP real, la fixtures/test_name.json
desde el sistema de archivos:
def test_if_response_is_ok(session):
r = session.get("http://google.com")
Es muy útil porque para mantener estos aparatos actualizados, solo necesito limpiar la carpeta de fixtures
y volver a ejecutar mis pruebas.
Si solo tiene algunas pruebas, ¿por qué no incluir los datos como un literal de cadena ?:
expected_data = """
Your data here...
"""
Si tiene un puñado, o los datos esperados son realmente largos, creo que su uso de los accesorios tiene sentido.
Sin embargo, si tiene muchos, entonces tal vez una solución diferente sería mejor. De hecho, para un proyecto tengo más de cien archivos de entrada y salida esperada. Así que construí mi propio marco de prueba (más o menos). Usé Nose, pero PyTest también funcionaría. Creé un generador de prueba que recorría el directorio de archivos de prueba. Para cada archivo de entrada, se produjo una prueba que comparó la salida real con la salida esperada (PyTest lo llama parametrizing ). Luego documenté mi marco para que otros pudieran usarlo. Para revisar y / o editar las pruebas, solo edita la entrada y / o los archivos de salida esperados y nunca necesita mirar el archivo de prueba de python. Para permitir que diferentes archivos de entrada tengan diferentes opciones definidas, también creé un archivo de configuración YAML para cada directorio (JSON también funcionaría para mantener las dependencias bajas). Los datos de YAML consisten en un diccionario donde cada clave es el nombre del archivo de entrada y el valor es un diccionario de palabras clave que pasarán a la función que se está probando junto con el archivo de entrada. Si está interesado, aquí está el código fuente y la documentation . Hace poco jugué con la idea de definir las opciones como Unittests here (solo requiere la unidad de pruebas incorporada lib) pero no estoy seguro de si me gusta.
Una vez tuve un problema similar, donde tengo que probar el archivo de configuración con un archivo esperado. Así es como lo arreglé:
Cree una carpeta con el mismo nombre de su módulo de prueba y en la misma ubicación. Pon todos tus archivos esperados dentro de esa carpeta.
test_foo/ expected_config_1.ini expected_config_2.ini test_foo.py
Cree un dispositivo responsable de mover el contenido de esta carpeta a un archivo temporal. Hice uso de
tmpdir
fixture para este asunto.from __future__ import unicode_literals from distutils import dir_util from pytest import fixture import os @fixture def datadir(tmpdir, request): '''''' Fixture responsible for searching a folder with the same name of test module and, if available, moving all contents to a temporary directory so tests can use them freely. '''''' filename = request.module.__file__ test_dir, _ = os.path.splitext(filename) if os.path.isdir(test_dir): dir_util.copy_tree(test_dir, bytes(tmpdir)) return tmpdir
Usa tu nuevo accesorio.
def test_foo(datadir): expected_config_1 = datadir.join(''expected_config_1.ini'') expected_config_2 = datadir.join(''expected_config_2.ini'')
Recuerde: datadir
es lo mismo que el dispositivo tmpdir
, además de la capacidad de trabajar con los archivos esperados colocados en la carpeta con el nombre del módulo de prueba.