Accesorios de Pytest

Completado

Los fixtures son funciones auxiliares de pytest utilizadas para crear pruebas modulares, escalables y fáciles de mantener. Los accesorios se usan para configurar condiciones previas para pruebas como las conexiones de base de datos, la creación de datos de prueba o la configuración de un estado del sistema necesario para poder ejecutar una prueba. También se pueden usar para limpiar después de que se ejecuten las pruebas.

Entre las características clave de los accesorios pytest se incluyen:

  • Control de ámbito: los accesorios se pueden configurar para tener distintos ámbitos mediante el scope parámetro (como function, class, moduleo session), que determinan la frecuencia con la que se llama al accesorio.
  • Control de la configuración y desmontaje: Pytest administra el ciclo de vida de los accesorios, configurando y desmontando automáticamente según sea necesario.
  • Inserción de dependencias: los fixtures se inyectan en funciones de prueba como argumentos, lo que clarifica qué pruebas dependen de qué fixtures.
  • Reutilización y modularidad: los accesorios se pueden definir en un solo lugar y usarse en varias funciones de prueba, módulos o incluso proyectos.

Creación de un accesorio de archivo temporal

Al escribir pruebas que interactúan con archivos, es habitual necesitar archivos temporales que no desordenen el sistema de archivos después de la prueba. Con pytest, podemos crear un accesorio que configure un archivo temporal. El accesorio usa el módulo de tempfile Python para generar archivos temporales de forma segura, lo que garantiza que se pueden usar y eliminar sin afectar al entorno local. (En esta versión inicial de nuestra configuración, el archivo no se eliminará automáticamente debido al indicador delete=False. Abordamos la eliminación de archivos más adelante).

Este es el aspecto del accesorio:

import pytest
import tempfile

@pytest.fixture
def tmp_file():
    def create():
        # Create a named temporary file that persists beyond the function scope
        temp = tempfile.NamedTemporaryFile(delete=False)
        return temp.name
    return create

En esta configuración, tmp_file() actúa como accesorio. El nombre del accesorio es cómo se hace referencia a las pruebas. Dentro del accesorio, la función create() anidada crea el archivo solo cuando se llama, en lugar de en la configuración del accesorio. Esto permite un control preciso sobre cuándo se crea el archivo temporal, lo que resulta útil en las pruebas en las que el tiempo y el estado del archivo son críticos.

Dentro de la función anidada create(), se crea un archivo temporal y, a continuación, se devuelve la ruta de acceso absoluta a este. Este es un ejemplo de cómo una prueba podría usar el accesorio que hemos escrito:

import os

def test_file(tmp_file):
    path = tmp_file()
    assert os.path.exists(path)

Una prueba usa un accesorio especificando el nombre del accesorio como argumento. Nuestro caso de uso simple se puede expandir fácilmente escribiendo en el archivo o realizando modificaciones, como cambiar los permisos o la propiedad.

Gestión del alcance

En pytest, administrar el ciclo de vida de los recursos de prueba a través de rutinas de configuración y desmontaje es fundamental para mantener entornos de prueba limpios y eficientes. También quiere proteger la integridad de las pruebas asegurándose de que cada prueba comienza con un estado conocido y coherente. De forma predeterminada, los fixtures de pytest funcionan con un function ámbito, lo que afecta al comportamiento de dos maneras:

  • Ciclo de vida por prueba: el valor devuelto del accesorio se vuelve a calcular para cada función de prueba que la usa, asegurándose de que cada prueba funciona con un estado nuevo.
  • Limpieza después de cada uso: las operaciones de limpieza necesarias se realizan después de cada prueba que utiliza el accesorio.

Pytest también permite definir un ámbito más amplio para los accesorios para optimizar el rendimiento y el uso de recursos. La delimitación es especialmente útil en situaciones como administrar el estado de la base de datos o cuando se tienen configuraciones de estado complejas que requieren mucho tiempo para establecerse. Los cuatro ámbitos disponibles son:

  • function: Ámbito predeterminado, el accesorio se ejecuta una vez por prueba.
  • class: el accesorio se ejecuta una vez por clase de prueba.
  • module: se ejecuta una vez para un módulo.
  • session: se ejecuta una vez por sesión de prueba. Este ámbito es útil para operaciones costosas que deben conservarse durante toda la sesión de prueba, como inicializar un servicio o iniciar un servidor de bases de datos.

En este caso, la ejecución una vez significa que el valor devuelto se almacena en caché. Por lo tanto, un accesorio que tiene un ámbito de "módulo" puede llamarse varias veces en un módulo de prueba, pero el valor devuelto será el de la primera prueba que lo llamó.

Este es el aspecto que tendría el accesorio tmp_file() con un ámbito de módulo:

import pytest
import tempfile

@pytest.fixture(scope="module")
def tmp_file():
    def create():
        temp = tempfile.NamedTemporaryFile(delete=False)
        return temp.name
    return create

Administración de limpieza

El código anterior que especifica el tmp_file accesorio crea un archivo temporal, pero no controla automáticamente la limpieza una vez completadas las pruebas. Para asegurarse de que los archivos temporales no se dejan atrás, puede usar el accesorio request de pytest para registrar una función de limpieza.

Aquí se muestra cómo puede modificar el tmp_file accesorio para incluir la limpieza automática:

import pytest
import tempfile
import os

@pytest.fixture(scope="module")
def tmp_file(request):
    # Create a temporary file that persists beyond the function scope
    temp = tempfile.NamedTemporaryFile(delete=False)

    def create():
        # Returns the path of the temporary file
        return temp.name

    def cleanup():
        # Remove the file after the tests are done
        os.remove(temp.name)

    # Register the cleanup function to be called after the last test in the module
    request.addfinalizer(cleanup)

    return create

Al usar request.addfinalizer() y pasar la función anidada cleanup(), se llama a la limpieza en función del ámbito. En este caso, el ámbito es module, por lo que después de todas las pruebas de un módulo, pytest llama a una función de limpieza.

Uso de conftest.py

En lugar de incluir los accesorios en los archivos de prueba, puede guardarlos en un archivo conftest.py . Todos los accesorios de conftest.py están disponibles automáticamente para las pruebas en el mismo directorio sin tener que importarlos explícitamente.

Exploración de accesorios integrados

Pytest tiene muchos accesorios integrados diseñados para simplificar las pruebas. Estos accesorios pueden controlar la instalación y limpieza automáticamente, lo que le permite centrarse en escribir los casos de prueba en lugar de la administración de pruebas.

Entre los accesorios integrados clave se incluyen:

  • cache: se usa para crear y administrar la caché de nivel de prueba, lo que resulta útil para almacenar datos entre sesiones de prueba.
  • capsys: captura y permite la inspección de stderr y stdout, lo que facilita la inspección y prueba de las salidas de la consola.
  • tmpdir: proporciona un directorio temporal para los archivos que deben crearse y usarse durante las pruebas.
  • monkeypatch: proporciona una manera de modificar de forma segura el comportamiento y los valores de objetos, funciones y el entorno del sistema operativo.

El papel del método monkeypatching en las pruebas

La prueba de código que se integra estrechamente con recursos externos, como bases de datos o API externas, puede resultar difícil debido a las dependencias implicadas. Una técnica llamada monkey patching implica la modificación temporal del sistema durante las pruebas, lo que permite la independencia de los sistemas externos y le permite alterar de forma segura el estado y el comportamiento del entorno del sistema operativo durante las pruebas.

Este es un ejemplo de cómo invalidar la os.path.exists() función mediante el monkeypatch accesorio:

import os

def test_os(monkeypatch):
    # Override os.path.exists to always return False
    monkeypatch.setattr('os.path.exists', lambda x: False)
    assert not os.path.exists('/')

Como alternativa, puede usar el setattr() método con referencia directa al objeto y al atributo :

def test_os(monkeypatch):
    # Specify the object and attribute to override
    monkeypatch.setattr(os.path, 'exists', lambda x: False)
    assert not os.path.exists('/')

Además de establecer atributos e invalidar métodos, el monkeypatch accesorio puede establecer y eliminar variables de entorno, cambiar valores de diccionario y modificar rutas de acceso del sistema. El accesorio monkeypatch revierte automáticamente los cambios después de cada prueba, pero se debe tener cuidado al usar el accesorio monkeypatch. Estas son algunas razones para tener cuidado al usarlo:

-Claridad y mantenimiento del monkeypatch código: el uso excesivo o el uso de él de maneras complejas pueden dificultar la comprensión y el mantenimiento de las pruebas. Al leer los resultados de las pruebas, es posible que no esté claro inmediatamente cómo se supone que los componentes se comportan normalmente frente a cómo se modifican para las pruebas.-Validez de la prueba: el método monkeypatching a veces puede dar lugar a pruebas que se superen en condiciones artificiales muy diferentes del entorno de producción. Esto puede crear una falsa sensación de seguridad, ya que las pruebas podrían superarse porque la prueba alteró el comportamiento del sistema demasiado drásticamente.-Sobredependencia en los detalles de implementación: las pruebas que dependen del monkeypatching podrían estar estrechamente acopladas a detalles específicos de implementación del código que están probando. Esto puede hacer que las pruebas sean frágiles y susceptibles a interrumpirse incluso con cambios menores en el código base subyacente.-Complejidad de la depuración: las pruebas de depuración que usan monkeypatch pueden ser más complejas, especialmente si la revisión cambia aspectos fundamentales del comportamiento de las aplicaciones. Comprender por qué se produce un error en una prueba podría requerir un análisis más profundo de cómo se modifican los componentes durante la prueba.

Aunque monkeypatch es una herramienta eficaz para crear entornos de prueba aislados y controlados, se debe usar con criterio y con una comprensión clara de cómo afecta al conjunto de pruebas y al comportamiento de la aplicación.