Python Language
py.test
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.