Back to flin
flin

3.452 pruebas, cero fallos

Cómo la suite de pruebas de FLIN creció de cero a 3.452 pruebas a lo largo de 301 sesiones -- la estrategia de pruebas, las categorías y lo que significa para la confianza en un runtime de lenguaje.

Thales & Claude | March 30, 2026 9 min flin
EN/ FR/ ES
flinrust

El día en que la auditoría concluyó, ejecutamos cargo test una última vez. El número que regresó fue 3.452. Cada prueba pasó. Cero fallos. Cero ignoradas. Cero resultados inestables. En un código base de 186.252 líneas construido a lo largo de 301 sesiones en 42 días, cada verificación automatizada seguía manteniéndose.

Ese número no ocurrió por accidente. Ocurrió porque cada sesión que introdujo una característica también introdujo las pruebas para verificarla. Ocurrió porque cada sesión de corrección de auditoría ejecutó la suite completa antes y después de los cambios. Y ocurrió porque el sistema de tipos de Rust detecta en tiempo de compilación las categorías de errores que de otro modo requerirían miles de pruebas más en un lenguaje de tipado dinámico.

Esta es la historia de la suite de pruebas de FLIN -- cómo creció, qué cubre, y qué significan realmente 3.452 pruebas aprobadas para un runtime de lenguaje.

La curva de crecimiento

El conteo de pruebas de FLIN creció con su conteo de características, pero no linealmente. Las primeras sesiones produjeron la mayor cantidad de pruebas por característica porque los componentes fundamentales -- lexer, parser, operaciones básicas de la VM -- tienen la mayor densidad de pruebas. Un lexer que reconoce más de 80 tipos de tokens necesita al menos 80 pruebas. Un parser que maneja más de 40 tipos de sentencias necesita al menos 40 pruebas. Una tabla de despacho de opcodes con más de 170 opcodes necesita al menos 170 pruebas.

Session Range    Focus                 Tests Added    Cumulative
001-050          Lexer, Parser, VM     ~1,200         1,200
051-100          Types, Entities       ~600           1,800
101-150          Web Server, Routes    ~400           2,200
151-200          Database, Security    ~350           2,550
201-250          Audit, Fixes          ~500           3,050
251-301          Polish, Gaps          ~400           3,452

Las sesiones intermedias (101-200) agregaron menos pruebas por sesión porque cada característica era más grande y más integrada. Una prueba de ruta de servidor web ejercita el lexer, parser, verificador de tipos, generador de código, VM, renderizador y servidor HTTP de una vez -- una prueba cubre muchos componentes. Las sesiones posteriores (201+) vieron un resurgimiento en el conteo de pruebas ya que las correcciones de auditoría requerían verificación dirigida de comportamientos específicos.

Categorías de pruebas

La suite de pruebas está organizada en tres niveles que reflejan las convenciones de pruebas de Rust:

Pruebas unitarias (embebidas en archivos fuente). Estas prueban funciones y métodos individuales de forma aislada. El parser tiene 29 pruebas unitarias para creación de AST, formato de visualización, elementos de vista, detección de componentes y hooks de ciclo de vida. El renderizador tiene 119 pruebas unitarias para generación de HTML, serialización de manejadores de eventos, paso de props de componentes y renderizado de layouts.

rust// Example: parser unit test for view element creation
#[test]
fn test_view_element_is_component() {
    let element = ViewElement {
        tag: "Button".to_string(),
        attributes: vec![],
        children: vec![],
        span: Span::default(),
    };
    assert!(element.is_component_tag());

    let element = ViewElement {
        tag: "div".to_string(),
        attributes: vec![],
        children: vec![],
        span: Span::default(),
    };
    assert!(!element.is_component_tag());
}

Pruebas de integración (en el directorio tests/). Estas compilan y ejecutan programas FLIN completos, verificando el comportamiento de extremo a extremo. Las pruebas de flujo del servidor de desarrollo de la Sesión 203 son pruebas de integración -- crean una VM, inyectan estado, ejecutan bytecode y verifican la persistencia de la base de datos.

rust// Example: integration test for entity persistence across restarts
#[test]
fn test_recovery_between_vms() {
    let db_path = tempdir().unwrap();

    // VM1: create and save an entity
    {
        let mut vm = VM::new_with_storage(db_path.path());
        vm.register_entity("Todo", &["title", "done"]);
        vm.save_entity("Todo", &[
            ("title", Value::Text("Buy milk".into())),
            ("done", Value::Bool(false)),
        ]).unwrap();
    }
    // VM1 is dropped -- simulates server shutdown

    // VM2: verify entity survived
    {
        let mut vm = VM::new_with_storage(db_path.path());
        vm.register_entity("Todo", &["title", "done"]);
        let todos = vm.query_all("Todo").unwrap();
        assert_eq!(todos.len(), 1);
        assert_eq!(
            vm.get_field(&todos[0], "title").unwrap(),
            Value::Text("Buy milk".into())
        );
    }
}

Pruebas de estrés del parser (línea 8866+ en parser.rs). El archivo del parser contiene aproximadamente 600 aserciones basadas en panic en su sección de pruebas. Estas son deliberadamente agresivas -- prueban que una sintaxis FLIN específica produce estructuras AST específicas, y cualquier desviación causa un fallo inmediato en la prueba. Las llamadas a panic en la sección de pruebas (que la auditoría inicialmente marcó) son aserciones estándar de pruebas de Rust, no problemas de código de producción.

Lo que cubren las pruebas

La cobertura de la suite de pruebas abarca cada subsistema principal:

Cobertura del lexer. Cada tipo de token en el vocabulario de 84 palabras clave está probado. Modos de interpolación de plantillas (cadena, atributo, vista), manejo de contenido sin procesar para etiquetas style/script/pre/code, las transiciones de la máquina de estados de tres modos, y casos límite como llaves anidadas en expresiones de atributos.

Cobertura del parser. Los más de 40 tipos de sentencias, más de 35 tipos de expresiones, los 7 tipos de patrones, las 16 variantes de anotaciones de tipo, y más de 50 validadores de campos. Las pruebas del parser verifican tanto el análisis correcto como la recuperación de errores -- qué sucede cuando la entrada está malformada.

Cobertura de la VM. Despacho de opcodes para todos los opcodes implementados, comportamiento de funciones nativas para operaciones de cadena/matemáticas/fecha/lista, recolección de basura bajo presión de asignación, y gestión de ámbitos para closures y upvalues.

Cobertura del renderizador. Generación de HTML para cada tipo de elemento, serialización de manejadores de eventos para elementos nativos y de componentes, renderizado condicional y en bucle, composición de slots y layouts, y búsqueda de traducciones.

Cobertura de la base de datos. Operaciones CRUD para todos los tipos de entidades, persistencia y recuperación del WAL, atomicidad de transacciones, versionado de viajes en el tiempo, y filtrado de consultas con varios operadores.

rust// Example: VM test for CreateMap with both string representations
#[test]
fn test_create_map_value_text_keys() {
    let mut vm = VM::new();
    let source = r#"
        translations = {
            en: { "hello": "Hello" },
            fr: { "hello": "Bonjour" }
        }
    "#;
    let result = vm.execute(source).unwrap();

    // Verify both Value::Text and Value::Object keys work
    let map = vm.get_global("translations").unwrap();
    let en = vm.map_get(&map, "en").unwrap();
    let hello = vm.map_get(&en, "hello").unwrap();
    assert_eq!(hello, Value::Text("Hello".into()));
}

Lo que las pruebas no cubren

La honestidad sobre la cobertura de pruebas requiere reconocer las brechas. La suite de pruebas de FLIN tiene tres puntos ciegos significativos a la fecha de finalización de la auditoría:

Sin pruebas de fuzzing. El parser y el lexer no han sido sometidos a generación de entrada aleatoria. Las pruebas de fuzzing ejercitarían las rutas de recuperación de errores y casos límite que las pruebas escritas por humanos pasan por alto. Para un lenguaje que procesa entrada de desarrolladores no confiable, esta es una brecha que vale la pena cerrar.

Pruebas de concurrencia limitadas. Los módulos de servidor web y WebSocket tienen pruebas funcionales pero no pruebas de carga. Bajo procesamiento concurrente de solicitudes, los patrones de acceso a estado compartido pueden revelar condiciones de carrera que las pruebas secuenciales no pueden detectar.

Sin pruebas basadas en propiedades. Las operaciones que deberían satisfacer propiedades algebraicas (como parse(format(ast)) == ast o deserialize(serialize(value)) == value) se prueban con ejemplos específicos en lugar de con marcos de pruebas basados en propiedades como proptest. Las pruebas basadas en propiedades proporcionarían garantías más fuertes sobre estos invariantes.

La suite de pruebas como documentación

Más allá de la verificación, la suite de pruebas de FLIN sirve como documentación ejecutable. Cuando la auditoría necesitaba entender cómo se suponía que una característica debía funcionar, las pruebas proporcionaban la respuesta autoritativa. Los logs de sesión describían la intención; las pruebas describían el comportamiento.

Este doble rol es particularmente importante para un runtime de lenguaje porque la especificación y la implementación pueden divergir. Cuando lo hacen, la pregunta "¿cuál es correcta?" tiene solo una respuesta confiable: las pruebas. Si las pruebas pasan y el comportamiento coincide con las expectativas de las pruebas, la implementación es correcta independientemente de lo que diga la especificación. Si las pruebas necesitan actualizarse, la especificación también necesita actualizarse.

rust// Tests as documentation: how does Entity.where() work?
#[test]
fn test_entity_where_filters_correctly() {
    let mut vm = setup_todo_vm();

    // Add test data
    vm.save_entity("Todo", &[
        ("title", Value::Text("Done task".into())),
        ("done", Value::Bool(true)),
    ]).unwrap();
    vm.save_entity("Todo", &[
        ("title", Value::Text("Open task".into())),
        ("done", Value::Bool(false)),
    ]).unwrap();

    // where(done == false) should return only open tasks
    let result = vm.execute(r#"
        open = Todo.where(done == false)
    "#).unwrap();

    let open = vm.get_global("open").unwrap();
    assert_eq!(vm.list_len(&open), 1);
}

Esta prueba te dice todo lo que necesitas saber sobre Entity.where(): acepta una comparación de campos, devuelve una lista filtrada, y evalúa el predicado contra los campos de cada entidad. Ninguna documentación en prosa podría ser más precisa o más confiable.

3.452 y contando

El número 3.452 no es un conteo final. Es una instantánea de enero de 2026. Cada futura sesión que agregue una característica agregará pruebas. Cada reporte de error producirá una prueba de regresión antes de la corrección. La propia auditoría agregó docenas de pruebas para rutas de código previamente no probadas.

Pero 3.452 pruebas pasando simultáneamente, después de 301 sesiones de desarrollo, es una declaración sobre la integridad del código base. Dice que las decisiones fundamentales -- usar Rust, mantener la disciplina de pruebas, ejecutar la suite completa después de cada cambio -- fueron correctas. Dice que un runtime de lenguaje construido en 42 días puede estar tan bien probado como uno construido durante años, si la cultura de pruebas está embebida desde la Sesión 1.

Y dice que cuando la auditoría encontró 30 TODOs y 5 panics de producción, los encontró contra un telón de fondo de miles de comportamientos verificados. Los defectos eran la excepción, no la regla. El código base era sólido.

El siguiente artículo examina lo que la auditoría nos enseñó sobre la práctica más amplia de construir un lenguaje de programación -- las lecciones arquitectónicas, las perspectivas de proceso, y los principios que llevaríamos adelante en la siguiente fase de FLIN.


Esta es la Parte 152 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abidjan y un CTO de IA diseñaron y construyeron un lenguaje de programación desde cero.

Navegación de la serie: - [151] Auditoría de persistencia de la base de datos - [152] 3.452 pruebas, cero fallos (estás aquí) - [153] Lo que la auditoría nos enseñó sobre construir un lenguaje

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles