Testing Avanzado y Automatización
Estructuras de pruebas, frameworks y testing basado en propiedades en C
Introducción¶
A medida que el tamaño y complejidad de los proyectos de software se incrementa (como al diseñar Tipos Abstractos de Datos dinámicos), las aserciones básicas aisladas en el código fuente resultan insuficientes. Es imperativo estructurar suites de prueba modulares y automatizar su compilación y ejecución.
Desarrollo¶
Testing Avanzado y Organización de Pruebas¶
Un proyecto de calidad industrial debe separar físicamente el código de producción del código de pruebas unitarias. Esto mantiene limpia la entrega del ejecutable final y permite compilar los módulos de testing únicamente cuando sea necesario.
Estructura de Directorios Estándar¶
proyecto/
├── src/
│ ├── calculadora.h
│ ├── calculadora.c
│ └── main.c
├── tests/
│ ├── test_calculadora.c
│ ├── test_utilidades.c
│ └── run_tests.c
└── MakefileAutomatización de Compilación en el Makefile¶
El archivo Makefile debe definir un objetivo específico (típicamente test o
check) que compile los archivos de prueba vinculándolos con los objetos de
producción, y ejecute el binario resultante:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16CC = gcc CFLAGS = -Wall -Wextra -Werror -std=c99 ## Objetivos principales all: programa programa: src/main.o src/calculadora.o $(CC) $(CFLAGS) -o programa src/main.o src/calculadora.o ## Objetivo de Pruebas Unitarias test: tests/run_tests.o src/calculadora.o $(CC) $(CFLAGS) -o run_tests tests/run_tests.o src/calculadora.o ./run_tests clean: rm -f src/*.o tests/*.o programa run_tests
Testing Basado en Propiedades y TDD¶
Testing Basado en Propiedades (Property-Based Testing)¶
En lugar de verificar valores de entrada específicos e individuales, el testing basado en propiedades valida que se cumplan axiomas y teoremas matemáticos generales de la función para cualquier entrada dentro de un rango determinado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17#include <assert.h> void test_suma_conmutativa(void) { // Axioma: a + b == b + a for (int i = -50; i <= 50; i++) { for (int j = -50; j <= 50; j++) { assert(suma(i, j) == suma(j, i)); } } } void test_suma_elemento_neutro(void) { // Axioma: x + 0 == x for (int i = -100; i <= 100; i++) { assert(suma(i, 0) == i); } }
Test-Driven Development (TDD)¶
El TDD es una metodología de diseño de software donde la prueba unitaria se escribe antes que el código de producción:
Red (Rojo): Escribir una prueba que falle (ya que la función aún no está implementada).
Green (Verde): Escribir la cantidad mínima de código de producción para que la prueba pase.
Refactor (Refactorizar): Limpiar y optimizar el código escrito garantizando que todas las pruebas sigan pasando (en verde).
Estructuración de Frameworks de Testing Personalizados¶
En proyectos más complejos, es útil construir un arnés de pruebas personalizado para capturar estadísticas detalladas de éxito y fallos sin abortar inmediatamente el binario completo ante el primer error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39#include <stdio.h> #include <stdbool.h> typedef struct { int ejecutadas; int exitosas; int fallidas; } stats_t; static stats_t stats = {0, 0, 0}; void verificar(bool condicion, const char* descripcion) { stats.ejecutadas++; if (condicion) { stats.exitosas++; printf(" ✓ [OK] %s\n", descripcion); } else { stats.fallidas++; printf(" ✗ [FAIL] %s\n", descripcion); } } // Ejemplo de suite void suite_aritmetica(void) { printf("\nEjecutando Suite Aritmética:\n"); verificar(suma(2, 2) == 4, "Suma de enteros positivos"); verificar(resta(5, 10) == -5, "Resta con resultado negativo"); } int main(void) { suite_aritmetica(); printf("\n==== Reporte Final ====\n"); printf("Pruebas ejecutadas: %d\n", stats.ejecutadas); printf("Exitosas: %d\n", stats.exitosas); printf("Fallidas: %d\n", stats.fallidas); return stats.fallidas > 0 ? 1 : 0; }
Ejercicios de Autoevaluación¶
Solution to Exercise 1
Prueba Unitaria: Verifica la corrección de un único componente de software (normalmente una función individual) de forma completamente aislada de cualquier otro módulo o recurso externo (disco, red, base de datos).
Prueba de Integración: Verifica la correcta interacción y comunicación entre dos o más módulos integrados del sistema.
El aislamiento en las pruebas unitarias es crucial porque asegura que si la prueba falla, la causa raíz del error se encuentra exclusivamente en la unidad bajo prueba. Si no existiera aislamiento, un fallo en una biblioteca de red externa podría hacer fallar el test unitario de un algoritmo de ordenamiento, haciendo que el diagnóstico del error sea complejo y confuso.
Solution to Exercise 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14#include <assert.h> void test_propiedad_asociativa_multiplicacion(void) { // Propiedad asociativa: (a * b) * c == a * (b * c) for (int a = -20; a <= 20; a++) { for (int b = -20; b <= 20; b++) { for (int c = -20; c <= 20; c++) { int resultado1 = (a * b) * c; int resultado2 = a * (b * c); assert(resultado1 == resultado2); } } } }
Glosario¶
- Pruebas Unitarias (Unit Testing)
- Pruebas que validan el comportamiento de componentes individuales.
- Mock
- Objeto simulado para imitar el comportamiento de componentes reales en pruebas.
- Assert (Aseveración)
- Expresión lógica utilizada en pruebas para comprobar que se cumple una condición.
Síntesis y Resumen¶
En este apunte se han presentado los conceptos fundamentales sobre estructuración de tests, testing basado en propiedades y frameworks personalizados en C, haciendo hincapié en la importancia de mantener las pruebas aisladas y automatizadas.
Referencias y Lecturas Complementarias¶
Beck, K. Beck, 2002. Test Driven Development: By Example. Addison-Wesley.
El texto seminal para comprender la filosofía de diseño guiado por pruebas unitarias (TDD) y su aplicación sistemática.
McConnell, S. McConnell, 2004. Code Complete (2.ª edición). Microsoft Press.
Consultá el Capítulo 22: Developer Testing para un desglose exhaustivo de cómo estructurar pruebas efectivas y evaluar la cobertura de código.
Claessen, K. y Hughes, J. Claessen & Hughes, 2000. QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs.
El artículo fundacional del testing basado en propiedades (property-based testing), que introduce los conceptos de generación aleatoria de entradas e invariantes de datos.
Sommers, J. Sommers, 2025. jsommers/cbook.
Consultá los capítulos de testing para ejemplos reales de frameworks mínimos a medida implementados en C.
- Beck, K. (2002). Test Driven Development: By Example. Addison-Wesley.
- McConnell, S. (2004). Code Complete (2nd ed.). Microsoft Press.
- Claessen, K., & Hughes, J. (2000). QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs. Proceedings of the Fifth ACM SIGPLAN International Conference on Functional Programming, 268–279. 10.1145/351240.351266
- Sommers, J. (2025). jsommers/cbook. https://github.com/jsommers/cbook (Original work published 2017)