Explicación de la cobertura del código: Cobertura de línea versus sucursal y lo que realmente significa 100%
La cobertura le indica qué código ejecutaron sus pruebas. No le dice si las pruebas comprobaron algo. Medimos un módulo pequeño de tres maneras para mostrar la diferencia.
- Cobertura de código mide la cantidad de código que se ejecuta durante las pruebas.
- La cobertura de sucursales es más estricta que la cobertura de línea y más útil.
- En nuestra prueba, una suite sin afirmaciones aún alcanzó una cobertura del 100 %.
- Utilice la cobertura para encontrar código no probado, no como prueba de calidad.
Un equipo informa con orgullo una cobertura de código del 95 % y luego envía un error en una línea que estaba “cubierta”. Esto no es una contradicción. La cobertura responde a una pregunta concreta: ¿las pruebas ejecutaron este código? Si verificaron el resultado es una cuestión completamente diferente.
Usamos Python con cobertura.py 7.17 y pytest 9.2. Cada número e informe a continuación es de nuestra propia ejecución. Las ideas se aplican en cualquier idioma.
¿Qué es la cobertura del código?
La cobertura de código es el porcentaje de su código que se ejecuta mientras se ejecutan sus pruebas automatizadas. Una herramienta registra qué líneas y, opcionalmente, qué ramas se ejecutaron y luego informa lo que se omitió.
Es más útil al revés. La baja cobertura en una zona te indica, con certeza, que las pruebas no la están ejerciendo. Una cobertura alta dice mucho menos.
Tipos de cobertura
| Tipo | Pregunta que responde |
|---|---|
| Función | ¿Se llamó a cada función al menos una vez? |
| Línea (declaración) | ¿Se ejecutó cada línea al menos una vez? |
| Sucursal | ¿Cada if fue en ambos sentidos, verdadero y falso? |
| Condición | ¿Cada parte de una condición combinada (a and b) tomó ambos valores? |
| MC/DC | ¿Cada condición cambió independientemente el resultado? Utilizado en software crítico para la seguridad. |
Cada nivel es más estricto que el superior. La mayoría de los equipos rastrean la cobertura de líneas y sucursales. La cobertura de condiciones y MC/DC aparecen en los estándares de aviación y automoción, donde el soporte de herramientas es especializado.
El código que medimos
def shipping_fee(total, is_member, express=False):
if total < 0:
raise ValueError("total cannot be negative")
fee = 0 if total >= 1000 else 50
if is_member:
fee = max(fee - 25, 0)
if express:
fee += 100
return fee
Nuestro primer archivo de prueba tuvo dos pruebas:
def test_regular_order():
assert shipping_fee(500, False) == 50
def test_member_express():
assert shipping_fee(500, True, express=True) == 125
Lo ejecutamos con la medición de rama activada:
coverage run --branch -m pytest test_shipping.py
coverage report -m
leyendo el informe
Name Stmts Miss Branch BrPart Cover Missing
---------------------------------------------------------
shipping.py 9 1 6 1 87% 3
- Stmts 9, Miss 1: nueve declaraciones ejecutables, una nunca se ejecutó.
- Rama 6, BrParte 1: tres
ifdecisiones dan seis resultados posibles. Una decisión sólo fue en un sentido. - Falta 3: línea 3, la
raise ValueError, nunca se ejecutó. Ninguna prueba pasó un total negativo.
La solución es una prueba de la ruta del error. También agregamos una prueba para un pedido grande:
def test_free_over_1000():
assert shipping_fee(1000, False) == 0
def test_negative_total():
with pytest.raises(ValueError):
shipping_fee(-1, False)
Con ambos archivos, el informe mostró 100% y no faltaron líneas ni ramas parciales. Las rutas de error son los hallazgos clásicos de cobertura de brechas, razón por la cual las pruebas de entradas no válidas, diseñadas con técnicas como partición de equivalencia, combinan tan bien con ellas.
Lo que escondió el informe
Mire el primer informe nuevamente. Dijo que sólo faltaba la línea 3. Pero las dos primeras pruebas utilizaron un total de 500, por lo que la regla de envío gratuito para pedidos de 1000 o más nunca se probó y el informe no la marcó.
La razón es que fee = 0 if total >= 1000 else 50 se encuentra en una línea. cover.py mide las ramas entre líneas, por lo que una expresión condicional dentro de una sola línea cuenta como una declaración ejecutada. Si esa regla tuviera un error, como > en lugar de >=, la primera suite aún habría mostrado un 87% sin ninguna pista. Las pruebas del límite en sí, como se describe en análisis del valor del límite, son las que lo detectan.
Cobertura del 100% sin controles
A continuación, escribimos una prueba deliberadamente deficiente que llama a todos los caminos pero no afirma nada:
def test_runs_everything_but_checks_nothing():
shipping_fee(500, True, express=True)
shipping_fee(1000, False)
shipping_fee(500, False)
try:
shipping_fee(-1, False)
except ValueError:
pass
shipping.py 9 0 6 0 100%
Alcanzó el 100% de cobertura de líneas y sucursales. La función podría devolver cualquier número y esta prueba aún pasaría. Éste es el límite central de la cobertura: mide la ejecución, no la verificación.
Consejo: Al revisar una solicitud de extracción, lea las afirmaciones, no solo el cambio de cobertura. Una prueba que añade cobertura sin una afirmación significativa sólo añade una confianza falsa.
Para medir si las pruebas realmente detectan errores, utilice pruebas de mutación. Una herramienta como mutmut para Python, PIT para Java o Stryker para JavaScript realiza pequeños cambios en su código, como cambiar >= a >, y verifica que una prueba falle. Nuestra prueba de no afirmación permitiría que todas las mutaciones sobrevivieran.
¿Qué objetivo establecer?
No existe un número universalmente correcto. Orientación práctica:
- No persigas el 100%. El último porcentaje a menudo significa probar código trivial o escribir pruebas que solo tocan líneas.
- Muchos equipos apuntan a aproximadamente entre el 70 y el 85 % en general, con expectativas más altas para la lógica empresarial central.
- Observe la tendencia, no el número. La cobertura que cae en cada lanzamiento es una señal de advertencia.
- Requerir cobertura de código nuevo en las solicitudes de extracción en lugar de forzar la publicación del código antiguo de una sola vez.
- Excluye lo que no importa, como el código generado, con reglas claras.
Se aplica la ley de Goodhart: cuando una medida se convierte en un objetivo, deja de ser una buena medida. Los objetivos difíciles fomentan pruebas como nuestro ejemplo de no afirmación.
Herramientas por idioma
| Idioma | Herramientas comunes |
|---|---|
| pitón | cobertura.py, pytest-cov |
| JavaScript/Mecanografiado | Estambul (Nueva York), integrada en Jest y Vitest |
| Java | jacoco |
| .NET | colcha |
| ir | ir a prueba -cubrir |
| Informes | Codecov, SonarQube, Monos |
Cobertura en CI
Ejecute la cobertura como parte de su canal de CI y publique el informe para que los revisores puedan ver qué líneas nuevas no se han probado. Con cobertura.py, coverage report --fail-under=80 falla la compilación por debajo de un umbral. Mantenga cualquier umbral modesto y combínelo con una regla sobre código nuevo, de modo que un único archivo heredado de gran tamaño no bloquee todos los cambios.
Más allá de la cobertura: mejores preguntas para hacer
- ¿Las pruebas cubren las partes del código riesgosas y que se modifican con frecuencia?
- ¿Se ponen a prueba los límites y los caminos del error, no sólo el camino feliz?
- ¿Fallaría una prueba si esta línea fuera incorrecta?
- ¿Las pruebas son lo suficientemente rápidas y estables como para ejecutarlas en cada cambio?
Estos pertenecen a cualquier estrategia de prueba. La cobertura ayuda a responder solo la primera.
Excluyendo código y leyendo informes HTML
No vale la pena probar algunos códigos directamente, como los asistentes de depuración o un bloque que solo se ejecuta cuando se inicia un script manualmente. cover.py te permite excluir líneas con un comentario:
if __name__ == "__main__": # pragma: no cover
main()
También puede establecer exclusiones y las carpetas de origen a medir en la configuración, por ejemplo en pyproject.toml:
[tool.coverage.run]
branch = true
source = ["shop"]
[tool.coverage.report]
show_missing = true
exclude_also = ["if TYPE_CHECKING:"]
Utilice las exclusiones con moderación y revíselas como si fueran código. Cada uno es una decisión que algunas líneas nunca serán revisadas. Excluir un controlador de errores difícil de probar solo para aumentar el número frustra el propósito.
Para el trabajo diario, el informe HTML es más útil que la tabla de terminal. Ejecute coverage html y abra htmlcov/index.html. Cada archivo se muestra con las líneas ejecutadas en verde, las líneas perdidas en rojo y las ramas parciales en amarillo, para que pueda ver de un vistazo cuál if nunca tomó su otra ruta. Muchos editores también pueden mostrar cobertura en el margen, lo que hace que los espacios sean visibles mientras se escribe el código y no después.
Cobertura antes de refactorizar
La cobertura también es una verificación de seguridad antes de refactorizar. Si planea reestructurar un módulo, consulte primero su informe. Las áreas rojas no tienen pruebas para advertirle si el comportamiento cambia, así que agregue pruebas de caracterización que capturen el comportamiento actual antes de comenzar. Utilizada de esta manera, la cobertura deja de ser un objetivo y se convierte en una práctica herramienta de planificación.
El mismo informe ayuda cuando un error pasa a producción. Encuentre la línea donde vivía el error y verifique si alguna prueba lo ejecutó. Si no, has encontrado una brecha que vale la pena cerrar. Si se ejecutó una prueba, el problema es una afirmación débil, que es exactamente la situación que mostró nuestro ejemplo sin afirmación. Ambas respuestas le dicen cómo mejorar la suite.
Conclusión
La cobertura del código es una linterna, no una calificación. Utilice la cobertura de ramas para encontrar código que ninguna prueba toque, especialmente el manejo de errores, y luego escriba pruebas que hagan afirmaciones reales. Si recuerda un resultado de nuestra ejecución, dígalo: se alcanzó una cobertura del 100% mediante una prueba que no comprobó nada.
Preguntas frecuentes
¿Qué es la cobertura del código en las pruebas?
Es el porcentaje de código ejecutado mientras se ejecutan pruebas automatizadas, medido por líneas, ramas o condiciones. Muestra qué pruebas tocaron, no si lo verificaron.
¿Cuál es la diferencia entre cobertura de línea y sucursal?
La cobertura de línea verifica que cada línea se haya ejecutado. La cobertura de sucursales verifica que cada decisión haya sido en ambos sentidos. La cobertura de sucursales encuentra caminos no probados que la cobertura de línea pasa por alto.
¿Es buena una cobertura de código del 100%?
No por sí solo. En nuestra prueba, una suite sin afirmaciones alcanzó el 100%. Apunte a realizar pruebas significativas sobre código importante en lugar de un número perfecto.
¿Qué es un buen porcentaje de cobertura de código?
Muchos equipos apuntan a aproximadamente entre el 70 y el 85 % en general, con más atención a la lógica crítica. La tendencia y la calidad de las afirmaciones importan más que el número exacto.
¿En qué se diferencian las pruebas de mutación de la cobertura?
Las pruebas de mutación cambian ligeramente su código y verifican que una prueba falle. Mide si las pruebas detectan errores, algo que la cobertura no puede hacer.
sigue aprendiendo
¿Necesitas ayuda con pruebas, WordPress o SEO?
Ayudamos a los equipos a enviar mejor software y hacer crecer sus sitios. Cuéntenos en qué está trabajando y le sugeriremos un siguiente paso claro.
Más guías sobre testing, IA y desarrollo moderno
¿Te gustó este artículo? Elige un tema y salta directo a nuestros tutoriales más recientes.
Explicación de las alucinaciones de la IA: por qué los modelos de lenguaje inventan cosas y cómo reducirlas
Se explicaron las alucinaciones de la IA: qué son, por qué los modelos de lenguaje las producen, tipos comunes, cómo reducirlas con conexión a tierr…
Leer artículo →DevOpsExplicación de las variables de entorno: archivos .env, secretos y configuración bien hechos
Variables de entorno explicadas con ejemplos de Python que ejecutamos: leerlas y configurarlas, por qué cada valor es una cadena, archivos .env, her…
Leer artículo →Desarrollo de softwareExplicación de la limitación de velocidad de API: algoritmos, encabezados y manejo de errores 429
La limitación de la tasa de API se explica con una simulación que ejecutamos: depósito de tokens, ventana fija, ventana deslizante, 429 respuestas, …
Leer artículo →Automatización de pruebasBurlarse en Python: unittest.mock, patch y los errores que hacen que las pruebas mientan
Burlarse en Python con unittest.mock, probado: Mock y return_value, side_effect, parches donde se usa un nombre, especificación y autospec, fechas d…
Leer artículo →Pruebas de softwareExplicación de la cobertura del código: cobertura de línea versus sucursal y lo que realmente significa 100%
Cobertura del código explicada con una ejecución real de cobertura.py: cobertura de línea, rama y condición, lectura de un informe, por qué una cobe…
Leer artículo →Bases de datos y SQLDELETE vs TRUNCATE vs DROP en SQL: diferencias, reversión y cuándo usar cada uno
DELETE vs TRUNCATE vs DROP explicado con SQL probado: qué elimina cada uno, reversión, activadores, restablecimiento de identidad, velocidad y en qu…
Leer artículo →Bases de datos y SQLVistas SQL explicadas: cree, use y evite los errores, con ejemplos
Vistas SQL explicadas con consultas que ejecutamos: creación de una vista, resultados en vivo, ocultación de columnas, rendimiento, vistas actualiza…
Leer artículo →Desarrollo de softwareInterfaz TypeScript versus tipo: diferencias reales, probadas con el compilador
Interfaz TypeScript versus tipo, verificado con el compilador real: qué pueden hacer ambos, qué puede hacer solo uno, fusión de declaraciones, exten…
Leer artículo →Desarrollo de softwarePython *args y **kwargs explicados: funciones flexibles con ejemplos claros
Python *args y **kwargs explicados con el código que ejecutamos: empaquetado y desempaquetado, orden de parámetros, argumentos solo de palabras clav…
Leer artículo →Desarrollo de softwareFunciones Lambda de Python: sintaxis, usos reales y cuándo no utilizarlas
Funciones lambda de Python explicadas con el código que ejecutamos: sintaxis, clasificación con clave, mapa y filtro, cierres, trampa de enlace tard…
Leer artículo →Inteligencia artificial¿Qué son los agentes de IA? Cómo funcionan, con un bucle de agente simple que puedes ejecutar
¿Qué son los agentes de IA? Una guía sencilla sobre el bucle del agente, las herramientas, la memoria y las barreras de seguridad, con un pequeño bu…
Leer artículo →DevOpsMonitoreo versus observabilidad: la verdadera diferencia, con registros, métricas y seguimientos
Explicación del monitoreo versus observabilidad: fallas conocidas y desconocidas, registros, métricas y seguimientos, un recorrido por incidentes de…
Leer artículo →DevOpsDocker Volumes versus Bind Mounts: persistir los datos del contenedor de la manera correcta
Se explican los volúmenes de Docker frente a los montajes de enlace: por qué desaparecen los datos del contenedor, volúmenes con nombre, montajes de…
Leer artículo →Automatización de pruebasTDD vs BDD: diferencias clave, una función creada en ambos sentidos y cuándo usar cada una
TDD vs BDD explicado con una característica de descuento construida en ambos sentidos: refactor rojo-verde con el código que ejecutamos, escenarios …
Leer artículo →Desarrollo de softwareGit merge vs rebase: diferencias, ejemplos y cuándo usar cada uno
Git merge vs rebase explicado con salida real del log, una tabla comparativa, la regla de oro del rebase y un flujo de trabajo seguro para equipos.
Leer artículo →Desarrollo de softwareList comprehension en Python: sintaxis, ejemplos y cuándo evitarla
List comprehension en Python explicada: sintaxis, filtros, if-else, bucles anidados, tiempos medidos y cuándo es mejor un bucle for normal.
Leer artículo →