Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_desde_cero_con_python:pruebas_y_fiabilidad

Pruebas y fiabilidad

Pruebas unitarias, aislamiento, cobertura e integración continua.

Este módulo forma parte del curso de programación desde cero con Python

Módulo 17. Pruebas y fiabilidad

En el Módulo 10 aprendiste a diseñar casos de prueba y a usar unittest para comprobar que tus funciones hacían lo que debían. Pero las pruebas no son un trámite que se hace una vez y se olvida: son la red de seguridad que te permite cambiar el código sin miedo, detectar regresiones y confiar en que un programa sigue funcionando después de una refactorización (Módulo 16) o de añadir una funcionalidad nueva. Este módulo profundiza en las pruebas automatizadas, en los marcos que Python ofrece (unittest y pytest), en el aislamiento de dependencias con mocks, en la medición de cobertura y en la integración de las pruebas en el ciclo de desarrollo diario.

El objetivo no es que escribas cientos de pruebas, sino que sepas qué probar, cómo aislar lo que pruebas y cómo interpretar los resultados para tomar decisiones informadas sobre la fiabilidad de tu código.


1. Título y alcance

Título: Pruebas y fiabilidad: pruebas unitarias, aislamiento, cobertura e integración continua.

Alcance: Distinguir pruebas manuales de automatizadas; escribir pruebas unitarias con unittest y pytest; parametrizar casos de prueba; usar fixtures para preparar y limpiar el entorno; aislar dependencias externas con unittest.mock; medir la cobertura con coverage.py y pytest-cov; interpretar la cobertura con criterio; y integrar las pruebas en el flujo de desarrollo (ejecución frecuente, regresión, integración continua). Se presupone todo el curso hasta el Módulo 16, en particular funciones (Módulo 9), excepciones (Módulo 11), módulos (Módulo 12) y refactorización (Módulo 16). No se introducen herramientas de CI/CD específicas (se mencionan conceptualmente), ni pruebas de interfaz gráfica o de carga.


2. Objetivos de aprendizaje

Al terminar este módulo serás capaz de:

1. Explicar las limitaciones de las pruebas manuales y las ventajas de las automatizadas. 2. Escribir pruebas unitarias con unittest usando TestCase, assertEqual, assertRaises y assertLogs. 3. Escribir pruebas con pytest usando funciones, assert y el descubrimiento automático. 4. Parametrizar pruebas para cubrir múltiples casos con poco código. 5. Usar fixtures de pytest para preparar datos y recursos, y entender su ciclo de vida. 6. Aislar dependencias externas (APIs, bases de datos, archivos) con unittest.mock y monkeypatch. 7. Medir la cobertura de pruebas con coverage.py o pytest-cov e interpretar el informe. 8. Distinguir entre cobertura de líneas y cobertura de ramas, y evitar la falsa sensación de seguridad. 9. Diseñar pruebas de regresión para errores ya corregidos. 10. Integrar la ejecución de pruebas en el flujo de trabajo diario y en un pipeline de integración continua. 11. Conocer las novedades de Python 3.15 y pytest 9.2 relacionadas con las pruebas.


3. Conocimientos previos

- Del Módulo 9: Funciones, return, docstrings y funciones puras. - Del Módulo 10: Diseño de casos de prueba (normales, límite, inválidos) y primeras pruebas con unittest. - Del Módulo 11: Excepciones, try/except y logging (necesario para assertRaises y assertLogs). - Del Módulo 12: Módulos e importaciones (necesario para organizar pruebas en archivos separados). - Del Módulo 16: Refactorización y Ruff (contexto para integrar pruebas en el flujo de calidad). - Ningún conocimiento de pytest, fixtures, mocks ni cobertura.


4. Introducción motivadora: el programa que “funcionaba ayer”

Imagina que has terminado un programa, lo has probado manualmente y todo funciona. Una semana después, cambias una función para añadir una mejora. Vuelves a probar el caso principal: funciona. Pero un caso límite que antes funcionaba ahora falla, y no lo descubres hasta que un usuario se queja. Ese es el problema de las pruebas manuales: no se ejecutan todas cada vez, y olvidan los casos que no se probaron explícitamente.

Las pruebas automatizadas resuelven esto: escribes una vez los casos, y un comando los ejecuta todos en segundos. Si un cambio rompe algo, lo sabes de inmediato, no semanas después. La fiabilidad no consiste en “probar mucho”, sino en probar de forma sistemática y repetible. Este módulo te enseña a construir esa red de seguridad.


5. Explicación paso a paso

5.1. Pruebas manuales y automatizadas

Una prueba manual es la que ejecuta una persona: abre el programa, introduce datos, observa el resultado y decide si es correcto. Es útil para explorar, pero tiene tres problemas: es lenta, no es repetible con fiabilidad (la persona puede olvidar casos) y no escala.

Una prueba automatizada es un programa que ejecuta otro programa (o una función) con datos conocidos y compara el resultado con el esperado. Se ejecuta con un comando, en segundos, y siempre cubre los mismos casos.

[BUENA PRÁCTICA] Automatiza las pruebas de la lógica (funciones puras, cálculos, validaciones). Las pruebas manuales se reservan para la experiencia de usuario (interfaz, flujo de pantallas), que es más difícil de automatizar y cambia con más frecuencia.

5.2. Pruebas unitarias con `unittest`

unittest es el marco de pruebas de la biblioteca estándar. Organiza las pruebas en clases que heredan de unittest.TestCase, y cada método cuyo nombre empiece por test_ es una prueba.

import unittest
from calculadora import sumar, dividir
 
class TestCalculadora(unittest.TestCase):
 
    def test_sumar_positivos(self):
        self.assertEqual(sumar(2, 3), 5)
 
    def test_dividir_entre_cero(self):
        with self.assertRaises(ZeroDivisionError):
            dividir(10, 0)
 
    def test_dividir_normal(self):
        self.assertAlmostEqual(dividir(10, 4), 2.5)

Aserciones más usadas:

MétodoComprueba
assertEqual(a, b)a == b
assertNotEqual(a, b)a != b
assertTrue(x) / assertFalse(x)Valor booleano
assertIsNone(x) / assertIsNotNone(x)Identidad con None
assertIn(a, b)Pertenencia
assertAlmostEqual(a, b)Igualdad con tolerancia (para float)
assertRaises(Excepcion)Que se lance una excepción
assertLogs(logger, level)Que se registre un mensaje de log
[PYTHON] En Python 3.15, unittest.TestCase.assertLogs() acepta un parámetro formatter para controlar el formato de los mensajes capturados, lo que facilita verificar logs con formato personalizado. Además, assertWarns() y assertWarnsRegex() ya no silencian los warnings que no coinciden con la categoría o expresión regular especificada, y admiten gestores de contexto anidados. Esto puede hacer que tus pruebas muestren warnings que antes se ocultaban; no es un fallo, sino una mayor transparencia.

Ejecutar las pruebas:

- Windows: py -m unittest test_calculadora.py - Linux: python3 -m unittest test_calculadora.py

5.3. Pruebas con `pytest`

pytest es un marco de pruebas de terceros que se ha convertido en el estándar de facto por su sintaxis más simple. No requiere clases: basta con funciones cuyo nombre empiece por test_, y usa assert directamente.

# archivo: test_calculadora_pytest.py
from calculadora import sumar, dividir
 
def test_sumar_positivos():
    assert sumar(2, 3) == 5
 
def test_dividir_entre_cero():
    with pytest.raises(ZeroDivisionError):
        dividir(10, 0)

Instalación: pip install pytest

Ejecución: pytest (descubre automáticamente los archivos test_*.py y *_test.py).

[PYTHON] pytest no forma parte de la biblioteca estándar, pero es la herramienta recomendada por la comunidad para proyectos nuevos. Su salida es más legible, su sistema de fixtures es más potente y su ecosistema de plugins es enorme. unittest sigue siendo útil para código que no puede instalar dependencias o para proyectos heredados.

5.4. Parametrización de pruebas

Si tienes varios casos con la misma lógica, no repitas la función de prueba. pytest ofrece @pytest.mark.parametrize para ejecutar la misma prueba con distintos datos:

import pytest
 
@pytest.mark.parametrize("entrada, esperado", [
    (2, 4),
    (3, 9),
    (0, 0),
    (-2, 4),
])
def test_cuadrado(entrada, esperado):
    assert entrada ** 2 == esperado
[PYTHON] En pytest 9.2.0 (octubre de 2026), los argumentos indirect, ids y scope de @pytest.mark.parametrize son solo por palabra clave. Pasarlos por posición, que antes producía errores confusos, ahora lanza un TypeError claro. Es un cambio de compatibilidad que conviene conocer si actualizas proyectos antiguos.

5.5. *Fixtures*: preparar y limpiar el entorno

Una fixture es un recurso que se prepara antes de una prueba y se limpia después: una conexión a una base de datos de prueba, un archivo temporal, un objeto configurado. pytest las implementa con funciones decoradas con @pytest.fixture:

import pytest
 
@pytest.fixture
def lista_vacia():
    return []
 
@pytest.fixture
def lista_con_datos():
    return [1, 2, 3]
 
def test_agregar(lista_vacia):
    lista_vacia.append(4)
    assert len(lista_vacia) == 1
 
def test_sumar(lista_con_datos):
    assert sum(lista_con_datos) == 6

Las fixtures se inyectan como parámetros en las funciones de prueba. Su ámbito (scope) puede ser function (por defecto, se ejecuta para cada prueba), module, class o session.

[BUENA PRÁCTICA] Usa fixtures para recursos que deben crearse y destruirse de forma controlada. Si una prueba modifica un recurso compartido, usa scope="function" para evitar que los cambios se filtren a otras pruebas.

5.6. Aislamiento de dependencias con *mocks*

Una prueba unitaria debe probar una unidad de código de forma aislada. Si tu función llama a una API externa, lee un archivo o consulta una base de datos, la prueba se vuelve lenta, frágil y dependiente del entorno. La solución es sustituir esas dependencias por dobles de prueba (mocks).

unittest.mock ofrece Mock y patch para reemplazar objetos durante una prueba:

from unittest.mock import patch
from mi_modulo import obtener_precio
 
def test_obtener_precio_sin_red():
    with patch("mi_modulo.consultar_api") as mock_api:
        mock_api.return_value = 100
        assert obtener_precio("producto") == 100
        mock_api.assert_called_once_with("producto")

patch reemplaza temporalmente consultar_api por un mock. La prueba no hace ninguna llamada real; verifica que la función se comportó como se esperaba y que se llamó con los argumentos correctos.

[PYTHON] pytest ofrece monkeypatch como alternativa más ligera para modificar atributos, diccionarios o variables de entorno durante una prueba. Para dependencias externas complejas, unittest.mock es más potente y permite verificar llamadas, argumentos y valores de retorno.
[BUENA PRÁCTICA] No mockees todo. Si tu función es pura (Módulo 9), no necesita mocks. Los mocks son para aislar fronteras del sistema: APIs, bases de datos, archivos, relojes, aleatoriedad. Abusar de ellos hace que las pruebas verifiquen la implementación en lugar del comportamiento.

5.7. Cobertura: medir qué se prueba

La cobertura (coverage) mide qué porcentaje de tu código se ejecuta durante las pruebas. No garantiza que las pruebas sean buenas, pero revela zonas no probadas que podrían contener errores.

Con coverage.py:

pip install coverage
coverage run -m pytest
coverage report -m

El informe muestra líneas cubiertas, no cubiertas y el porcentaje total. -m añade las líneas exactas que no se ejecutaron.

Con pytest-cov (plugin):

pip install pytest-cov
pytest --cov=mi_paquete --cov-report=term-missing
[PYTHON] coverage.py 7.15.4 (agosto de 2026) soporta Python 3.10 hasta 3.15, incluida la versión free-threading. Se puede configurar en pyproject.toml bajo [tool.coverage]. El plugin pytest-cov integra la medición directamente en la ejecución de pytest y es la forma más cómoda de obtener cobertura en proyectos que ya usan este marco.

Cobertura de líneas vs. cobertura de ramas: la cobertura de líneas cuenta si una línea se ejecutó alguna vez. La cobertura de ramas comprueba si ambos caminos de una condición (if/else) se han probado. Un 100% de líneas no implica un 100% de ramas: una línea dentro de un if puede ejecutarse, pero la rama else puede no haberse probado nunca. Activa --cov-branch para medir ramas.

[BUENA PRÁCTICA] No persigas el 100% de cobertura como un fin. Un 80-90% bien elegido suele ser más valioso que un 100% obsesivo que prueba líneas triviales. Prioriza cubrir la lógica de negocio, los casos límite y los manejadores de errores. La cobertura es una brújula, no una medalla.

5.8. Pruebas de regresión

Una prueba de regresión es una prueba que se añade después de corregir un error, para asegurar que ese error no vuelva a aparecer. Es una de las prácticas más rentables: cada bug corregido deja una prueba que lo vigila para siempre.

def test_regresion_edad_negativa():
    """Regresión: la validación de edad aceptaba valores negativos."""
    with pytest.raises(ValueError):
        validar_edad(-5)
[BUENA PRÁCTICA] Cuando corrijas un error, escribe primero la prueba que lo reproduce (y que falla), y luego la corrección. Así garantizas que la prueba realmente detecta el problema y que la corrección lo resuelve.

5.9. Integración de las pruebas en el desarrollo

Las pruebas no son una fase final: se ejecutan constantemente.

- Durante el desarrollo: ejecuta las pruebas de la función que estás modificando después de cada cambio pequeño. - Antes de un commit: ejecuta toda la suite para asegurarte de que no has roto nada. - En integración continua (CI): un servidor ejecuta las pruebas automáticamente en cada push. Si fallan, el cambio no se integra.

[BUENA PRÁCTICA] Integra las pruebas en tu editor (VS Code, PyCharm) para ejecutarlas con un atajo. Cuanto menor sea la fricción, más probable es que las ejecutes. Y configura Ruff (Módulo 16) junto con las pruebas: el linter detecta problemas de estilo y las pruebas detectan problemas de comportamiento.

6. Ejemplo completo comentado: pruebas de una función con dependencia externa

Código bajo prueba (clima.py):

import requests
 
def obtener_temperatura(ciudad):
    """Devuelve la temperatura actual de una ciudad usando una API externa."""
    respuesta = requests.get(f"https://api.clima.example/{ciudad}")
    respuesta.raise_for_status()
    return respuesta.json()["temperatura"]

Prueba con mock (test_clima.py):

from unittest.mock import patch
import pytest
from clima import obtener_temperatura
 
def test_obtener_temperatura_sin_red():
    with patch("clima.requests.get") as mock_get:
        mock_get.return_value.json.return_value = {"temperatura": 22}
        mock_get.return_value.raise_for_status.return_value = None
 
        assert obtener_temperatura("Sevilla") == 22
        mock_get.assert_called_once_with("https://api.clima.example/Sevilla")
 
def test_obtener_temperatura_error_http():
    with patch("clima.requests.get") as mock_get:
        mock_get.return_value.raise_for_status.side_effect = requests.HTTPError
 
        with pytest.raises(requests.HTTPError):
            obtener_temperatura("Sevilla")

Seguimiento: patch sustituye requests.get por un mock. En la primera prueba, el mock devuelve un JSON con la temperatura y se verifica que se llamó con la URL correcta. En la segunda, raise_for_status lanza una excepción, y la prueba comprueba que se propaga. No se hace ninguna llamada real a la red.


7. Seguimiento de la ejecución de una prueba

Cuando ejecutas pytest:

1. Descubre los archivos test_*.py o *_test.py en el directorio actual. 2. Recoge las funciones cuyo nombre empieza por test_. 3. Para cada prueba, prepara las fixtures necesarias. 4. Ejecuta la función. Si un assert falla, la prueba se marca como fallida y se muestra el valor esperado y el obtenido. 5. Limpia las fixtures (si tienen código de limpieza). 6. Al final, muestra un resumen: pruebas pasadas, fallidas, errores y omitidas.

Con unittest, el proceso es similar, pero las pruebas se organizan en clases y se ejecutan con unittest.main() o el descubrimiento automático.


8. Errores habituales

ErrorCausaCómo evitarlo
Probar solo el caso felizOlvidar límites y errores.Diseña casos normales, límite e inválidos (Módulo 10).
Pruebas que dependen del ordenEstado compartido entre pruebas.Usa fixtures con scope="function" y limpia el estado.
Pruebas lentas por llamadas realesNo aislar APIs o bases de datos.Usa mock o monkeypatch para las fronteras del sistema.
Perseguir el 100% de coberturaMétrica como fin, no como medio.Prioriza lógica de negocio y casos límite.
No ejecutar las pruebas con frecuenciaFricción o pereza.Intégralas en el editor y en el flujo de commit.
Pruebas frágiles que fallan por detalles irrelevantesVerificar implementación en lugar de comportamiento.Prueba entradas y salidas, no pasos internos.
No añadir pruebas de regresiónOlvidar el bug corregido.Cada corrección genera una prueba.
[ERROR HABITUAL] Una prueba que pasa siempre, incluso cuando el código está mal, es peor que no tener prueba: da una falsa sensación de seguridad. Asegúrate de que las pruebas pueden fallar: introduce un error deliberado y comprueba que la prueba lo detecta.

9. Particularidades de Python frente a otros lenguajes

- unittest en la biblioteca estándar: no necesitas instalar nada para empezar. En Java, JUnit es externo; en C, no hay marco estándar. - pytest como estándar de facto: su sintaxis con assert y funciones es más ligera que JUnit o NUnit. La comunidad lo ha adoptado mayoritariamente para proyectos nuevos. - unittest.mock: el módulo de mocks está en la biblioteca estándar desde Python 3.3. En otros lenguajes, se requieren bibliotecas externas (Mockito en Java, Moq en C#). - Fixtures con inyección por parámetro: el sistema de fixtures de pytest es más expresivo que los setUp/tearDown de unittest o JUnit, porque permite composición y ámbitos flexibles. - Cobertura con coverage.py: es la herramienta de referencia, integrada con pytest mediante pytest-cov. En otros ecosistemas, las herramientas de cobertura suelen estar ligadas al marco de pruebas.


10. Buenas prácticas

Incorrecto (prueba dependiente de la red y del orden):

def test_obtener_datos():
    datos = obtener_datos_de_api()  # Llamada real
    assert len(datos) > 0

Mejor (aislada y determinista):

def test_obtener_datos(mocker):
    mocker.patch("mi_modulo.obtener_datos_de_api", return_value=[1, 2, 3])
    datos = obtener_datos()
    assert datos == [1, 2, 3]
[BUENA PRÁCTICA] Escribe pruebas rápidas, aisladas y deterministas. Una prueba que depende de la red, del tiempo o del orden de ejecución es una prueba frágil que acabará ignorándose. Y recuerda: la cobertura es una herramienta de diagnóstico, no un objetivo en sí mismo.

11. Windows y Linux

- Ejecutar unittest: py -m unittest discover (Windows) o python3 -m unittest discover (Linux). - Ejecutar pytest: pytest funciona igual en ambos sistemas. Si el comando no se encuentra, usa py -m pytest o python3 -m pytest. - Cobertura: coverage run -m pytest y coverage report -m son idénticos en ambos sistemas. - Rutas en pruebas: usa pathlib y tmp_path (fixture de pytest) para crear archivos temporales de forma portable. Evita rutas absolutas codificadas. - CI: las plataformas de integración continua (GitHub Actions, GitLab CI) ejecutan las pruebas en contenedores Linux por defecto; si necesitas Windows, configura una matriz de sistemas operativos.

[WINDOWS / LINUX] Si una prueba pasa en Linux pero falla en Windows, revisa las diferencias de rutas, finales de línea (\n vs. \r\n) y mayúsculas en nombres de archivo. pathlib y las fixtures de pytest ayudan a evitarlo.

12. Ejercicios guiados

Ejercicio guiado 1: Primera prueba con unittest.

Escribe una función es_mayor_de_edad(edad) y una prueba con unittest que compruebe los casos 17, 18 y 25. Ejecuta la prueba y comprueba que pasa.

Ejercicio guiado 2: Parametrización con pytest.

Escribe una función clasificar_nota(nota) que devuelva "Suspenso", "Aprobado", "Notable" o "Sobresaliente". Escribe una prueba parametrizada que cubra al menos seis casos, incluidos los límites (4.9, 5.0, 6.9, 7.0, 8.9, 9.0).

Ejercicio guiado 3: Mock de una dependencia.

Escribe una función obtener_usuario(id) que llame a consultar_api(id). Escribe una prueba que sustituya consultar_api con un mock y verifique que obtener_usuario devuelve el diccionario esperado y que la API se llamó con el id correcto.


13. Ejercicios autónomos

1. Predicción: ¿Qué muestra pytest si una prueba tiene assert 2 + 2 == 5? ¿Y si tiene assert 2 + 2 == 4? Ejecútalo y observa la salida. 2. Cobertura: Toma una función con varias ramas (if/elif/else). Escribe pruebas que cubran solo una rama y mide la cobertura. Luego añade pruebas para las demás y compara. 3. Fixtures: Crea una fixture que proporcione una lista con tres elementos y úsala en dos pruebas distintas. Comprueba que cada prueba recibe una lista independiente. 4. Regresión: Introduce un error deliberado en una función (por ejemplo, edad > 18 en lugar de edad >= 18). Escribe una prueba que lo detecte. Corrige el error y comprueba que la prueba pasa. 5. Reto opcional: Investiga el parámetro formatter de assertLogs() en Python 3.15 y escribe una prueba que verifique que una función registra un mensaje con un formato concreto.


14. Soluciones razonadas

Solución al ejercicio guiado 1:

import unittest
 
def es_mayor_de_edad(edad):
    return edad >= 18
 
class TestMayoriaEdad(unittest.TestCase):
    def test_menor(self):
        self.assertFalse(es_mayor_de_edad(17))
 
    def test_limite(self):
        self.assertTrue(es_mayor_de_edad(18))
 
    def test_mayor(self):
        self.assertTrue(es_mayor_de_edad(25))

Solución al ejercicio guiado 2:

import pytest
 
def clasificar_nota(nota):
    if nota < 5:
        return "Suspenso"
    elif nota < 7:
        return "Aprobado"
    elif nota < 9:
        return "Notable"
    return "Sobresaliente"
 
@pytest.mark.parametrize("nota, esperado", [
    (4.9, "Suspenso"),
    (5.0, "Aprobado"),
    (6.9, "Aprobado"),
    (7.0, "Notable"),
    (8.9, "Notable"),
    (9.0, "Sobresaliente"),
])
def test_clasificar(nota, esperado):
    assert clasificar_nota(nota) == esperado

Solución al ejercicio guiado 3:

from unittest.mock import patch
from usuarios import obtener_usuario
 
def test_obtener_usuario():
    with patch("usuarios.consultar_api") as mock_api:
        mock_api.return_value = {"id": 1, "nombre": "Ana"}
        assert obtener_usuario(1) == {"id": 1, "nombre": "Ana"}
        mock_api.assert_called_once_with(1)

Solución al ejercicio 1 (predicción): La primera prueba falla y pytest muestra assert 4 == 5 con los valores esperado y obtenido. La segunda pasa y se marca como PASSED.


15. Resumen

- Las pruebas automatizadas son repetibles, rápidas y no dependen de la memoria del programador. - unittest es el marco estándar; pytest es el estándar de facto por su sintaxis simple y su sistema de fixtures. - La parametrización evita duplicar pruebas para casos similares. - Las fixtures preparan y limpian recursos; los mocks aíslan dependencias externas. - La cobertura mide qué código se ejecuta; es una brújula, no una medalla. Distingue líneas de ramas. - Las pruebas de regresión protegen contra la reaparición de errores ya corregidos. - Las pruebas se integran en el desarrollo diario y en la integración continua. - Python 3.15 mejora assertLogs (formatter) y assertWarns (no silencia warnings no coincidentes); pytest 9.2 hace que parametrize sea keyword-only en sus argumentos avanzados.


16. Autoevaluación

1. ¿Por qué una prueba que llama a una API real es frágil? ¿Cómo la aislarías? 2. ¿Qué diferencia hay entre cobertura de líneas y cobertura de ramas? ¿Cuál es más informativa? 3. ¿Cuándo usarías unittest en lugar de pytest? 4. ¿Qué es una prueba de regresión y por qué es rentable? 5. Reto: Sin ejecutar, predice qué ocurre si una prueba tiene dos assert y el primero falla. ¿Se ejecuta el segundo? Justifica la respuesta.


17. Fuentes oficiales

- Documentación oficial de unittest: https://docs.python.org/3.15/library/unittest.html - Documentación oficial de unittest.mock: https://docs.python.org/3.15/library/unittest.mock.html - Novedades de Python 3.15 (unittest): https://docs.python.org/3/whatsnew/3.15.html - Documentación de pytest: https://docs.pytest.org/ - Changelog de pytest (9.2.0, octubre 2026): https://docs.pytest.org/en/stable/changelog.html - Documentación de coverage.py: https://coverage.readthedocs.io/ - Documentación de pytest-cov: https://pytest-cov.readthedocs.io/ - Guía de configuración de cobertura en pyproject.toml: https://coverage.readthedocs.io/en/7.15.4/config.html

informatica/programacion/cursos/programacion_desde_cero_con_python/pruebas_y_fiabilidad.txt · Última modificación: por tempwin