Pruebas automatizadas - Ejercicios
Las instrucciones de este documento asumen que estás utilizando Visual Studio Code. En la última sección se incluyen instrucciones para ejecutar los tests desde la terminal.
Creación de un proyecto
Para esta práctica, vas a trabajar en el proyecto pymath
, estructurándolo de forma modular. Esto te permitirá organizar el código y los tests de manera profesional, y además facilitará la ejecución de pruebas y otras herramientas de análisis.
Estructura del proyecto
Crea la carpeta pymath
para el proyecto con la siguiente estructura:
pymath/
├── src/
| └── pymath/
| └── functions.py # Aquí irá el código fuente
├── tests/ # Aquí irán los tests con unittest
├── .env # Variable de entorno para PYTHONPATH
└── .vscode/ └── settings.json # Configuración para que VS Code encuentre los módulos
Archivos iniciales
.env
PYTHONPATH=src
Este archivo permitirá que los tests funcionen correctamente al ejecutarlos desde la raíz del proyecto.
.vscode/settings.json
{
"python.analysis.extraPaths": ["src"]
}
Si estás usando Visual Studio Code, añade este archivo para que el editor pueda resolver correctamente las importaciones de tus módulos.
Este paso evita errores como ModuleNotFoundError cuando importas pymath en los tests.
src/pymath/functions.py
# Aquí irá el código fuente
src/pymath/__init__.py
# Aquí exportaremos las funciones que queramos que sean accesibles al importar el módulo pymath
Creación de un entorno virtual
Crea y activa un entorno virtual para el proyecto. Esto te permitirá gestionar las dependencias de manera aislada y evitar conflictos con otras aplicaciones.
Función add
Añade la función add
al archivo src/pymath/functions.py
. Esta función suma dos números (de forma muy enrevesada) y devuelve el resultado. Nos servirá para realizar pruebas.
src/pymath/functions.py
def add(a: int, b: int) -> int:
"""
Adds two integers.
:param a: First integer
:param b: Second integer
:return: Sum of a and b
"""
= 0
total = 1
increment if a < 0:
= -a
a = -1
increment for i in range(int(a)):
+= increment
total
= 1
increment if b < 0:
= -b
b = -1
increment for j in range(int(b)):
+= increment
total
return total
Añade la función add
al archivo src/pymath/__init__.py
para que sea accesible al importar el módulo pymath
.
src/pymath/__init__.py
from .functions import add
Probando la función add
Ahora que tenemos la función add
, vamos a crear pruebas para asegurarnos de que funciona correctamente.
Crea un archivo test_add.py
en la carpeta tests
y añade la siguiente prueba unitaria para la función add
.
tests/test_add.py
import unittest
from pymath import add
class TestAddFunction(unittest.TestCase):
def test_add_positive_numbers(self):
self.assertEqual(add(2, 3), 5)
Ejecuta las pruebas para asegurarte de que todo funciona correctamente. Si todo está bien la ejecución de la prueba debería mostrarse en verde.
Cobertura de código
Para asegurarte de que tus pruebas cubren adecuadamente el código puedes utilizar la herramienta coverage
. Para instalarla ejecuta el siguiente comando:
pip install coverage
Luego, ejecuta las pruebas con cobertura pulsando el botón Run Tests with Coverage
en la parte superior derecha de la ventana de pruebas de Visual Studio Code.
Esto generará un informe de cobertura que te mostrará qué líneas de código han sido ejecutadas durante las pruebas y cuáles no.
Como podrás observar, además del código fuente de la aplicación, el informe también incluye el código de los tests y todos los ficheros del entorno virtual (si está en la misma carpeta).
Para filtrar el informe y mostrar solo la cobertura del código fuente de la aplicación puedes añadir un archivo .coveragerc
en la raíz del proyecto con el siguiente contenido:
.coveragerc
[run]
omit =
__init__.py
./tests/*
.conda/*
.venv/*
Vuelve a ejecutar las pruebas con cobertura y verás que el informe ahora solo muestra la cobertura del código fuente de la aplicación.
Añade más pruebas para la función add
en el archivo test_add.py
con el objetivo de cubrir todos los casos posibles y obtener una cobertura en las pruebas del 100%. Asegúrate de cubrir diferentes casos, como:
- Sumar números negativos.
- Sumar cero.
- Sumar un número positivo y uno negativo.
- Sumar dos números negativos.
Función substract
Añade la función substract
al archivo src/pymath/functions.py
. Esta función resta dos números haciendo uso de add
y devuelve el resultado.
src/pymath/functions.py
def substract(a: int, b: int) -> int:
"""
Subtracts two integers.
:param a: First integer
:param b: Second integer
:return: Difference of a and b
"""
= -b
b = add(a, b)
total
return total
Recuerda añadir la función substract
al archivo src/pymath/__init__.py
para que sea accesible al importar el módulo pymath
.
Probando la función substract
Ahora que tenemos la función substract
, vamos a crear pruebas para asegurarnos de que funciona correctamente.
tests/test_substract.py
import unittest
from pymath import substract
class TestSubstractFunction(unittest.TestCase):
def test_subtract_positive_numbers(self):
self.assertEqual(substract(5, 3), 2)
Aunque la prueba anterior es correcta, no tiene en cuenta que la función substract
hace uso de la función add
. Por lo tanto, si la función add
no funciona correctamente, la función substract
tampoco lo hará.
Hay varias alternativas para comprobar el funcionamiento de substract
.
Comprobar que substract
llama a add
Una forma de comprobar que substract
llama a add
es utilizando unittest.mock
para simular el comportamiento de la función add
. Esto te permitirá verificar que substract
llama a add
con los argumentos correctos.
tests/test_substract.py
import unittest
from unittest.mock import patch
from pymath import substract
class TestSubstractFunction(unittest.TestCase):
def test_subtract_positive_numbers(self):
self.assertEqual(substract(5, 3), 2)
@patch('pymath.functions.add')
def test_subtract_without_add(self, mock_add):
= 2 # add siempre devuelve 2
mock_add.return_value self.assertEqual(substract(5, 3), 2)
5, -3) mock_add.assert_called_once_with(
Simular el comportamiento de add
Simulando el comportamiento de add
puedes verificar que substract
hace su trabajo correctamente sin depender de la implementación de add
. Esto es útil para asegurarte de que substract
funciona correctamente incluso si add
tiene errores.
tests/test_substract.py
import unittest
from unittest.mock import patch
from pymath import substract
class TestSubstractFunction(unittest.TestCase):
def test_subtract_positive_numbers(self):
self.assertEqual(substract(5, 3), 2)
@patch('pymath.functions.add')
def test_subtract_without_add(self, mock_add):
= 2 # add siempre devuelve 2
mock_add.return_value self.assertEqual(substract(5, 3), 2)
5, -3)
mock_add.assert_called_once_with(
@patch('pymath.functions.add')
def test_substract_with_sum(self, mock_add):
= lambda a, b: a + b # Reemplaza el comportamiento de add
mock_add.side_effect self.assertEqual(substract(5, 3), 2)
Función divide
Añade la función divide
al archivo src/pymath/functions.py
. Esta función divide dos números y devuelve el resultado. Si el divisor es cero, lanza una excepción ValueError
.
src/pymath/functions.py
def divide(a: int, b: int) -> float:
"""
Divides two integers.
:param a: Dividend
:param b: Divisor
:return: Quotient of a and b
"""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
Recuerda añadir la función divide
al archivo src/pymath/__init__.py
para que sea accesible al importar el módulo pymath
.
Probando la función divide
Ahora que tenemos la función divide
, vamos a crear pruebas para asegurarnos de que funciona correctamente.
tests/test_divide.py
import unittest
from pymath import divide
class TestDivideFunction(unittest.TestCase):
def test_divide_positive_numbers(self):
self.assertEqual(divide(6, 3), 2)
def test_divide_by_zero(self):
with self.assertRaises(ValueError):
6, 0) divide(
En este caso, la prueba test_divide_by_zero
verifica que se lanza una excepción ValueError
cuando se intenta dividir por cero.
Pruebas para logging
Al usar logging
en el código, es importante asegurarse de que los mensajes de registro se generen correctamente.
Modifica el archivo src/pymath/functions.py
para incluir un logger y registrar los resultados de las funciones.
src/pymath/functions.py
import logging
=logging.INFO)
logging.basicConfig(level= logging.getLogger(__name__)
logger
def add(a: int, b: int) -> int:
f"Adding {a} and {b}")
logger.info(# resto de la función...
Añade ahora una prueba para verificar que el logger se llama correctamente.
tests/test_add.py
class TestAddFunction(unittest.TestCase):
def test_logging(self):
with self.assertLogs('pymath.functions', level='INFO') as log:
2, 3)
add(self.assertIn('INFO:pymath.functions:Adding 2 and 3', log.output)
Añade mensajes de logging a las funciones substract
y divide
y las pruebas correspondientes para verificar que se registran correctamente.
Ejercicios
Añade estas funciones al archivo src/pymath/functions.py
y crea las pruebas correspondientes en la carpeta tests
, asegurándote de cubrir todos los casos posibles y obtener una cobertura del 100%.
src/pymath/functions.py
def multiply(a: int, b: int) -> int:
f"Multiplying {a} by {b}")
logger.info(
if a == 0 or b == 0:
return 0
= 0
result for _ in range(abs(b)):
= add(result, a)
result if abs(result) > 1_000_000:
"Overflow detected")
logger.error(raise OverflowError("Result too large")
return result if b > 0 else -result
def calculate_expression(a: int, b: int, op: str):
f"Calculating: {a} {op} {b}")
logger.info(if op == "+":
return add(a, b)
elif op == "-":
return substract(a, b)
elif op == "*":
return multiply(a, b)
elif op == "/":
return divide(a, b)
else:
f"Unsupported operation: {op}")
logger.error(raise ValueError(f"Unsupported operation '{op}'")
def safe_divide(a: int, b: int, default=None) -> float:
try:
return divide(a, b)
except ValueError as e:
f"Attempted to divide {a} by zero")
logger.warning(if default is not None:
return default
raise
def average(numbers: list[int]) -> float:
if not numbers:
"Empty list provided to average()")
logger.warning(raise ValueError("Cannot compute average of empty list")
if not all(isinstance(n, int) for n in numbers):
"Non-integer value in average input")
logger.error(raise TypeError("All elements must be integers")
= 0
total for n in numbers:
= add(total, n)
total
return divide(total, len(numbers))
Anexo. Uso de la terminal
El fichero .env
y la configuración de VS Code son útiles para que el editor reconozca las rutas de los módulos. Sin embargo, si prefieres ejecutar los tests desde la terminal deberás establecer la variable de entorno PYTHONPATH
manualmente. Puedes hacerlo de la siguiente manera:
# Linux / Mac
export PYTHONPATH=src
# Windows
set PYTHONPATH=src
Una vez establecida la variable de entorno, puedes ejecutar los tests desde la terminal con el siguiente comando:
python -m unittest discover -s tests
Esto ejecutará todos los tests en la carpeta tests
y mostrará los resultados en la terminal.
Para ejecutar los tests con cobertura, puedes usar el siguiente comando:
coverage run -m unittest discover -s tests
coverage report -m
Esto ejecutará los tests y generará un informe de cobertura en la terminal. Si deseas generar un informe en formato HTML, puedes usar el siguiente comando:
coverage html
Esto generará un informe en formato HTML en la carpeta htmlcov
, que podrás abrir en tu navegador para ver la cobertura de código de manera más visual.