Lección 5 Pruebas unitarias
Veremos los fundamentos de las pruebas unitarias y algunos ejemplos
- Pruebas unitarias
unit-tests
assert
pytest
- Cadenas de prueba
testing-strings)
- Organizar pruebas en carpetas
- Resumiendo
Pruebas unitarias unit-tests
Hasta ahora, probablemente hayas estado probando tu propio código usando
print
declaraciones.Lo más común en la industria es escribir código para probar sus propios programas.
En la ventana de tu consola, escribe
code calculator.py
otouch calculator.py
. Tenga en cuenta que es posible que haya codificado previamente este archivo en una conferencia anterior. En el editor de texto, asegúrese de que su código aparezca de la siguiente manera:def main(): x = int(input("What's x? ")) print("x squared is", square(x)) def square(n): return n * n if __name__ == "__main__": main()
Tenga en cuenta que es posible que pueda probar el código anterior por su cuenta utilizando algunos números obvios como
2
. Sin embargo, considere por qué es posible que desee crear una prueba que garantice que el código anterior funcione correctamente.Siguiendo la convención, creemos un nuevo programa de prueba escribiendo
code test_calculator.py
y modificando su código en el editor de texto de la siguiente manera:from calculator import square def main(): test_square() def test_square(): if square(2) != 4: print("2 squared was not 4") if square(3) != 9: print("3 squared was not 9") if __name__ == "__main__": main()
Observe que estamos importando la
square
función desdesquare.py
la primera línea de código. Por convención, estamos creando una función llamadatest_square
. Dentro de esa función, definimos algunas condiciones para probar.En la ventana de la consola, escriba
python test_calculator.py
. Notarás que no se genera nada. ¡Puede ser que todo esté funcionando bien! Alternativamente, podría ser que nuestra función de prueba no descubriera uno de los "casos de esquina" que podrían producir un error.En este momento, nuestro código prueba dos condiciones. Si quisiéramos probar muchas más condiciones, nuestro código de prueba podría inflarse fácilmente. ¿Cómo podríamos ampliar nuestras capacidades de prueba sin expandir nuestro código de prueba?
assert
El comando de Python
assert
nos permite decirle al compilador que algo, alguna afirmación, es verdadera. Podemos aplicar esto a nuestro código de prueba de la siguiente manera:from calculator import square def main(): test_square() def test_square(): assert square(2) == 4 assert square(3) == 9 if __name__ == "__main__": main()
Observe que definitivamente estamos afirmando qué
square(2)
ysquare(3)
debería ser igual. Nuestro código se reduce de cuatro líneas de prueba a dos.Podemos romper intencionalmente el código de nuestra calculadora modificándolo de la siguiente manera:
def main(): x = int(input("What's x? ")) print("x squared is", square(x)) def square(n): return n + n if __name__ == "__main__": main()
Observe que hemos cambiado el
*
operador a a+
en la función cuadrada.Ahora que se ejecuta
python test_square.py
en la ventana de la consola, notará queAssertionError
el compilador genera an. Básicamente, este es el compilador que nos dice que no se cumplió una de nuestras condiciones.Uno de los desafíos que enfrentamos ahora es que nuestro código podría volverse aún más pesado si quisiéramos proporcionar resultados de error más descriptivos a nuestros usuarios. Posiblemente, podríamos codificar de la siguiente manera:
from calculator import square def main(): test_square() def test_square(): try: assert square(2) == 4 except AssertionError: print("2 squared is not 4") try: assert square(3) == 9 except AssertionError: print("3 squared is not 9") try: assert square(-2) == 4 except AssertionError: print("-2 squared is not 4") try: assert square(-3) == 9 except AssertionError: print("-3 squared is not 9") try: assert square(0) == 0 except AssertionError: print("0 squared is not 0") if __name__ == "__main__": main()
Tenga en cuenta que ejecutar este código producirá múltiples errores. Sin embargo, no produce todos los errores anteriores. Este es un buen ejemplo de que vale la pena probar varios casos para poder detectar situaciones en las que hay errores de codificación.
El código anterior ilustra un desafío importante: ¿Cómo podríamos hacer que sea más fácil probar su código sin docenas de líneas de código como el anterior?
Puede obtener más información en la documentación de Python de assert
.
pytest
pytest
es una biblioteca de terceros que le permite realizar pruebas unitarias de su programa. Es decir, puedes probar tus funciones dentro de tu programa.Para utilizarlo
pytest
, escribapip install pytest
en la ventana de su consola.Antes de aplicar
pytest
a nuestro propio programa, modifique sutest_calculator
función de la siguiente manera:from calculator import square def test_assert(): assert square(2) == 4 assert square(3) == 9 assert square(-2) == 4 assert square(-3) == 9 assert square(0) == 0
Observe cómo el código anterior afirma todas las condiciones que queremos probar.
pytest
nos permite ejecutar nuestro programa directamente a través de él, de modo que podamos ver más fácilmente los resultados de nuestras condiciones de prueba.En la ventana de terminal, escriba
pytest test_calculator.py
. Inmediatamente notarás que se proporcionará el resultado. Observe el rojoF
cerca de la parte superior del resultado, lo que indica que algo en su código falló. Además, observe que el rojoE
proporciona algunas pistas sobre los errores en sucalculator.py
programa. Según el resultado, puede imaginar un escenario en el que3 * 3
se haya generado6
en lugar de9
. Según los resultados de esta prueba, podemos corregir nuestrocalculator.py
código de la siguiente manera:def main(): x = int(input("What's x? ")) print("x squared is", square(x)) def square(n): return n * n if __name__ == "__main__": main()
Observe que hemos cambiado el
+
operador a una*
función en el cuadrado, devolviéndola a un estado de funcionamiento.Volviendo a ejecutar
pytest test_calculator.py
, observe como no se producen errores. ¡Felicidades!Por el momento, no es ideal que
pytest
deje de funcionar después de la primera prueba fallida. Nuevamente, devolvamos nuestrocalculator.py
código a su estado roto:def main(): x = int(input("What's x? ")) print("x squared is", square(x)) def square(n): return n + n if __name__ == "__main__": main()
Observe que hemos cambiado el
*
operador a a+
en la función cuadrada, devolviéndolo a un estado roto.Para mejorar nuestro código de prueba, modifiquemos
test_calculator.py
para dividir el código en diferentes grupos de pruebas:from calculator import square def test_positive(): assert square(2) == 4 assert square(3) == 9 def test_negative(): assert square(-2) == 4 assert square(-3) == 9 def test_zero(): assert square(0) == 0
Observe que hemos dividido las mismas cinco pruebas en tres funciones diferentes. Los marcos de prueba como
pytest
ejecutarán cada función, incluso si hubo una falla en una de ellas. Al volver a ejecutarpytest test_calculator.py
, notará que se muestran muchos más errores. Más resultados de error le permiten explorar más a fondo qué podría estar produciendo los problemas dentro de su código.Habiendo mejorado nuestro código de prueba, devuelva su
calculator.py
código a su pleno funcionamiento:def main(): x = int(input("What's x? ")) print("x squared is", square(x)) def square(n): return n * n if __name__ == "__main__": main()
Observe que hemos cambiado el
+
operador a una*
función en el cuadrado, devolviéndola a un estado de funcionamiento.Al volver a ejecutar
pytest test_calculator.py
, notará que no se encuentran errores.Finalmente, podemos probar que nuestro programa maneja excepciones. Modifiquemos
test_calculator.py
para hacer precisamente eso.
import pytest
from calculator import square
def test_positive():
assert square(2) == 4
assert square(3) == 9
def test_negative():
assert square(-2) == 4
assert square(-3) == 9
def test_zero():
assert square(0) == 0
def test_str():
with pytest.raises(TypeError):
square("cat")
Tenga en cuenta que en lugar de usar assert
, estamos aprovechando una función dentro de la pytest
propia biblioteca llamada raises
que le permite expresar que espera que se genere un error. Necesitamos ir a la parte superior de nuestro programa y agregar import pytest
y luego llamar pytest.raises
con el tipo de error que esperamos.
Nuevamente, al volver a ejecutar
pytest test_calculator.py
, notará que no se encuentran errores.En resumen, ¡depende de usted como codificador definir tantas condiciones de prueba como mejor le parezca!
Puede obtener más información en la documentación de Pytest de pytest
.
Cadenas de prueba
Retrocediendo en el tiempo, considere el siguiente código
hello.py
:def main(): name = input("What's your name? ") hello(name) def hello(to="world"): print("hello,", to) if __name__ == "__main__": main()
Tenga en cuenta que es posible que deseemos probar el resultado de la
hello
función.Considere el siguiente código para
test_hello.py
:from hello import hello def test_hello(): assert hello("David") == "hello, David" assert hello() == "hello, world"
Al observar este código, ¿cree que este enfoque de prueba funcionará bien? ¿Por qué esta prueba podría no funcionar bien? Observe que la
hello
funciónhello.py
imprime algo: es decir, ¡no devuelve un valor!Podemos cambiar nuestra
hello
función dentro dehello.py
la siguiente manera:def main(): name = input("What's your name? ") print(hello(name)) def hello(to="world"): return f"hello, {to}" if __name__ == "__main__": main()
Observe que cambiamos nuestra
hello
función para devolver una cadena. Esto significa efectivamente que ahora podemos usarpytest
para probar lahello
función.Ejecutando
pytest test_hello.py
, ¡nuestro código pasará todas las pruebas!Al igual que con nuestro caso de prueba anterior en esta lección, podemos dividir nuestras pruebas por separado:
from hello import hello def test_default(): assert hello() == "hello, world" def test_argument(): assert hello("David") == "hello, David"
Observe que el código anterior separa nuestra prueba en múltiples funciones, de modo que todas se ejecutarán, incluso si se produce un error.
Organizar pruebas en carpetas
El código de prueba unitaria que utiliza múltiples pruebas es tan común que tiene la capacidad de ejecutar una carpeta completa de pruebas con un solo comando.
Primero, en la ventana de la terminal, ejecute
mkdir test
para crear una carpeta llamadatest
.Luego, para crear una prueba dentro de esa carpeta, escriba en la ventana del terminal
code test/test_hello.py
. Observe quetest/
le indica al terminal que creetest_hello.py
en la carpeta llamadatest
.En la ventana del editor de texto, modifique el archivo para incluir el siguiente código:
from hello import hello def test_default(): assert hello() == "hello, world" def test_argument(): assert hello("David") == "hello, David"
Observe que estamos creando una prueba tal como lo hicimos antes.
pytest
no nos permitirá ejecutar pruebas como una carpeta simplemente con este archivo (o un conjunto completo de archivos) solo sin un__init__
archivo especial. En la ventana de su terminal, cree este archivo escribiendocode test/__init__.py
. Tenga en cuenta lotest/
anterior, así como los guiones bajos dobles a cada lado deinit
. Incluso dejando este__init__.py
archivo vacío,pytest
se informa que toda la carpeta que lo contiene__init__.py
tiene pruebas que se pueden ejecutar.Ahora, escribiendo
pytest test
en la terminal, puede ejecutar toda latest
carpeta de código.
Puede obtener más información en la documentación de Pytest sobre mecanismos de importación .
Resumiendo
Probar su código es una parte natural del proceso de programación. Las pruebas unitarias le permiten probar aspectos específicos de su código. Puedes crear tus propios programas que prueben tu código. Alternativamente, puede utilizar marcos como pytest
para ejecutar sus pruebas unitarias por usted. En esta conferencia aprendiste sobre...
- Pruebas unitarias
assert
pytest