Pruebas asistidas por IA: usar pruebas unitarias como puerta de enlace para respaldar el código generado por IA

Compartir el flujo de trabajo completo de pruebas asistidas por IA, desde el diseño de documentos hasta el código de casos de prueba, usar pruebas unitarias como puerta de enlace para mitigar los riesgos del código generado por IA, cambiando el objeto de revisión del código a la documentación de casos de prueba, dejar que los casos de prueba fallidos vigilen la pereza y los errores de la IA, para que el desarrollo tenga confianza en el vibe coding.

Las pruebas asistidas por IA no resuelven la cobertura de pruebas, sino mi problema de confianza en el código generado por IA. La IA puede generar una gran cantidad de código en tres minutos, pero una persona necesita dos horas para revisarlo; tres minutos de generación y dos horas de revisión no es una exageración. Mi solución es cambiar el objeto de revisión: no leer el código línea por línea, sino leer la documentación de casos de prueba, y luego establecer una puerta de enlace con pruebas unitarias en el entorno de desarrollo local, para que los casos de prueba fallidos vigilen la pereza y los errores de la IA.

Los pequeños bugs son el campo de batalla principal de las pruebas asistidas por IA

Los bugs se dividen en dos categorías: los fáciles de resolver y los difíciles de resolver.

Los bugs difíciles de resolver suelen originarse en defectos de arquitectura o diseño de soluciones, evolucionando gradualmente como carga histórica, donde un cambio afecta a todo; también pueden ser causados por terceros, como bibliotecas de terceros, sistemas operativos o compiladores, requiriendo conocimientos más especializados y una exploración profunda para resolverlos. Esta clase de problemas complejos no está dentro del alcance de este artículo.

Este artículo se centra en los pequeños bugs del ciclo de vida del producto, utilizando IA para crear casos de prueba, resolviendo los siguientes problemas:

  • Bugs que se pueden detectar rápidamente
  • Bugs de bajo nivel que solo requieren cambios mínimos para corregirse
  • Nuevos bugs introducidos por modificaciones
  • Errores cometidos bajo presión durante modificaciones urgentes
  • Compensación entre costo y eficiencia de pruebas específicas

La mayoría de los bugs son pequeños bugs, pero los pequeños bugs también pueden causar problemas graves. Resolver los pequeños bugs localmente puede ahorrar una gran cantidad de esfuerzo posterior.

Cambiar el objeto de revisión del código a la documentación de casos de prueba

Una vez intenté una idea audaz: dejar que la IA se encargara por completo de un requisito, sin revisar el código que generaba, soltando las riendas por completo como en el modo agente de Copilot. Más tarde lo abandoné. La razón fundamental era la falta de confianza interna. Para un programa binario con lógica rigurosa y clara, no tener confianza interna significa perder la confianza de los equipos upstream y downstream, y perder la confianza del cliente. Por eso estoy reflexionando sobre el vibe coding: para el código generado por la IA, definitivamente debe haber confianza interna.

El problema es el costo de revisión. A veces la IA genera o modifica una cantidad demasiado grande de código; tres minutos de generación y dos horas de revisión no es una exageración. La carga de trabajo del código de prueba suele ser incluso mayor que la del código de negocio, y no necesariamente se incluye en la evaluación de entrega de I+D, lo cual es una de las razones por las que mucha gente no quiere escribir pruebas de forma proactiva. Pero que el asistente de IA es mucho más inteligente que yo es un hecho; en lugar de resistirse, es mejor proporcionarle una puerta de enlace.

Cambiar el objeto de revisión del código a la documentación de casos de prueba es la clave para romper este punto muerto. A diferencia del código de negocio, los casos de prueba generados por IA suelen describir un escenario muy específico, fácil de revisar y verificar. Muchos casos de prueba ni siquiera necesitan leerse línea por línea; solo hay que examinarlos detenidamente cuando reportan un error. Decir que no se revisa en absoluto el código de IA es una exageración, pero con una documentación de casos de prueba completa, se puede demostrar que no todas las líneas de código requieren revisión manual.

---
title: De la revisión de código a la revisión de documentación
---
flowchart TB
    A[IA genera una gran cantidad de código] e1@--> B[El costo de revisión línea por línea es alto]
    B e2@--> C{¿Qué revisar?}
    C e3@-->|Código| D[Dos horas de revisión, sin confianza interna]
    C e4@-->|Documentación de casos de prueba| E[Fácil de revisar, verificación rápida]
    E e5@--> F[Puerta de enlace de pruebas unitarias]
    F e6@--> G[Los casos de prueba fallidos exponen problemas]

    classDef risk fill:#FFF3E0,stroke:#E65100,color:#E65100;
    classDef ok fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20;
    classDef animate stroke:#EF6C00,stroke-width:2px,stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
    class D risk;
    class E,F,G ok;
    class e1,e2,e3,e4,e5,e6 animate;

Base metodológica: Seis Sigma y DMAIC

Six Sigma es una metodología de mejora de calidad basada en datos, cuyo objetivo central es reducir la variación del proceso y controlar la tasa de defectos en 3.4 por millón, es decir, el nivel 6σ1.

Proporciona un flujo de trabajo ejecutable llamado DMAIC:

  • Definir: análisis de requisitos, conversión a modelos de datos, diseño de estructuras de datos e interfaces
  • Medir: monitoreo de casos de prueba, cobertura, rendimiento, seguridad
  • Analizar: analizar casos de prueba y código de negocio
  • Mejorar: mejorar casos de prueba y código de negocio
  • Controlar: puerta de enlace de pruebas automatizadas

Las herramientas técnicas de soporte incluyen linter, verificación estática, verificación dinámica y marcos de prueba. También hay muchos indicadores de calidad de código a considerar, como indicadores de complejidad (complejidad ciclomática, complejidad cognitiva, profundidad de anidación, acoplamiento de clases), indicadores de calidad (tasa de duplicación, longitud de funciones, números mágicos) e indicadores de prueba (cobertura de pruebas unitarias, tasa de aprobación de pruebas de integración, validez de casos de prueba, cobertura de manejo de excepciones).

La metodología y las herramientas pueden ayudar a descubrir y resolver problemas, a costa de posiblemente aumentar el costo de entrega y reducir la eficiencia en cierto grado. Con recursos limitados, se debe elegir un método que pueda seguir beneficiando a corto, mediano y largo plazo. Mi elección es utilizar las pruebas unitarias como herramienta auxiliar en el proceso de desarrollo.

Gracias a la capacidad de la IA para convertir entre diferentes lenguajes de roles, el desarrollo puede combinar las ventajas de TDD, BDD y ATDD, generando rápidamente tablas de casos de prueba y código de casos de prueba, mejorando la calidad de entrega.

Pruebas dirigidas: TDD/BDD/ATDD y código testeable

Las diferencias entre los tres tipos de pruebas dirigidas son las siguientes:

DimensiónTDDBDDATDD
Origen del impulsoPruebas unitariasEscenarios de comportamiento del usuarioCondiciones de aceptación
ParticipantesDesarrolladoresNegocio + DesarrolloNegocio + Pruebas + Desarrollo
Nivel de pruebaPruebas unitariasPruebas de integración/end-to-endPruebas de aceptación
Ejemplos de herramientasJUnit, pytestCucumber, SpecFlowFitNesse, Robot Framework
Salida típicaInforme de cobertura de códigoDocumentación de escenarios de usuario ejecutablesTasa de aprobación de pruebas de aceptación
Lenguajeassert add(2,3) == 5Escenarios de lenguaje natural (Dado-Cuándo-Entonces)Lenguaje de negocio (tabla de condiciones de aceptación)

El código testeable tiene cuatro principios fundamentales: responsabilidad única (cada función hace una sola cosa), inyección de dependencias (transmitir dependencias a través de parámetros), aislamiento de interfaces (tipos de entrada y salida claros) y estado observable (proporcionar interfaces de consulta para verificar el estado interno).

En las instrucciones para la IA, recomiendo escribir directamente extensible, responsabilidad única, inyección de dependencias. Una vez intenté hacer que la IA controlara la longitud de las funciones y archivos generados, pero su nivel de cumplimiento fue bajo; sin embargo, al cambiar a responsabilidad única e inyección de dependencias, el código generado se ajustó claramente a lo esperado. La optimización prematura es la raíz de todos los males; al generar estructuras de datos e interfaces básicas, basta con indicarle a la IA que priorice la extensibilidad.

Ejemplo de código fácil de probar:

// user.h
typedef struct {
    int id;
    char name[50];
    int age;
} User;

// Abstracción de la interfaz de almacenamiento en memoria
typedef struct {
    User* (*get_user)(int id);
    int (*save_user)(const User* user);
} UserStorage;

// Crear usuario (inyección de dependencias de la interfaz de almacenamiento)
User* create_user(const char* name, int age, UserStorage* storage) {
    if(strlen(name) >= 50 || age <= 0) return NULL;

    User* new_user = malloc(sizeof(User));
    new_user->id = generate_user_id();
    strncpy(new_user->name, name, 49);
    new_user->name[49] = '\0';
    new_user->age = age;

    if(storage->save_user(new_user) != 0) {
        free(new_user);
        return NULL;
    }
    return new_user;
}

Ejemplo de código difícil de probar:

// Operación de base de datos estrechamente acoplada
void save_user_to_db(User* user) {
    MYSQL conn;
    mysql_init(&conn);
    if(!mysql_real_connect(&conn, "localhost", "root", "pass", "test", 0, NULL, 0)) {
        log_error("DB connection failed"); // Dependencia de registro difícil de probar
        exit(1); // Salida directa que afecta la ejecución de pruebas
    }
    // Concatenar SQL y escribir en la base de datos
}

El buen código pasa desapercibido, el mal código se hace notar. Las funciones fáciles de probar tienen valores de retorno claros, manejo de errores estandarizado, parámetros de entrada definidos, efectos secundarios aislados y admiten inyección de dependencias.

El código proporcionado por la IA no siempre sigue las mejores prácticas y puede tener problemas de estilo. Aunque a menudo resuelve el problema, un proyecto real no es algo de una sola vez; se debe considerar el mantenimiento posterior. Al solicitar a la IA que genere código, también debe enfatizarse legibilidad primero, extensibilidad, responsabilidad única, inyección de dependencias. No se discutirán más problemas de estilo de código; se puede consultar la guía de estilo de código de Google2.

Trampas y compensaciones en el desarrollo real

Cuando los requisitos se transmiten entre roles, pasan por múltiples rondas de conversión de lenguaje, y cada ronda puede generar desviaciones en la expresión. Por ejemplo, cuando el cliente dice “vista centralizada”, el backend lo entiende como “retorno de una sola interfaz” sin implementar paginación. Mediante la generación de plantillas de lenguaje para cada rol con IA, se puede reducir la pérdida de información en la conversión manual.

La falta de previsión es la raíz de muchos bugs. Al utilizar la IA para diseñar escenarios de casos de prueba, no solo puede completar la tarea esperada, sino también ofrecer inspiración más allá de lo esperado, complementando los detalles que el autor de los requisitos no consideró. En cada ronda de conversión de lenguaje entre roles, se puede dialogar con la IA para perfeccionar las lagunas. Markdown es el idioma más fácil de entender para la IA; la entrada y salida deben adoptar el formato Markdown siempre que sea posible.

Hay varios puntos que deben tenerse en cuenta en el código de casos de prueba: aislamiento de casos de prueba, precisión en la simulación de dependencias, validez de las aserciones, valores límite, manejo de excepciones, rendimiento y concurrencia. La IA será perezosa y no generará casos de prueba que cubran todos los escenarios de una sola vez. Generar todo el código de prueba de una vez exige mucho del modelo; se puede generar en varias ocasiones.

Tomando como ejemplo el modelo Claude en Cursor, cumple bastante bien con algunas consideraciones del código de casos de prueba, pero a menudo olvida el aislamiento de casos de prueba y los escenarios de excepciones. Agregar una regla de usuario puede reducir ligeramente la pereza: al generar casos de prueba, preste atención a la documentación de casos de prueba y al aislamiento de casos de prueba. Sin embargo, aún no generará todos los escenarios de una vez; los escenarios de ruta normal, ruta excepcional, valores límite, rendimiento y concurrencia requieren complementarse mediante múltiples conversaciones.

Un flujo de trabajo viable de pruebas asistidas por IA

Combinado con el documento de diseño, se puede encadenar una canalización desde escenarios hasta casos de prueba:

---
title: Flujo de trabajo de pruebas asistidas por IA
---
flowchart TB
    A[Documento de diseño] e1@--> B[Generar escenarios de prueba de aceptación]
    B e2@--> C[Generar estructuras de datos e interfaces]
    C e3@--> D[Generar tabla de casos de prueba del cliente]
    D e4@--> E[Generar código de casos de prueba]
    D e5@--> F[Generar código de negocio]
    E e6@--> G[Puerta local: casos de prueba aprobados]
    F e7@--> G
    G e8@--> H{¿Existen casos de prueba fallidos?}
    H e9@-->|Sí| E
    H e10@-->|No| I[Enviar a pruebas]

    classDef input fill:#E3F2FD,stroke:#1565C0,color:#0D47A1;
    classDef gen fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20;
    classDef gate fill:#FFF3E0,stroke:#E65100,color:#E65100;
    classDef animate stroke:#EF6C00,stroke-width:2px,stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
    class A input;
    class B,C,D,E,F gen;
    class G,H,I gate;
    class e1,e2,e3,e4,e5,e6,e7,e8,e9,e10 animate;

Las instrucciones para cada paso pueden organizarse de la siguiente manera: generar escenarios de prueba de aceptación basados en el documento de diseño del producto; generar estructuras de datos e interfaces basados en los escenarios de prueba; generar la tabla de casos de prueba del cliente basados en los escenarios de prueba y las interfaces; generar código de casos de prueba basados en las interfaces y la tabla de casos de prueba; generar código de negocio basados en los escenarios de prueba y garantizar que los casos de prueba pasen.

Existe un riesgo oculto: la IA podría modificar el código de negocio según el código de prueba, o viceversa, modificar el código de prueba según el código de negocio. Si ambos lados contienen errores, la IA se confundirá; a medida que aumenta el número de conversaciones, el código de prueba y el código de negocio se volverán cada vez más desordenados, momento en el cual es necesaria la intervención humana.

Un ejemplo de instrucción demasiado ambiciosa es solicitar a la IA que cubra de una sola vez escenarios normales, excepcionales, valores límite y concurrencia, y genere el código. Este tipo de solicitudes se interrumpe fácilmente. El enfoque correcto es dividir los escenarios en Cursor y generar por lotes mediante múltiples solicitudes. Los casos de prueba de rendimiento requieren una gran cantidad de datos; deben incluirse en la etapa de pruebas del sistema. Primero, pídale a la IA que escriba un generador de datos aleatorios y luego utilice los datos generados para construir los casos de prueba.

Las pruebas de integración requieren construir un entorno de prueba. Algunos casos de prueba dependen de herramientas externas; por ejemplo, el aislamiento micro depende de iptables e ipset, y se pueden construir contenedores de prueba con Docker. El principio es: instruya a la IA para que implemente los mismos pasos de automatización que un humano realizaría para probar. En cuanto a las pruebas de seguridad, actualmente se basan principalmente en la experiencia y el análisis, y aún no existe experiencia madura en pruebas asistidas por IA.

Resumen

Este flujo de trabajo genera más documentación y código de prueba en comparación con no usar IA, por lo que es difícil revisar cada palabra. Sin embargo, la documentación es más fácil de revisar que el código, especialmente la tabla de casos de prueba, que proporciona una guía precisa para la generación de casos de prueba. En el desarrollo real, a veces solo reviso los casos de prueba fallidos, confirmando el problema mediante la depuración de los casos de prueba fallidos.

Hay tres direcciones de mejora: los subprocesos de la etapa de diseño se solapan, los escenarios de prueba de aceptación pueden depender de estructuras de datos e interfaces; la tabla de casos de prueba del cliente es responsabilidad del propio equipo de I+D; antes de enviar a pruebas, es necesario generar nuevamente escenarios de prueba detallados que incluyan detalles del código de negocio.

El beneficio final no radica en las cifras de cobertura de pruebas, sino en la confianza: gracias a esta puerta de enlace local de pruebas unitarias, finalmente puedo tener confianza en el código generado por IA, en lugar de enviar código producido mediante vibe coding a producción sin pensarlo.


  1. La definición de Six Sigma y el método DMAIC hacen referencia a la entrada de Wikipedia. ↩︎

  2. Google Style Guide es la guía de estilo de código publicada por Google. ↩︎