Buscar..


Configurando py.test

py.test es una de varias bibliotecas de pruebas de terceros que están disponibles para Python. Se puede instalar utilizando pip con

pip install pytest

El código a probar

Digamos que estamos probando una función de adición en projectroot/module/code.py :

# projectroot/module/code.py
def add(a, b):
    return a + b

El código de prueba

Creamos un archivo de prueba en projectroot/tests/test_code.py . El archivo debe comenzar con test_ para que se reconozca como un archivo de prueba.

# projectroot/tests/test_code.py
from module import code


def test_add():
    assert code.add(1, 2) == 3

Corriendo la prueba

Desde projectroot simplemente ejecutamos py.test :

# ensure we have the modules
$ touch tests/__init__.py
$ touch module/__init__.py
$ py.test
================================================== test session starts ===================================================
platform darwin -- Python 2.7.10, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /projectroot, inifile:
collected 1 items

tests/test_code.py .

================================================ 1 passed in 0.01 seconds ================================================

Pruebas de falla

Una prueba fallida proporcionará resultados útiles en cuanto a lo que salió mal:

# projectroot/tests/test_code.py
from module import code


def test_add__failing():
    assert code.add(10, 11) == 33

Resultados:

$ py.test
================================================== test session starts ===================================================
platform darwin -- Python 2.7.10, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /projectroot, inifile:
collected 1 items

tests/test_code.py F

======================================================== FAILURES ========================================================
___________________________________________________ test_add__failing ____________________________________________________

    def test_add__failing():
>       assert code.add(10, 11) == 33
E       assert 21 == 33
E        +  where 21 = <function add at 0x105d4d6e0>(10, 11)
E        +    where <function add at 0x105d4d6e0> = code.add

tests/test_code.py:5: AssertionError
================================================ 1 failed in 0.01 seconds ================================================

Introducción a los accesorios de prueba

Las pruebas más complicadas a veces necesitan tener las cosas configuradas antes de ejecutar el código que desea probar. Es posible hacer esto en la función de prueba en sí, pero luego terminas con funciones de prueba grandes que hacen tanto que es difícil decir dónde se detiene la configuración y comienza la prueba. También puede obtener una gran cantidad de códigos de configuración duplicados entre sus diversas funciones de prueba.

Nuestro archivo de código:

# projectroot/module/stuff.py
class Stuff(object):
    def prep(self):
        self.foo = 1
        self.bar = 2

Nuestro archivo de prueba:

# projectroot/tests/test_stuff.py
import pytest
from module import stuff


def test_foo_updates():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Estos son ejemplos bastante simples, pero si nuestro objeto Stuff necesitara mucha más configuración, se volvería difícil de manejar. Vemos que hay un código duplicado entre nuestros casos de prueba, así que vamos a reformular eso en una función separada primero.

# projectroot/tests/test_stuff.py
import pytest
from module import stuff


def get_prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff


def test_foo_updates():
    my_stuff = get_prepped_stuff()
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates():
    my_stuff = get_prepped_stuff()
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Esto se ve mejor, pero todavía tenemos la my_stuff = get_prepped_stuff() nuestras funciones de prueba.

Py.test accesorios para el rescate!

Los accesorios son versiones mucho más potentes y flexibles de las funciones de configuración de prueba. Pueden hacer mucho más de lo que estamos aprovechando aquí, pero lo haremos paso a paso.

Primero cambiamos get_prepped_stuff a un dispositivo llamado prepped_stuff . Desea nombrar sus aparatos con sustantivos en lugar de verbos debido a la forma en que los aparatos se utilizarán en las funciones de prueba más adelante. El @pytest.fixture indica que esta función específica debe manejarse como un accesorio en lugar de una función regular.

@pytest.fixture
def prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff

Ahora debemos actualizar las funciones de prueba para que usen el aparato. Esto se hace agregando un parámetro a su definición que coincida exactamente con el nombre del dispositivo. Cuando se ejecuta py.test, ejecutará el dispositivo antes de ejecutar la prueba, luego pasará el valor de retorno del dispositivo a la función de prueba a través de ese parámetro. (Tenga en cuenta que los dispositivos no necesitan devolver un valor; en su lugar, pueden hacer otras tareas de configuración, como llamar a un recurso externo, organizar las cosas en el sistema de archivos, poner valores en una base de datos, independientemente de las pruebas necesarias para la configuración)

def test_foo_updates(prepped_stuff):
    my_stuff = prepped_stuff
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates(prepped_stuff):
    my_stuff = prepped_stuff
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Ahora puedes ver por qué lo nombramos con un sustantivo. pero la línea my_stuff = prepped_stuff es bastante inútil, así que simplemente utilicemos prepped_stuff directamente.

def test_foo_updates(prepped_stuff):
    assert 1 == prepped_stuff.foo
    prepped_stuff.foo = 30000
    assert prepped_stuff.foo == 30000


def test_bar_updates(prepped_stuff):
    assert 2 == prepped_stuff.bar
    prepped_stuff.bar = 42
    assert 42 == prepped_stuff.bar

Ahora estamos usando accesorios! Podemos ir más lejos cambiando el alcance del dispositivo (de modo que solo se ejecute una vez por módulo de prueba o sesión de ejecución del conjunto de pruebas en lugar de una vez por función de prueba), construyendo dispositivos que utilicen otros dispositivos, parametrizando el dispositivo (para que el dispositivo y todo las pruebas que usan ese dispositivo se ejecutan varias veces, una vez para cada parámetro dado al dispositivo), dispositivos que leen los valores del módulo que los llama ... como se mencionó anteriormente, los dispositivos tienen mucha más potencia y flexibilidad que una función de configuración normal.

Limpieza después de las pruebas.

Digamos que nuestro código ha crecido y nuestro objeto Stuff ahora necesita una limpieza especial.

# projectroot/module/stuff.py
class Stuff(object):
def prep(self):
    self.foo = 1
    self.bar = 2

def finish(self):
    self.foo = 0
    self.bar = 0

Podríamos agregar algún código para llamar a la limpieza al final de cada función de prueba, pero los dispositivos proporcionan una mejor manera de hacerlo. Si agrega una función al dispositivo y lo registra como finalizador , se llamará al código en la función de finalizador después de que se realice la prueba con el dispositivo. Si el alcance del dispositivo es mayor que una sola función (como módulo o sesión), el finalizador se ejecutará una vez que se hayan completado todas las pruebas en el alcance, por lo que una vez que el módulo haya terminado de ejecutarse o al final de toda la sesión de ejecución de prueba .

@pytest.fixture
def prepped_stuff(request):  # we need to pass in the request to use finalizers
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    def fin():  # finalizer function
        # do all the cleanup here
        my_stuff.finish()
    request.addfinalizer(fin)  # register fin() as a finalizer
    # you can do more setup here if you really want to
    return my_stuff

Usar la función de finalizador dentro de una función puede ser un poco difícil de entender a primera vista, especialmente cuando tienes dispositivos más complicados. En su lugar, puede utilizar un dispositivo de rendimiento para hacer lo mismo con un flujo de ejecución legible más humano. La única diferencia real es que, en lugar de utilizar la return , usamos un yield en la parte del dispositivo donde se realiza la configuración y el control debe ir a una función de prueba, luego se agrega todo el código de limpieza después del yield . También lo decoramos como un yield_fixture para que py.test sepa cómo manejarlo.

@pytest.yield_fixture
def prepped_stuff():  # it doesn't need request now!
    # do setup
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    # setup is done, pass control to the test functions
    yield my_stuff
    # do cleanup 
    my_stuff.finish()

¡Y eso concluye la Introducción a los Aparatos de Prueba!

Para obtener más información, consulte la documentación oficial de la prueba py.test y la documentación oficial de la unidad de rendimiento.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow