Testing y Depuración. En este capítulo exploraremos dos aspectos fundamentales del desarrollo de software: testing y depuración. Estos procesos son esenciales para identificar y corregir errores y así garantizar que tu código funcione correctamente. Un programa puede estar bien diseñado, pero sin pruebas y depuración adecuadas es difícil asegurar su fiabilidad, rendimiento y calidad.
¿Qué aprenderás en este capítulo?
- Depuración de Código:
- Técnicas básicas como el uso de
print
para rastrear valores de variables en un punto concreto del código. - Uso de herramientas más avanzadas como el depurador
pdb
, que permite ejecutar tu programa paso a paso para localizar errores.
- Técnicas básicas como el uso de
- Introducción a Testing:
- Pruebas unitarias: Asegúrate de que las partes individuales de tu código funcionen como se espera utilizando herramientas como
unittest
. - Pruebas avanzadas con pytest: Conocerás esta herramienta moderna y flexible que simplifica la escritura y ejecución de pruebas.
- Pruebas unitarias: Asegúrate de que las partes individuales de tu código funcionen como se espera utilizando herramientas como
- Buenas Prácticas:
- Cómo escribir pruebas que cubran la mayor cantidad posible de escenarios.
- La importancia de probar tanto casos exitosos como escenarios de fallo.
- Organización del código de pruebas para mantenerlo limpio y eficiente.
¡¡Vamos!!
ANDHOT Repetidor WiFi, expandedor WiFi de Doble frecuencia de 1200mps 5ghz y 2,4ghz, Modo repetidor/Router/Ap, Amplificador de señal Wi-Fi con 4 Antenas potentes,Adecuado para Uso doméstico/de Oficina
17% FueraDepuración de Código con print()
La depuración es el proceso de identificar y corregir errores en el código. Uno de los métodos más simples y comunes es el uso de declaraciones print()
para inspeccionar el estado de las variables en un punto determinado del código.
Uso de print()
:
def suma(a, b):
print(f"Sumando {a} y {b}") # Imprimir valores antes de la operación
return a + b
resultado = suma(5, 10)
print("Resultado:", resultado)
Aunque útil y sencillo, este método puede volverse engorroso en programas más grandes y complejos. Por ello, Python incluye el módulo pdb
, que proporciona un depurador interactivo más robusto.
Depuración de Código con pdb
:
El depurador pdb
(Python Debugger) es una herramienta interactiva incorporada en Python que permite analizar y depurar programas paso a paso. Es especialmente útil para identificar errores y entender cómo fluye el código durante la ejecución. A diferencia de print que simplemente permite comprobar valores, pdb
ofrece un control mucho más granular sobre el flujo del programa lo que lo hace ideal para una depuración mucho más avanzada.
¿Qué puedes hacer con pdb
?
- Ejecutar código paso a paso: Examinar cómo se ejecutan las líneas de tu programa.
- Inspeccionar variables: Ver los valores de las variables en cualquier punto de la ejecución.
- Establecer puntos de interrupción: Detener la ejecución en ubicaciones específicas del código para analizar el estado actual.
- Modificar el flujo del programa: Saltar a diferentes partes del código o continuar la ejecución desde un punto específico.
- Examinar errores: Analizar un error después de que el programa haya fallado.
Cómo usar pdb
1. Iniciar el depurador
Hay varias formas de activar pdb
en tu programa:
Desde el terminal:
Puedes ejecutar tu script directamente con el depurador utilizando el siguiente comando:
python -m pdb tu_archivo.py
Esto ejecutará tu programa en modo depuración, permitiéndote interactuar con él línea por línea.
Insertar un punto de interrupción en el código:
Utiliza la función pdb.set_trace()
para pausar la ejecución en un punto específico:
import pdb
def calcular_area(base, altura):
area = base * altura
pdb.set_trace() # Aquí se pausará la ejecución
return area
resultado = calcular_area(5, 10)
print(f"El área es {resultado}")
Cuando la ejecución llega a pdb.set_trace()
se abre una consola interactiva de pdb
.
@Tip. Puedes poner tantos puntos de ruptura como necesites en diferentes partes del código. Así podrás saltar bloques de código que no necesites comprobar.
2. Comandos básicos de pdb
Control del flujo del programa:
Una vez dentro de la consola de depuración puedes utilizar los siguientes comandos:
n
(next): Ejecuta la siguiente línea de código sin entrar en funciones.s
(step): Avanza a la siguiente línea y entra en funciones llamadas, si es necesario.c
(continue): Reanuda la ejecución normal del programa hasta el próximo punto de interrupción.q
(quit): Termina la sesión de depuración y sale del programa.
Inspección de variables y estado:
p nombre_variable
: Muestra el valor de la variable especificada.pp nombre_variable
: Muestra el valor de una variable de manera más legible.locals()
: Lista todas las variables locales disponibles en el contexto actual.globals()
: Muestra las variables globales del programa.
Puntos de interrupción:
b línea
: Establece un punto de interrupción en una línea específica.b archivo:línea
: Establece un punto de interrupción en otro archivo y línea.l
: Muestra el código alrededor de la línea actual para contexto.
Exploración del stack (llamadas):
w
(where): Muestra la pila de llamadas actual para entender cómo se llegó al punto actual.u
(up): Mueve hacia arriba en la pila de llamadas.d
(down): Mueve hacia abajo en la pila de llamadas.
3. Ejemplo Práctico de Uso
Imagina que estás depurando una función que calcula el factorial de un número, ¿Cómo podríamos depurarlo?
import pdb
def factorial(n):
if n < 0:
raise ValueError("El número debe ser positivo.")
elif n == 0 or n == 1:
return 1
else:
pdb.set_trace() # Punto de interrupción
return n * factorial(n - 1)
resultado = factorial(5)
print(f"El factorial es {resultado}")
Cuando se ejecuta, la ejecución se detendrá en pdb.set_trace()
al calcular el factorial. Desde allí, puedes ir avanzando con el comando n a lo largo del programa e inspeccionar el valor de las variables con locals(). Si continuas haciendo lo mismo en cada llamada recursiva verás como cambia el valor de n
$python -m pdb factorial.py
> /python/factorial.py(1)<module>()
-> import pdb
(Pdb) n
> /python/factorial.py(3)<module>()
-> def factorial(n):
(Pdb) n
> /python/factorial.py(12)<module>()
-> resultado = factorial(5)
(Pdb) n
> /python/factorial.py(10)factorial()
-> return n * factorial(n - 1)
(Pdb) locals()
{'n': 5}
(Pdb) n
> /python/factorial.py(10)factorial()
-> return n * factorial(n - 1)
(Pdb) locals()
{'n': 4}
(Pdb) n
> /python/factorial.py(10)factorial()
-> return n * factorial(n - 1)
(Pdb) locals()
{'n': 3}
(Pdb) n
> /python/factorial.py(10)factorial()
-> return n * factorial(n - 1)
(Pdb) locals()
{'n': 2}
(Pdb) n
--Return--
> /python/factorial.py(10)factorial()->2
-> return n * factorial(n - 1)
(Pdb) locals()
{'n': 2, '__return__': 2}
(Pdb)
4. Depuración Post-Mortem
A veces, es útil depurar un programa después de que haya fallado. Python ofrece esto con pdb
, vamos a crear un programa para forzar un error al hacer una división por 0:
#!/bin/python3
a = 5
b = 0
resultado = a / b
print (resultado)
$ python -m pdb div_cero.py
> /python/div_cero.py(3)<module>()
-> a = 5
(Pdb) c
Traceback (most recent call last):
File "/usr/lib/python3.12/pdb.py", line 1944, in main
pdb._run(target)
File "/usr/lib/python3.12/pdb.py", line 1738, in _run
self.run(target.code)
File "/usr/lib/python3.12/bdb.py", line 600, in run
exec(cmd, globals, locals)
File "<string>", line 1, in <module>
File "/python/div_cero.py", line 6, in <module>
resultado = a / b
~~^~~
ZeroDivisionError: division by zero
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> /python/div_cero.py(6)<module>()
-> resultado = a / b
(Pdb) --KeyboardInterrupt--
Si ocurre un error, pdb
se activará automáticamente en el punto donde ocurrió el fallo, y podrás inspeccionar el estado del programa.
5. Consejos para Usar pdb
- Usa puntos de interrupción estratégicamente en áreas donde sospechas que podría haber problemas.
- Combina
pdb
con pruebas automatizadas para depurar casos específicos. - Familiarízate con los comandos básicos; esto hará que la depuración sea más rápida y eficiente.
- Considera herramientas gráficas basadas en
pdb
, comopdb++
o plugins de los IDEs de programación para una experiencia más visual.
Testing. (unittest y pytest)
El testing es una parte fundamental del desarrollo de software que asegura que nuestro código funciona según lo esperado. Python ofrece herramientas para realizar pruebas, siendo unittest
y pytest
dos de las más populares. Aunque ambas cumplen con el propósito de probar el código, tienen características particulares que las hacen adecuadas para diferentes necesidades.
Unittest. El Módulo Estándar de Python para Pruebas
unittest
es el framework estándar de Python para escribir y ejecutar pruebas. Está inspirado en el modelo xUnit, que es utilizado en muchos lenguajes de programación.
Características Clave
- Soporte para Pruebas Unitarias y Funcionales: Ideal para probar funciones o clases individuales.
- Integración con Python: Incluido en la biblioteca estándar, lo que significa que no necesitas instalar paquetes adicionales.
- Estructura Formal: Requiere que organices tus pruebas dentro de clases y métodos.
Estructura de una Prueba en unittest
Para usar unittest
, debes crear clases que hereden de unittest.TestCase
. Cada método que comience con test_
será reconocido como una prueba.
Ejemplo Básico con unittest
import unittest
# Función a probar
def sumar(a, b):
return a + b
# Clase de prueba
class TestFunciones(unittest.TestCase):
def test_sumar_numeros(self):
self.assertEqual(sumar(2, 3), 5) # Verifica que la suma sea correcta
def test_sumar_cadenas(self):
self.assertEqual(sumar("Hola ", "Mundo"), "Hola Mundo") # Verifica la concatenación de cadenas
# Ejecutar las pruebas
if __name__ == '__main__':
unittest.main()
Explicación del Código
unittest.TestCase
: Todas las clases de prueba deben heredar de esta clase base.assertEqual
: Compara si el resultado desumar(2, 3)
es igual a5
.unittest.main()
: Ejecuta todas las pruebas en el archivo.
Métodos Comunes de unittest
assertEqual(a, b)
: Verifica quea
sea igual ab
.assertNotEqual(a, b)
: Verifica quea
no sea igual ab
.assertTrue(expr)
: Verifica que la expresiónexpr
sea verdadera.assertFalse(expr)
: Verifica que la expresiónexpr
sea falsa.assertRaises(Exception, callable)
: Verifica que se levante una excepción.
Configuración Previa y Limpieza con setUp
y tearDown
Puedes inicializar recursos antes de cada prueba con setUp
y liberar recursos con tearDown
.
pythonCopiar códigoclass TestConRecursos(unittest.TestCase):
def setUp(self):
self.recurso = "Recurso inicializado"
def tearDown(self):
self.recurso = None
def test_recurso(self):
self.assertIsNotNone(self.recurso)
pytest
. Framework para Pruebas
pytest
es un framework más reciente que simplifica la escritura de pruebas, haciendo el proceso más flexible y menos formal que unittest
.
Características Clave de pytest
- Menos Verbosidad: No necesitas usar clases, aunque son compatibles.
- Ampliable: Permite usar plugins y personalizar el comportamiento.
- Detección Automática de Pruebas: Detecta funciones que comienzan con
test_
. - Mejor Salida de Errores: Ofrece mensajes claros cuando una prueba falla.
Estructura de una Prueba en pytest
En pytest
, puedes escribir pruebas simplemente definiendo funciones con el prefijo test_
.
Ejemplo Básico con pytest
# Función a probar
def multiplicar(a, b):
return a * b
# Pruebas
def test_multiplicar_numeros():
assert multiplicar(3, 4) == 12
def test_multiplicar_cadenas():
assert multiplicar("Hola", 3) == "HolaHolaHola"
Para ejecutar estas pruebas, simplemente usa:
pytest nombre_del_archivo.py
Ventajas de pytest
- Sin Clases Necesarias: Puedes escribir funciones en lugar de clases.
- Asserts Sencillos: Usa el operador
assert
nativo de Python. - Configuraciones Avanzadas: Soporte para fixtures y parametrización.
Comparación de unittest
y pytest
Característica | unittest | pytest |
---|---|---|
Verbosidad | Requiere clases y métodos formales | Funciones simples con assert |
Detección Automática | Limitada | Excelente |
Plugins | No soporta plugins | Amplia biblioteca de plugins |
Compatibilidad | Incluido en Python | Requiere instalación |
Curva de Aprendizaje | Ligeramente más pronunciada | Más intuitivo y rápido |
Casos Prácticos Avanzados
Uso de Fixtures en pytest
Los fixtures son funciones especiales en pytest
que se utilizan para inicializar datos antes de ejecutar las pruebas.
import pytest
@pytest.fixture
def datos_ejemplo():
return {"nombre": "Ana", "edad": 30}
def test_datos(datos_ejemplo):
assert datos_ejemplo["edad"] == 30
Pruebas Parametrizadas con pytest
Permite ejecutar una misma prueba con diferentes valores de entrada.
import pytest
@pytest.mark.parametrize("a,b,esperado", [
(2, 3, 5),
(-1, 1, 0),
(10, 5, 15)
])
def test_suma_parametrizada(a, b, esperado):
assert a + b == esperado
Conclusión
Tanto unittest
como pytest
son herramientas para realizar pruebas en Python. Mientras que unittest
es formal y ampliamente utilizado debido a su inclusión en la biblioteca estándar, pytest
es más flexible y fácil de usar, siendo la elección preferida para proyectos modernos.
Dependiendo de tus necesidades y del tamaño de tu proyecto, puedes optar por uno u otro, o incluso combinarlos si es necesario.
Resumen
El testing y la depuración son elementos esenciales del desarrollo de software que garantizan la calidad y confiabilidad del código. Utilizar herramientas como print()
y pdb
para depurar y bibliotecas como unittest
y pytest
para realizar pruebas unitarias permite a los desarrolladores identificar y corregir errores. Aplicar buenas prácticas en la escritura de pruebas no solo mejora la calidad del software, sino que también facilita el mantenimiento y la evolución del código a lo largo del tiempo.
Pasemos a la parte práctica!!!!
Ejercicio 1: Depuración con pdb
Tienes un programa que calcula la suma de los cuadrados de los números en una lista. Sin embargo, parece que no está funcionando correctamente. Usa pdb
para depurar el programa y encontrar el error.
pdb
Tienes un programa que calcula la suma de los cuadrados de los números en una lista. Sin embargo, parece que no está funcionando correctamente. Usa
pdb
para depurar el programa y encontrar el error.import pdb
def suma_cuadrados(lista):
resultado = 0
for num in lista:
pdb.set_trace() # Punto de interrupción
resultado += num ** 2
return resultado
# Lista de ejemplo
numeros = [1, 2, '3', 4]
# Llamada a la función
print(suma_cuadrados(numeros))
Explicación
- Punto de Interrupción: La sentencia
pdb.set_trace()
detiene la ejecución en ese punto, permitiéndote inspeccionar variables y ejecutar comandos interactivos. - Error Detectado: El error ocurre al intentar elevar
'3'
(una cadena) al cuadrado. - Solución: Asegurarte de que todos los elementos de la lista sean números.
Versión Corregida
def suma_cuadrados(lista):
resultado = 0
for num in lista:
if isinstance(num, (int, float)):
resultado += num ** 2
else:
print(f"Ignorado elemento no numérico: {num}")
return resultado
numeros = [1, 2, '3', 4]
print(suma_cuadrados(numeros)) # Salida: 21
Ejercicio 2: Prueba Unitaria con unittest
Escribe una prueba unitaria para verificar el funcionamiento correcto de una función que calcula el factorial de un número. La función debe devolver 1 si el número es 0 o 1, y multiplicar los números consecutivos en caso contrario.
unittest
Escribe una prueba unitaria para verificar el funcionamiento correcto de una función que calcula el factorial de un número. La función debe devolver 1 si el número es 0 o 1, y multiplicar los números consecutivos en caso contrario.
import unittest
def factorial(n):
if n == 0 or n == 1:
return 1
elif n > 1:
resultado = 1
for i in range(2, n + 1):
resultado *= i
return resultado
else:
raise ValueError("El número debe ser mayor o igual a 0.")
class TestFactorial(unittest.TestCase):
def test_factorial_cero(self):
self.assertEqual(factorial(0), 1)
def test_factorial_uno(self):
self.assertEqual(factorial(1), 1)
def test_factorial_positivo(self):
self.assertEqual(factorial(5), 120)
def test_factorial_error(self):
with self.assertRaises(ValueError):
factorial(-3)
if __name__ == '__main__':
unittest.main()
Explicación
- Función Probada:
factorial
. - Casos Cubiertos:
- Factorial de 0 y 1.
- Factorial de un número positivo.
- Manejo de valores negativos que generan excepciones.
- Método
assertRaises
: Verifica que se lance una excepción específica.
Ejercicio 3: Pruebas Parametrizadas con pytest
Crea una función que determine si un número es primo. Escribe pruebas parametrizadas usando pytest
para verificar su funcionalidad con múltiples valores.
pytest
Crea una función que determine si un número es primo. Escribe pruebas parametrizadas usando
pytest
para verificar su funcionalidad con múltiples valores.import pytest
def es_primo(n):
if n <= 1:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
@pytest.mark.parametrize("numero, esperado", [
(1, False), # 1 no es primo
(2, True), # 2 es primo
(9, False), # 9 no es primo
(17, True), # 17 es primo
(25, False), # 25 no es primo
])
def test_es_primo(numero, esperado):
assert es_primo(numero) == esperado
Explicación
- Función Probada:
es_primo
, que verifica si un número es primo. - Parametrización: Permite probar varios casos con diferentes entradas y resultados esperados.
- Ejecución: Usa el comando
pytest nombre_del_archivo.py
para ejecutar las pruebas.
Bibliografía de Python de interés.
- Curso de Programación Python. Autor: Arturo Montejo Ráez y Salud María Jiménez Zafra (Editorial Anaya)
- Aprende Python desde cero hasta avanzado. Autor: Xavier Reyes Ochoa (Editorial Book Shelter GmbH)
- Aprende la Programación Orientada a Objetos con el lenguaje Python. Autor: Vincent Boucheny (Editorial Ediciones ENI)
- 100 Ejercicios Python para praticar. Autor: Laurentine K.Masson (Editorial: Publicación Independiente).