Ejercicio
En este ejercicio, se usa Pytest para probar una función. A continuación, encontrará y corregirá algunos posibles problemas con la función que provocan que las pruebas tengan errores. Examinar los errores y usar los informes de errores enriquecidos de Pytest es esencial para identificar y corregir errores o pruebas problemáticas en el código de producción.
En este ejercicio, se usa una función denominada admin_command() que acepta un comando del sistema como entrada y, opcionalmente, le agrega un prefijo con la herramienta sudo. La función tiene un error que detecta escribiendo pruebas.
Paso 1: Adición de un archivo con pruebas para este ejercicio
Cree un nuevo archivo de prueba usando las convenciones de nombre de archivo de Python para los archivos de prueba. Dele el nombre test_exercise.py al archivo de prueba y agregue el siguiente código:
def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: ["sudo"] + command return commandLa función
admin_command()toma una lista como entrada mediante el argumentocommandy, opcionalmente, puede agregar un prefijo a la lista consudo. Si el argumento de palabra clavesudose establece enFalse, devuelve el mismo comando especificado como entrada.En el mismo archivo, anexe las pruebas para la función
admin_command(). Las pruebas usan un método auxiliar que devuelve un comando de ejemplo:class TestAdminCommand: def command(self): return ["ps", "aux"] def test_no_sudo(self): result = admin_command(self.command(), sudo=False) assert result == self.command() def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() assert result == expected
Nota
No es habitual tener pruebas dentro del mismo archivo como código real. Por motivos de simplicidad, los ejemplos de este ejercicio tienen código real en el mismo archivo. En los proyectos de Python reales, las pruebas suelen estar separadas por archivos y directorios del código que están probando.
Paso 2: Ejecución de las pruebas e identificación del error
Ahora que el archivo de prueba tiene una función para probarla y un par de pruebas para comprobar su comportamiento, es el momento de ejecutar las pruebas y trabajar con errores.
Ejecute el archivo con Python:
$ pytest test_exercise.pyLa ejecución debe completarse con la superación de una prueba y un error, y la salida del error debe ser similar a la siguiente:
=================================== FAILURES =================================== __________________________ TestAdminCommand.test_sudo __________________________ self = <test_exercise.TestAdminCommand object at 0x10634c2e0> def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() > assert result == expected E AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux'] E At index 0 diff: 'ps' != 'sudo' E Right contains one more item: 'aux' E Use -v to get the full diff test_exercise.py:24: AssertionError =========================== short test summary info ============================ FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert... ========================= 1 failed, 1 passed in 0.04s ==========================El resultado produce un error en la prueba
test_sudo(). Pytest proporciona detalles sobre las dos listas que se comparan. En este caso, la variableresultno contiene el comandosudo, que es lo que espera la prueba.
Paso 3: Corrección del error y superación de las pruebas
Antes de realizar cambios, debe comprender por qué hay un error en primer lugar. Aunque se puede ver que no se cumple la expectativa (sudo no está en el resultado), debe averiguar por qué.
Examine las siguientes líneas de código de la función admin_command() cuando se cumpla la condición sudo=True:
if sudo:
["sudo"] + command
La operación de las listas no se usa para devolver el valor. Puesto que no se devuelve, la función termina devolviendo siempre el comando sin sudo.
Actualice la función
admin_command()para que devuelva la operación de lista de modo que se use el resultado modificado al solicitar un comandosudo. La función actualizada debe tener este aspecto:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: return ["sudo"] + command return commandVuelva a ejecutar la prueba con Pytest. Intente aumentar el nivel de detalle de la salida mediante la marca
-vcon Pytest:$ pytest -v test_exercise.pyCompruebe la salida. Ahora debería mostrar que se superan dos pruebas:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 cachedir: .pytest_cache rootdir: /private collected 2 items test_exercise.py::TestAdminCommand::test_no_sudo PASSED [ 50%] test_exercise.py::TestAdminCommand::test_sudo PASSED [100%] ============================== 2 passed in 0.00s ===============================
Nota
Dado que la función puede trabajar con más valores de mayúsculas y minúsculas diferentes, se deben agregar más pruebas para cubrir esas variaciones. Esto impediría que los cambios futuros que se realicen en la función provoquen un comportamiento diferente (inesperado).
Paso 4: Adición de código nuevo con pruebas
Después de agregar pruebas en los pasos anteriores, debe sentirse cómodo al realizar más cambios en la función y comprobarlos con pruebas. Incluso si los cambios no están cubiertos por las pruebas existentes, puede sentirse seguro de que no está rompiendo ninguna suposición anterior.
En este caso, la función admin_command() confía ciegamente en que el argumento command siempre es una lista. Vamos a mejorarlo asegurándose de que se genera una excepción con un mensaje de error útil.
En primer lugar, cree una prueba que capture el comportamiento. Aunque la función aún no se ha actualizado, pruebe un enfoque primero de prueba (también conocido como Desarrollo controlado por pruebas o TDD).
- Actualice el archivo test_exercise.py para que importe
pytesten la parte superior. Esta prueba usa un asistente interno del marco de trabajopytest:
import pytest- Ahora, anexe una nueva prueba a la clase para comprobar la excepción. Esta prueba debe esperar un
TypeErrorde la función cuando el valor que se le pasa no es una lista:
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)- Actualice el archivo test_exercise.py para que importe
Vuelva a ejecutar las pruebas con Pytest. Todos deberían pasar:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /private/ collected 3 items test_exercise.py ... [100%] ============================== 3 passed in 0.00s ===============================La prueba es lo suficientemente buena para comprobar
TypeError, pero sería bueno agregar el código con un mensaje de error útil.Actualice la función para generar explícitamente un
TypeErrorcon un mensaje de error útil:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if not isinstance(command, list): raise TypeError(f"was expecting command to be a list, but got a {type(command)}") if sudo: return ["sudo"] + command return commandPor último, actualice el método
test_non_list_commands()para comprobar el mensaje de error:def test_non_list_commands(self): with pytest.raises(TypeError) as error: admin_command("some command", sudo=True) assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"La prueba actualizada usa
errorcomo una variable que contiene toda la información de excepción. Conerror.value.args, puede examinar los argumentos de la excepción. En este caso, el primer argumento tiene la cadena de error que la prueba puede comprobar.
Compruebe su trabajo
Llegado a este punto, debe tener un archivo de prueba de Python llamado test_exercise.py, en el que se incluya lo siguiente:
- Una función
admin_command()que acepta un argumento y un argumento de palabra clave. - Una excepción
TypeErrorcon un mensaje de error útil en la funciónadmin_command(). - Una clase de prueba
TestAdminCommand()que tiene un método auxiliarcommand()y tres métodos de prueba que comprueban la funciónadmin_command().
Todas las pruebas deben pasar sin errores al ejecutarlas en el terminal.