CommunityProgramación y desarrollogithub.com

luk036/dedup-cpp-tests

Encuentra y elimina pruebas unitarias C++ redundantes, luego completa las brechas de cobertura analizando informes de gcov y escribiendo pruebas dirigidas, todo verificado con comparación de cobertura antes/después.

¿Qué es dedup-cpp-tests?

Esta habilidad para agentes de IA permite optimizar un conjunto de pruebas unitarias C++ en dos modos principales: DEDUP y GAP-FILL. En el modo DEDUP, identifica archivos de prueba que cubren las mismas rutas de código con valores de entrada idénticos y los elimina, verificando que la cobertura no se reduzca mediante informes de gcov. En el modo GAP-FILL, analiza los informes de cobertura para detectar código no probado o insuficientemente probado (tanto en cabeceras como en archivos fuente compilados) y escribe pruebas unitarias dirigidas para llenar esos vacíos. El proceso incluye la definición de una línea base de cobertura, la eliminación de archivos redundantes, la reconstrucción del proyecto, la verificación de que la cobertura no haya cambiado, la identificación de brechas y la escritura de pruebas que aumenten la cobertura. La habilidad sigue reglas críticas: la cobertura es la verdad absoluta, se comparan las líneas exactas no cubiertas, no se eliminan pruebas basadas en propiedades, y se priorizan los archivos fuente compilados sobre las cabeceras. Todo el flujo de trabajo se basa en el uso de gcov con GCC o herramientas similares, y requiere reconstruir el proyecto desde cero para obtener métricas precisas. Esta herramienta es ideal para equipos que tienen un conjunto de pruebas que ha crecido orgánicamente o que necesitan aumentar la cobertura rápidamente antes de un lanzamiento.

Compatible con~Claude Code~Codex CLI~Cursor✓OpenCode
npx skills add https://github.com/luk036/agent-skills/tree/HEAD/skills/dedup-cpp-tests

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Lo que hago

Tengo dos modos de operación que trabajan juntos:

  1. DEDUP — Identificar y eliminar archivos de prueba redundantes o escenarios donde otro archivo ya cubre las mismas rutas de código con los mismos valores de entrada. Verificado mediante comparación de cobertura gcov (antes vs. después).

  2. GAP-FILL — Después de la deduplicación, analizar el informe de cobertura para encontrar código no probado o insuficientemente probado (en cabeceras y fuentes compiladas), luego escribir pruebas unitarias dirigidas para llenar esos vacíos. Verificado mediante la mejora de cobertura gcov (antes vs. después).

Ambos modos comparten un flujo de trabajo común de compilación de línea base y medición de cobertura.

Cuándo usarme

Para DEDUP:

  • Tu conjunto de pruebas C++ ha crecido orgánicamente con múltiples archivos que cubren el mismo terreno.
  • Se agregaron archivos de prueba para cubrir vacíos (*_gaps.cpp) junto a archivos de prueba completos (*_comprehensive.cpp).
  • Heredaste una base de código con escenarios de prueba duplicados.
  • Quieres reducir el tiempo de las pruebas en CI sin perder cobertura.

Para GAP-FILL:

  • Tienes archivos fuente .cpp compilados sin un archivo de prueba correspondiente.
  • El informe de cobertura muestra cabeceras con menos del 90% de cobertura de líneas.
  • Acabas de eliminar pruebas redundantes y quieres reinvertir el tiempo de prueba ahorrado en llenar vacíos.
  • Necesitas aumentar la cobertura antes de una fecha límite de lanzamiento.

NO me uses cuando:

  • Las pruebas son parametrizadas/generativas que verifican la misma propiedad (esa redundancia es intencional).
  • El proyecto no tiene herramientas de cobertura configuradas.
  • Solo necesitas agregar pruebas sin verificar primero la redundancia.

Cómo trabajo

Fase 0: Entender el panorama de pruebas

Leer el directorio de pruebas y el CMakeLists.txt:

test/source/*.cpp
test/CMakeLists.txt
include/<project>/*.hpp
source/*.cpp              # fuentes compiladas

Verificar:

  • Pares de patrones: *_gaps.cpp + *_comprehensive.cpp, *_basic.cpp + *_detailed.cpp
  • Archivos .cpp compilados en source/ que no tienen un test_*.cpp correspondiente.
  • Cabeceras en include/ sin un archivo de prueba dedicado.
  • Framework de pruebas (doctest, Catch2, Google Test, etc.) y cómo se hace el globbing en CMake.

Nota:

  • Framework de pruebas y macros utilizados (TEST_CASE, CHECK_EQ, etc.).
  • Si CMakeLists.txt usa file(GLOB ...) o listas de fuentes explícitas.
  • El patrón de banderas de cobertura (normalmente -DENABLE_TEST_COVERAGE=ON).

Fase 1: Detectar redundancia a nivel de ARCHIVO

Para cada archivo candidato, comparar sus escenarios de prueba contra cada otro archivo de prueba en el conjunto.

Método de comparación concreto:

  1. Leer ambos archivos lado a lado. Agrupar los escenarios de prueba según el código que ejercitan:

    • Funciones libres (mcd, mcm, etc.)
    • Métodos de clase (constructores, operadores, accesores, mutadores)
    • Instanciaciones y especializaciones de plantillas
    • Cobertura de casos límite (cero, infinito, NaN, valores frontera)
  2. Verificar duplicación a nivel de valor. Si ambos archivos prueban la misma función con valores idénticos (por ejemplo, mcd_recur(12, 8) = 4 en ambos), ese escenario es redundante. Variaciones menores (números de Fibonacci en uno pero no en el otro) NO son redundantes — esas son entradas únicas.

  3. Marcar un archivo como "redundante" si >90% de sus escenarios de prueba aparecen en otro archivo con la misma cobertura o una más completa. El <10% restante de escenarios únicos (variaciones triviales) se puede descartar de manera segura.

Fase 2: Compilar la línea base (ANTES de la eliminación)

Compilar el conjunto de pruebas con instrumentación de cobertura:

HerramientaConfiguración
GCC + gcovcmake -S test -B build/cov -DENABLE_TEST_COVERAGE=ON -DCMAKE_CXX_COMPILER=g++ -G "Unix Makefiles"
Clang + llvm-covcmake -S test -B build/cov -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" -G "Ninja"
MSVCUsa OpenCppCoverage o la cobertura integrada de VS Enterprise

Luego compilar y ejecutar:

cmake --build build/cov
./build/cov/<TestExecutable>

Generar cobertura para cabeceras de bibliotecas

cd build/cov

# Para cada cabecera de biblioteca, ejecutar gcov contra un archivo objeto de prueba que la #incluye
gcov -o CMakeFiles/<Tests>.dir/source/<archivo_prueba>.cpp.o <ruta>/include/<proyecto>/<cabecera>.hpp

# Para cada fuente compilada, ejecutar gcov contra el archivo objeto de la biblioteca
gcov -o _deps/<proyecto>-build/CMakeFiles/<Project>.dir/source/<fuente>.cpp.o <ruta>/source/<fuente>.cpp

Registrar métricas de línea base:

Cabeceras:
  array_like.hpp:    100%  (28/28)
  bpqueue.hpp:       100%  (73/73)
  ...
Fuentes compiladas:
  ell1d.cpp:           0%   (0/23)   ← ¡NO PROBADO!
  ldlt_mgr.cpp:      100%  (10/10)
  ...

Casos de prueba: N  |  Aserciones: M  |  Estado: ÉXITO

También registrar las líneas exactas no cubiertas para cada archivo (las líneas #####: en la salida de .gcov).

Fase 3: Eliminar los archivos redundantes (modo DEDUP)

Remove-Item -Force test/source/test_*_gaps.cpp    # PowerShell
git status test/
git diff --stat

Fase 4: Recompilar y recopilar métricas DESPUÉS DE LA DEDUPLICACIÓN

# Reconfigurar (CMake GLOB puede detectar archivos faltantes)
cmake -S test -B build/cov -DENABLE_TEST_COVERAGE=ON -DCMAKE_CXX_COMPILER=g++ -G "Unix Makefiles"
cmake --build build/cov
./build/cov/<TestExecutable>

Comparar la cobertura contra la línea base. Si la cobertura de alguna cabecera disminuye o las líneas no cubiertas cambian, revertir inmediatamente — el archivo NO era redundante.

Criterios de veredicto para DEDUP:

VerificaciónRequisito
Todas las pruebas pasanDebe pasar (tanto antes como después)
Cobertura de líneas por cabeceraExactamente igual antes y después
Líneas no cubiertasConjunto idéntico de líneas no cubiertas
Número de pruebas disminuyóEsperado — se eliminaron casos de prueba
Número de aserciones disminuyóEsperado — se eliminaron aserciones

Fase 5: Analizar las brechas de cobertura (modo GAP-FILL)

Si después de la deduplicación (o empezando desde cero) el informe de cobertura muestra brechas, identificar el código no probado:

Brecha Tipo A: Fuente compilada con cobertura cero

Verificar source/*.cpp contra test/source/*.cpp. Cualquier .cpp en source/ sin un test_*.cpp correspondiente probablemente no está probado.

# Encontrar archivos fuente sin cobertura de prueba
Get-ChildItem source/*.cpp | ForEach-Object {
    $base = $_.Name -replace '\.cpp$'
    $test = "test/source/test_$base.cpp"
    if (-not (Test-Path $test)) { Write-Host "NO PROBADO: $($_.Name)" }
}

Brecha Tipo B: Cabeceras de bibliotecas con baja cobertura

Mirar el informe de gcov para cabeceras con <90% de cobertura de líneas o "sin líneas ejecutables". Las cabeceras con muchas plantillas pueden atribuir la cobertura al archivo .cpp — verificar ambos.

Brecha Tipo C: Funciones/ramas dentro de archivos cubiertos

Las líneas no cubiertas (#####: en gcov) dentro de archivos parcialmente cubiertos revelan ramas específicas que no se ejercitan. Estos son los objetivos de mayor valor.

Fase 6: Escribir pruebas dirigidas

6a: Leer el código fuente no probado

Leer el archivo fuente completo para entender:

  • La API de la clase/función (constructores, métodos, parámetros)
  • Todas las ramas condicionales (if/else, casos switch, retornos tempranos)
  • Casos límite (entradas nulas/cero, valores frontera, condiciones de error)
  • El comportamiento matemático o algorítmico

6b: Diseñar casos de prueba que cubran cada rama

Para cada método, enumerar los escenarios de prueba necesarios:

MétodoCondiciónEntradaComportamiento esperado
update(cut)beta > taug=1, beta=10 con _r=5Devuelve NoSoln, estado sin cambios
update(cut)beta < -taug=1, beta=-10 con _r=5Devuelve NoEffect, estado sin cambios
update(cut)|beta| <= tau, g>0g=1, beta=2 con _r=5Devuelve Success, nuevo centro en l+_r
update(cut)|beta| <= tau, g<0g=-1, beta=2 con _r=5Devuelve Success, nuevo centro en l+_r
update_central_cut(cut)g > 0g=1, beta=0Reduce _r a la mitad, desplaza el centro a la izquierda
update_central_cut(cut)g < 0g=-1, beta=0Reduce _r a la mitad, desplaza el centro a la derecha

Incluir casos límite:

  • Intervalos de ancho cero
  • Valores frontera (beta == tau, beta == -tau)
  • Gradiente cero (si aplica)
  • Operaciones repetidas/encadenadas para verificar transiciones de estado

6c: Escribir el archivo de prueba

Escribir un nuevo test_<módulo>.cpp en test/source/ siguiendo los patrones de prueba existentes del proyecto:

#define DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
#include <doctest/doctest.h>

#include <ellalgo/<módulo>.hpp>   // cabecera bajo prueba
#include <ellalgo/ell_config.hpp>  // si es necesario para definiciones de tipos
#include <utility>                 // si es necesario para std::pair

TEST_CASE("<Módulo>: construcción") {
    auto x = <Módulo>(...);
    CHECK_EQ(x.método_(), valor_esperado);
}

TEST_CASE("<Módulo>: método con ruta de éxito") {
    auto x = <Módulo>(...);
    auto estado = x.update(...);
    CHECK_EQ(estado, CutStatus::Success);
    CHECK_EQ(x.estado(), estado_esperado);
}

TEST_CASE("<Módulo>: método con ruta de fallo") {
    auto x = <Módulo>(...);
    auto estado = x.update(...);  // dispara la rama beta > tau
    CHECK_EQ(estado, CutStatus::NoSoln);
}

TEST_CASE("<Módulo>: casos límite") {
    // Intervalo de ancho cero, condiciones frontera, etc.
}

TEST_CASE("<Módulo>: operaciones encadenadas") {
    // Varias actualizaciones en secuencia
}

Reglas para los valores de prueba:

  • Usar valores esperados codificados (no calculados a partir del mismo código bajo prueba).
  • Anotar cada valor esperado con la derivación matemática en un comentario (por ejemplo, // tau = |5*1| = 5, bound = 5 - 2/1 = 3, _r = half(3-0) = 1.5, _xc = 1.5).
  • Probar cada rama if al menos una vez.
  • Probar cada caso switch al menos una vez.
  • Probar al menos una operación encadenada/secuencial para verificar la persistencia del estado.

Fase 7: Recompilar y recopilar métricas DESPUÉS DE LLENAR LAS BRECHAS

cmake -S test -B build/cov -DENABLE_TEST_COVERAGE=ON -DCMAKE_CXX_COMPILER=g++ -G "Unix Makefiles"
cmake --build build/cov
./build/cov/<TestExecutable>

# Regenerar informes de cobertura
gcov -o CMakeFiles/<Tests>.dir/source/test_<módulo>.cpp.o <ruta>/include/<proyecto>/<módulo>.hpp
gcov -o _deps/<proyecto>-build/CMakeFiles/<Project>.dir/source/<módulo>.cpp.o <ruta>/source/<módulo>.cpp

Fase 8: Reportar la mejora de cobertura

| Métrica | Antes | Después | Δ |
|--------|--------|-------|---|
| Casos de prueba | N | N' | +Δ |
| Aserciones | M | M' | +Δ |
| <módulo>.hpp | XX% | 100% | +Δ |
| <módulo>.cpp (compilado) | 0% | 100% | +100% |
| Todos los demás archivos | ≥90% | ≥90% | 0% |

Brechas de cobertura llenadas: ✅

REGLAS CRÍTICAS

Regla 1: La cobertura es la verdad, no el número de pruebas

Un archivo de prueba es redundante solo si su eliminación deja sin cambios la cobertura de líneas de cada cabecera de biblioteca. Si la cobertura disminuye, el archivo estaba cubriendo rutas de código únicas. Revertir inmediatamente.

Al agregar pruebas, una nueva prueba solo es exitosa si aumenta la cobertura del archivo objetivo. Si la cobertura del archivo objetivo no aumenta, la prueba no está ejercitando el código — revisar la lógica de la prueba.

Regla 2: Verificar las líneas no cubiertas, no solo el porcentaje

Dos ejecuciones pueden mostrar "98.06%" pero tener diferentes líneas no cubiertas. Siempre comparar el conjunto exacto de líneas #####: en la salida del archivo .gcov:

Get-Content build/cov/foo.hpp.gcov | Select-String -Pattern "^    #####:"

Estas deben ser idénticas antes/después de la deduplicación. Para el llenado de brechas, las líneas #####: del archivo objetivo deben disminuir.

Regla 3: NO eliminar pruebas basadas en propiedades

Las pruebas basadas en propiedades / generativas (por ejemplo, RapidCheck) son intencionalmente redundantes por diseño. Verifican invariantes con entradas aleatorizadas. Estas NO deben eliminarse incluso si se superponen con pruebas basadas en ejemplos.

Regla 4: NO eliminar pruebas con diferentes valores

Dos pruebas para el mismo operador con diferentes valores de entrada ejercitan diferentes rutas de código. Solo eliminar cuando los valores de entrada y las salidas esperadas son idénticos.

Al escribir nuevas pruebas, usar valores de entrada DIFERENTES a los de las pruebas existentes. Duplicar los mismos valores no aumenta la cobertura.

Regla 5: Siempre reconstruir desde un estado limpio

Los archivos .gcda son consumidos por gcov y se vuelven obsoletos después de cualquier cambio en el código fuente o binario. Siempre reconstruir y volver a ejecutar antes de medir:

cmake --build build/cov --clean-first   # o reconstruir desde cero
./build/cov/<TestExecutable>

Regla 6: Verificar el comportamiento de GLOB de CMake

Si CMakeLists.txt usa file(GLOB ...) con CONFIGURE_DEPENDS, CMake detecta automáticamente archivos agregados/eliminados al reconstruir (CMake 3.12+). En CMake más antiguo, reconfigurar manualmente después de agregar o eliminar archivos de prueba.

Al escribir un nuevo archivo de prueba, verificar que CMake lo reconozca:

  • Buscar test_<módulo>.cpp en la salida de compilación.
  • Si falta, reconfigurar: cmake -S test -B build/cov

Regla 7: Codificar los valores esperados, no recalcularlos

Al escribir pruebas, NUNCA calcular los valores esperados usando el mismo código bajo prueba. Siempre derivarlos manualmente y codificarlos como literales. Esto detecta regresiones donde la implementación cambia pero la prueba sigue pasando porque ambos lados usan el mismo código defectuoso.

Anotar derivaciones complejas:

// tau = |5*1| = 5, bound = 5 - 2/1 = 3
// g>0 entonces u=3, l=5-5=0
// _r = half_nonnegative(3-0) = 1.5, _xc = 0 + 1.5 = 1.5
CHECK_EQ(e.xc(), 1.5);

Regla 8: Probar cada rama

Para cada función con lógica condicional, enumerar todas las ramas y escribir al menos una prueba por rama:

RamaEscenario de prueba
if (beta > tau)Ruta sin solución
if (beta < -tau)Ruta sin efecto
else (éxito)Ruta de éxito
g > 0 dentro del éxitoGradiente positivo
g < 0 dentro del éxitoGradiente negativo
Frontera beta == tauAún éxito (≤)
Frontera beta == -tauDepende de < vs ≤

Regla 9: Priorizar fuentes compiladas sobre cabeceras

Es más probable que los archivos .cpp compilados tengan cobertura cero porque el código de solo cabeceras a menudo se ejercita incidentalmente por otras pruebas. Priorizar la prueba de:

  1. source/*.cpp sin test_*.cpp correspondiente → prioridad más alta
  2. Cabeceras con cobertura de líneas <90% → prioridad media
  3. Ramas no cubiertas dentro de archivos ya cubiertos → prioridad más baja

Regla 10: Coincidir con las convenciones de prueba del proyecto

Antes de escribir pruebas, estudiar 2-3 archivos de prueba existentes para coincidir con:

...

[truncado para la traducción]

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

Skills relacionados