Ejercicios: Testing, Variables Globales y Estructura de Código
Pruebas unitarias, eliminación de variables globales, análisis de flujo y buenas prácticas
Acerca de¶
El testing y el diseño estructurado de código son disciplinas indispensables para asegurar la confiabilidad de los programas en C. En este bloque no solo vas a escribir código nuevo, sino también a leer, analizar, depurar y refactorizar programas existentes.
Aprenderás a identificar el impacto negativo de las variables globales mutables,
diseñar suites de pruebas unitarias automatizadas con assert(), analizar la
cobertura de ramas de ejecución y aplicar las reglas de estilo institucional
(Allman, contratos y desacoplamiento de entrada/salida).
Capítulos de Apunte Correspondientes¶
Cuestiones de Estilo Aplicables¶
Variables Globales Prohibidas: Según la Regla 0x2004h, está estrictamente prohibido el uso de variables globales.
Una aserción por función de prueba: Según la Regla 0x2006h, cada función de test debe validar un escenario o comportamiento específico.
Desacoplamiento de E/S: En conformidad con la Regla 0x2002h, las funciones de cálculo deben ser puras y no contener
printfniscanf.Cláusulas de Guarda: Según la Regla 0x2001h, se deben usar retornos anticipados para evitar anidaciones profundas.
Estilo Allman: Según la Regla 0x000Bh, las llaves deben colocarse en su propia línea alineadas con el bloque.
1. Conceptos Fundamentales de Testing y Aserciones¶
Ejercicio 4.1 - Anatomía de una aserción ⭐☆☆☆☆¶
Solution to Exercise 1
Se incluye
<assert.h>.Si la condición es
true, el programa continúa su ejecución normal sin ninguna interrupción ni mensaje.Si la condición es
false, la macroassertaborta inmediatamente el programa conSIGABRTe imprime enstderrel nombre del archivo, el número de línea, la función y el texto exacto de la condición que falló.
Ejercicio 4.2 - Patrón AAA (Arrange - Act - Assert) ⭐☆☆☆☆¶
Solution to Exercise 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14void test_calcular_promedio_tres_notas(void) { // 1. Arrange (Preparar): Definición de datos de entrada y resultado esperado int nota1 = 8; int nota2 = 6; int nota3 = 10; double esperado = 8.0; // 2. Act (Actuar): Invocación de la función bajo prueba double resultado = calcular_promedio(nota1, nota2, nota3); // 3. Assert (Verificar): Comprobación de que el resultado coincide con lo esperado assert(resultado == esperado); }
Ejercicio 4.3 - Refactorización de pruebas acumuladas ⭐⭐☆☆☆¶
Solution to Exercise 3
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#include <assert.h> void test_sumar_positivos(void) { assert(sumar(2, 3) == 5); } void test_sumar_opuestos(void) { assert(sumar(-1, 1) == 0); } void test_restar_positivo_mayor(void) { assert(restar(10, 4) == 6); } void test_restar_resultado_negativo(void) { assert(restar(3, 8) == -5); } void test_multiplicar_positivos(void) { assert(multiplicar(3, 4) == 12); } void correr_todas_las_pruebas(void) { test_sumar_positivos(); test_sumar_opuestos(); test_restar_positivo_mayor(); test_restar_resultado_negativo(); test_multiplicar_positivos(); }
Ejercicio 4.4 - Efectos colaterales prohibidos dentro de assert ⭐⭐☆☆☆¶
Solution to Exercise 4
Cuando se define NDEBUG, el preprocesador reemplaza todas las macros
assert(x) por nada (bloque vacío ((void)0)).
En consecuencia, las llamadas a procesar_siguiente_item(&idx) desaparecen
por completo del código ejecutable, por lo que idx nunca se incrementa y el
programa finaliza imprimiendo Índice final: 0 en lugar de 2.
Forma correcta:
int idx = 0;
int r1 = procesar_siguiente_item(&idx);
assert(r1 == 0);
int r2 = procesar_siguiente_item(&idx);
assert(r2 == 1);Ejercicio 4.5 - Particiones de Equivalencia ⭐⭐☆☆☆¶
Solution to Exercise 5
Las particiones de equivalencia mínimas son:
Inválida negativa:
edad = -5Esperado:-1Menor de edad:
edad = 10Esperado:1Adulto:
edad = 30Esperado:2Adulto mayor:
edad = 70Esperado:3Inválida excesiva:
edad = 150Esperado:-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18#include <assert.h> int clasificar_edad(int edad) { if (edad < 0 || edad > 130) { return -1; } if (edad < 18) { return 1; } if (edad <= 64) { return 2; } return 3; }
Ejercicio 4.6 - Análisis de Valores de Borde (Boundary Values) ⭐⭐☆☆☆¶
Solution to Exercise 6
Valores de borde críticos:
Límite inferior de validez:
-1(inválido) y0(válido, menor).Frontera menor/adulto:
17(menor) y18(adulto).Frontera adulto/mayor:
64(adulto) y65(mayor).Límite superior de validez:
130(mayor) y131(inválido).
1 2 3 4 5 6 7 8 9 10 11void test_bordes_clasificar_edad(void) { assert(clasificar_edad(-1) == -1); assert(clasificar_edad(0) == 1); assert(clasificar_edad(17) == 1); assert(clasificar_edad(18) == 2); assert(clasificar_edad(64) == 2); assert(clasificar_edad(65) == 3); assert(clasificar_edad(130) == 3); assert(clasificar_edad(131) == -1); }
Ejercicio 4.7 - Comparación de punto flotante en testing ⭐⭐☆☆☆¶
Solution to Exercise 7
En aritmética binaria de punto flotante (IEEE 754), números como 0.1 o
1.0/3.0 no tienen representación exacta finita, lo que produce pequeñas
discrepancias en los últimos decimales.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15#include <assert.h> #include <math.h> #include <stdbool.h> bool son_cercanos(double a, double b, double epsilon) { return fabs(a - b) < epsilon; } void test_suma_decimales(void) { double resultado = 0.1 + 0.2; double esperado = 0.3; assert(son_cercanos(resultado, esperado, 1e-9)); }
Ejercicio 4.8 - Aserciones defensivas vs Validación de Usuario ⭐☆☆☆☆¶
Solution to Exercise 8
Validación de Usuario (
if/ manejo de errores): Es obligatoria para entradas externas incontrolables (teclado, archivos, red). El programa debe manejar el error con gracia, avisar al usuario y recuperarse.Aserciones (
assert): Son herramientas de depuración para detectar bugs de lógica del programador (violaciones de contrato que “nunca deberían ocurrir” si el código estuviera bien escrito).
Ejercicio 4.9 - Función pura para cálculo de descuento ⭐⭐☆☆☆¶
Solution to Exercise 9
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#include <assert.h> #include <stdbool.h> double aplicar_descuento(double precio_base, double porcentaje) { if (precio_base < 0.0 || porcentaje < 0.0 || porcentaje > 100.0) { return -1.0; } return precio_base * (1.0 - (porcentaje / 100.0)); } void test_descuento_normal(void) { assert(aplicar_descuento(200.0, 25.0) == 150.0); } void test_descuento_cero(void) { assert(aplicar_descuento(100.0, 0.0) == 100.0); } void test_descuento_total(void) { assert(aplicar_descuento(100.0, 100.0) == 0.0); } void test_descuento_precio_invalido(void) { assert(aplicar_descuento(-50.0, 10.0) == -1.0); } void test_descuento_porcentaje_invalido(void) { assert(aplicar_descuento(100.0, 150.0) == -1.0); }
Ejercicio 4.10 - Estructura de Runner de Tests sin bibliotecas ⭐⭐☆☆☆¶
Solution to Exercise 10
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#include <assert.h> #include <stdio.h> int test_cuadrado_positivo(void) { assert(2 * 2 == 4); return 1; } int test_cuadrado_cero(void) { assert(0 * 0 == 0); return 1; } int main(void) { int pasados = 0; int totales = 0; totales++; pasados += test_cuadrado_positivo(); totales++; pasados += test_cuadrado_cero(); printf("Resultados: %d/%d tests aprobados.\n", pasados, totales); return (pasados == totales) ? 0 : 1; }
2. Variables Globales y Efectos Colaterales¶
Ejercicio 4.11 - El misterio del test que falla según el orden ⭐⭐⭐☆☆¶
Solution to Exercise 11
total_acumulado es una variable global mutable. Al terminar test_uno(), su
valor queda en 15. Cuando inicia test_dos(), acumular(20) suma 20 a 15,
resultando en 35 en lugar de 20, provocando la caída del assert.
Refactorización a función pura:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19int acumular(int total_actual, int valor) { return total_actual + valor; } void test_uno(void) { int total = 0; total = acumular(total, 5); total = acumular(total, 10); assert(total == 15); } void test_dos(void) { int total = 0; total = acumular(total, 20); assert(total == 20); }
Ejercicio 4.12 - Sombreado de Variables (Shadowing) ⭐⭐☆☆☆¶
Solution to Exercise 12
Imprime: Resultado: 10, Factor: 10.
La variable local factor = 2 dentro de multiplicar oculta (“ensombrece”) a
la variable global factor = 10 durante el alcance de la función. Al salir de
la función, la global permanece inalterada con valor 10. La bandera -Wshadow
alerta explícitamente sobre estas declaraciones ambiguas.
Ejercicio 4.13 - Refactorización de Cuenta Bancaria con Estado Global ⭐⭐⭐☆☆¶
Solution to Exercise 13
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 <assert.h> #include <stdbool.h> double depositar(double saldo_actual, double monto) { if (monto <= 0.0 || saldo_actual < 0.0) { return saldo_actual; } return saldo_actual + monto; } double extraer(double saldo_actual, double monto, bool *exito) { if (monto <= 0.0 || monto > saldo_actual) { *exito = false; return saldo_actual; } *exito = true; return saldo_actual - monto; } void test_operaciones_bancarias_puras(void) { double saldo = 1000.0; bool ok = false; saldo = depositar(saldo, 500.0); assert(saldo == 1500.0); saldo = extraer(saldo, 200.0, &ok); assert(ok == true); assert(saldo == 1300.0); saldo = extraer(saldo, 2000.0, &ok); assert(ok == false); assert(saldo == 1300.0); }
Ejercicio 4.14 - Clasificación de funciones: Puras vs Impuras ⭐⭐☆☆☆¶
Solution to Exercise 14
duplicar(Pura): Para el mismo valor de entrada siempre devuelve el mismo resultado y no produce ningún efecto secundario.obtener_siguiente(Impura): Modifica y depende de la variable globalg_contador. Llamadas sucesivas devuelven valores distintos.sumar_con_log(Impura): Produce un efecto secundario en la consola medianteprintf.es_positivo(Pura): Cálculo determinístico basado únicamente en su parámetro.
Ejercicio 4.15 - Variable static local vs Variable Global ⭐⭐⭐☆☆¶
Solution to Exercise 15
La primera llamada retorna 1, la segunda 2, la tercera 3, etc.
Aunque su alcance está restringido a la función, su tiempo de vida es durante toda la ejecución del programa. Esto retiene estado mutable entre pruebas, imposibilitando correr tests unitarios aislados y repetibles sin reiniciar el binario.
Ejercicio 4.16 - Modificación inadvertida de bandera global ⭐⭐☆☆☆¶
Solution to Exercise 16
validar_rango(-5) detecta el error y pone hay_error = true. Pero luego
validar_paridad(4) como 4 es par, ejecuta la rama else y pone hay_error = false, borrando el error detectado previamente.
Refactorización:
1 2 3 4 5 6 7 8 9bool es_rango_valido(int x) { return (x >= 0 && x <= 100); } bool es_par(int x) { return (x % 2 == 0); }
Ejercicio 4.17 - Acoplamiento espurio entre módulos ⭐⭐⭐☆☆¶
Solution to Exercise 17
Cuando dos módulos acceden a una variable global compartida mediante extern:
No existe un contrato explícito sobre quién tiene derecho a modificarla ni cuándo.
Es imposible razonar sobre el estado de un módulo sin inspeccionar todo el código fuente del otro.
Las pruebas unitarias de un módulo requieren simular o sincronizar el estado global del otro, eliminando el aislamiento.
Ejercicio 4.18 - Conversor de Temperaturas desacoplado de estado ⭐⭐☆☆☆¶
Solution to Exercise 18
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#include <assert.h> #include <math.h> #include <stdbool.h> bool son_cercanos(double a, double b, double eps) { return fabs(a - b) < eps; } double celsius_a_fahrenheit(double c) { return (c * 9.0 / 5.0) + 32.0; } double fahrenheit_a_celsius(double f) { return (f - 32.0) * 5.0 / 9.0; } double celsius_a_kelvin(double c) { return c + 273.15; } void test_celsius_a_fahrenheit_cero(void) { assert(son_cercanos(celsius_a_fahrenheit(0.0), 32.0, 1e-6)); } void test_celsius_a_fahrenheit_ebullicion(void) { assert(son_cercanos(celsius_a_fahrenheit(100.0), 212.0, 1e-6)); } void test_celsius_a_kelvin_cero(void) { assert(son_cercanos(celsius_a_kelvin(0.0), 273.15, 1e-6)); }
Ejercicio 4.19 - Simulación de cronómetro sin estado global ⭐⭐⭐☆☆¶
Solution to Exercise 19
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#include <assert.h> int tiempo_avanzar(int tiempo_actual, int segundos_delta) { if (segundos_delta < 0) { return tiempo_actual; } return tiempo_actual + segundos_delta; } int tiempo_reiniciar(void) { return 0; } int tiempo_obtener_minutos(int tiempo_total) { return tiempo_total / 60; } int tiempo_obtener_segundos(int tiempo_total) { return tiempo_total % 60; } void test_cronometro_avance(void) { int t = tiempo_reiniciar(); t = tiempo_avanzar(t, 45); t = tiempo_avanzar(t, 30); assert(t == 75); assert(tiempo_obtener_minutos(t) == 1); assert(tiempo_obtener_segundos(t) == 15); }
Ejercicio 4.20 - Detección de fugas de estado entre funciones ⭐⭐☆☆☆¶
Solution to Exercise 20
Si una parte del programa llama a multiplicar_y_guardar(3, 4), el resultado
esperado es 12. Pero si otra parte del programa llama a
multiplicar_y_guardar(10, 2) antes de que la primera lea ultimo_resultado,
el valor se sobrescribe a 20, corrompiendo los datos del primer llamador.
Ejercicio 4.21 - Calculadora con memoria local vs global ⭐⭐⭐☆☆¶
Solution to Exercise 21
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 40 41 42#include <assert.h> #include <stdbool.h> double calc_sumar(double acumulador, double valor) { return acumulador + valor; } double calc_restar(double acumulador, double valor) { return acumulador - valor; } double calc_multiplicar(double acumulador, double valor) { return acumulador * valor; } double calc_dividir(double acumulador, double valor, bool *ok) { if (valor == 0.0) { *ok = false; return acumulador; } *ok = true; return acumulador / valor; } void test_secuencia_calculadora(void) { double acc = 0.0; bool ok = false; acc = calc_sumar(acc, 50.0); acc = calc_multiplicar(acc, 2.0); acc = calc_restar(acc, 20.0); acc = calc_dividir(acc, 4.0, &ok); assert(ok == true); assert(acc == 20.0); }
Ejercicio 4.22 - Máquina de Estados Finita Pura ⭐⭐⭐☆☆¶
Solution to Exercise 22
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#include <assert.h> #include <ctype.h> int transicion_estado(int estado_actual, char entrada) { if (estado_actual == 0) { return isalpha(entrada) ? 1 : -1; } if (estado_actual == 1) { return isdigit(entrada) ? 2 : 1; } if (estado_actual == 2) { return 2; } return -1; } void test_fsm_camino_exitoso(void) { int estado = 0; estado = transicion_estado(estado, a); assert(estado == 1); estado = transicion_estado(estado, b); assert(estado == 1); estado = transicion_estado(estado, '9\); assert(estado == 2); }
3. Lectura, Análisis y Flujo de Control¶
Ejercicio 4.23 - Cobertura de Ramas (Branch Coverage) ⭐⭐☆☆☆¶
Solution to Exercise 4
Se requieren al menos 5 casos de prueba para cubrir todas las ramas:
asistencia_pct < 75(ej. 50, 80, 80)0parcial1 >= 60 && parcial2 >= 60(ej. 80, 70, 80)2parcial1 >= 60peroparcial2 < 60y>= 40(ej. 70, 50, 80)1parcial1 < 60peroparcial2 >= 40(ej. 30, 50, 80)1Ambos parciales
< 40con asistencia (ej. 30, 20, 80)0
Ejercicio 4.24 - Detección de Código Muerto (Dead Code) ⭐⭐☆☆☆¶
Solution to Exercise 24
La línea return 0; al final es código muerto porque los tres bloques if
anteriores cubren exhaustivamente todos los números enteros posibles (, , ).
Versión refactorizada y limpia:
1 2 3 4 5 6 7 8 9 10 11 12int calcular_tarifa(int edad) { if (edad < 0) { return -1; } if (edad <= 12) { return 50; } return 100; }
Ejercicio 4.25 - Cortocircuito Lógico y Orden de Evaluación ⭐⭐☆☆☆¶
Solution to Exercise 25
Imprime:
Llamadas tras primer if: 0
Rama 2
Llamadas tras segundo if: 0En
a != 0 && incrementar(), comoa != 0es falso, el operador&&evalúa a falso sin evaluar la segunda parte.En
b == 1 || incrementar(), comob == 1es verdadero, el operador||evalúa a verdadero sin evaluar la segunda parte. En ningún caso se ejecutaincrementar().
Ejercicio 4.26 - Cláusulas de Guarda vs Código en Flecha ⭐⭐☆☆☆¶
Solution to Exercise 26
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#include <assert.h> #include <stdbool.h> int procesar_pedido(int cantidad, double precio_unitario, bool cliente_vip) { if (cantidad <= 0 || precio_unitario <= 0.0) { return -1; } if (cliente_vip) { return (int)(cantidad * precio_unitario * 0.85); } return (int)(cantidad * precio_unitario); } void test_pedido_invalido_cantidad(void) { assert(procesar_pedido(0, 10.0, false) == -1); } void test_pedido_invalido_precio(void) { assert(procesar_pedido(5, -2.0, false) == -1); } void test_pedido_regular(void) { assert(procesar_pedido(10, 10.0, false) == 100); } void test_pedido_vip(void) { assert(procesar_pedido(10, 10.0, true) == 85); }
Ejercicio 4.27 - Detección de Fallthrough accidental en switch ⭐⭐☆☆☆¶
Solution to Exercise 27
En el código original falta la instrucción break; al final de cada grupo de
casos. Por lo tanto, para cualquier mes, la ejecución continúa cayendo
(fallthrough) hasta el bloque default, retornando siempre -1.
Solución:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23int dias_del_mes(int mes) { switch (mes) { case 2: return 28; case 4: case 6: case 9: case 11: return 30; case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31; default: return -1; } }
Ejercicio 4.28 - Detección de Error Off-by-one en Lazos ⭐⭐☆☆☆¶
Solution to Exercise 28
El lazo utiliza i < n, lo que omite sumar el último valor . Para ,
suma en lugar de .
Corrección:
1 2 3 4 5 6 7 8 9 10 11 12 13int suma_primeros_n(int n) { if (n <= 0) { return 0; } int total = 0; for (int i = 1; i <= n; i++) { total += i; } return total; }
Ejercicio 4.29 - Desacople de Entrada/Salida para Testabilidad ⭐⭐⭐☆☆¶
Solution to Exercise 29
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#include <assert.h> #include <stdbool.h> float calcular_imc(float peso, float altura) { if (peso <= 0.0f || altura <= 0.0f) { return -1.0f; } return peso / (altura * altura); } int clasificar_imc(float imc) { if (imc <= 0.0f) { return -1; // Inválido } if (imc < 18.5f) { return 1; // Bajo peso } if (imc < 25.0f) { return 2; // Normal } return 3; // Sobrepeso } void test_calculo_y_clasificacion_imc(void) { float imc = calcular_imc(70.0f, 1.75f); assert(imc > 22.8f && imc < 22.9f); assert(clasificar_imc(imc) == 2); assert(clasificar_imc(16.0f) == 1); assert(clasificar_imc(28.0f) == 3); assert(clasificar_imc(-1.0f) == -1); }
Ejercicio 4.30 - Tabla de Traza de Ejecución (Trace Table) ⭐⭐☆☆☆¶
Solution to Exercise 30
| Iteración | a (inicio) | b (inicio) | temp (b) | a % b | b (nuevo) | a (nuevo) | Condición (b != 0) |
|---|---|---|---|---|---|---|---|
| Inicio | 18 | 12 | - | - | - | - | true |
| 1 | 18 | 12 | 12 | 6 | 6 | 12 | true |
| 2 | 12 | 6 | 6 | 0 | 0 | 6 | false |
| Fin | 6 | 0 | - | - | - | - | Retorna 6 |
Ejercicio 4.31 - Principio de Responsabilidad Única (SRP) ⭐⭐☆☆☆¶
Solution to Exercise 31
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 40 41 42 43 44 45 46 47 48 49#include <assert.h> int calcular_suma(const int arr[], int n) { int total = 0; for (int i = 0; i < n; i++) { total += arr[i]; } return total; } int encontrar_maximo(const int arr[], int n) { if (n <= 0) { return 0; } int max = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } int contar_negativos(const int arr[], int n) { int cont = 0; for (int i = 0; i < n; i++) { if (arr[i] < 0) { cont++; } } return cont; } void test_funciones_atomicas(void) { int datos[] = {3, -1, 8, -5, 2}; assert(calcular_suma(datos, 5) == 7); assert(encontrar_maximo(datos, 5) == 8); assert(contar_negativos(datos, 5) == 2); }
Ejercicio 4.32 - Testeo de Año Bisiesto Exhaustivo ⭐⭐☆☆☆¶
Solution to Exercise 32
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#include <assert.h> #include <stdbool.h> bool es_bisiesto(int anio) { if (anio <= 0) { return false; } return ((anio % 4 == 0 && anio % 100 != 0) || (anio % 400 == 0)); } void test_bisiesto_multiplo_4(void) { assert(es_bisiesto(2024) == true); } void test_no_bisiesto_comun(void) { assert(es_bisiesto(2023) == false); } void test_no_bisiesto_fin_de_siglo(void) { assert(es_bisiesto(1900) == false); } void test_bisiesto_cuatrisecular(void) { assert(es_bisiesto(2000) == true); } void test_bisiesto_invalido(void) { assert(es_bisiesto(-400) == false); }
Ejercicio 4.33 - Detección de mutación en arreglos de solo lectura ⭐⭐☆☆☆¶
Solution to Exercise 33
El calificador const garantiza contractualmente que la función es de solo
lectura. Si por error un programador escribe arr[i] = 0; en lugar de if (arr[i] == 0), el compilador rechaza la compilación con un error inmediato
(assignment of read-only location), evitando corromper los datos del llamador.
Ejercicio 4.34 - Búsqueda de errores lógicos con aserciones de invariante ⭐⭐⭐☆☆¶
Solution to Exercise 34
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16#include <assert.h> void test_division_con_invariante(void) { int c = 0; int r = 0; division_lenta(10, 3, &c, &r); assert(c == 3 && r == 1); division_lenta(20, 5, &c, &r); assert(c == 4 && r == 0); division_lenta(3, 7, &c, &r); assert(c == 0 && r == 3); }
4. Estilo, Estructura y Contratos¶
Ejercicio 4.35 - Verificación de Estilo Allman ⭐☆☆☆☆¶
Solution to Exercise 35
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20#include <assert.h> int valor_absoluto(int n) { if (n < 0) { return -n; } else { return n; } } void test_valor_absoluto(void) { assert(valor_absoluto(-12) == 12); assert(valor_absoluto(0) == 0); assert(valor_absoluto(15) == 15); }
Ejercicio 4.36 - Eliminación de Números Mágicos ⭐⭐☆☆☆¶
Solution to Exercise 36
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#include <assert.h> #define PESO_LIMITE_LIVIANO 5.0 #define PESO_LIMITE_MEDIO 20.0 #define TARIFA_BASE_LIVIANO 500.0 #define TARIFA_BASE_MEDIO 1700.0 #define COSTO_KG_MEDIO 80.0 #define COSTO_KG_PESADO 120.0 double calcular_costo_envio(double peso) { if (peso <= 0.0) { return -1.0; } if (peso <= PESO_LIMITE_LIVIANO) { return TARIFA_BASE_LIVIANO; } if (peso <= PESO_LIMITE_MEDIO) { return TARIFA_BASE_LIVIANO + (peso - PESO_LIMITE_LIVIANO) * COSTO_KG_MEDIO; } return TARIFA_BASE_MEDIO + (peso - PESO_LIMITE_MEDIO) * COSTO_KG_PESADO; } void test_costo_envio_limites(void) { assert(calcular_costo_envio(5.0) == 500.0); assert(calcular_costo_envio(10.0) == 900.0); assert(calcular_costo_envio(20.0) == 1700.0); }
Ejercicio 4.37 - Documentación de Contratos con Doxygen ⭐⭐☆☆☆¶
Solution to Exercise 37
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 40 41 42 43 44#include <assert.h> #include <stdbool.h> /** * @brief Calcula la potencia entera de una base elevada a un exponente no negativo. * * @param base Base entera. * @param exponente Exponente entero. * @param ok Puntero a booleano de estado de éxito. * * @pre `exponente >= 0` * @pre `ok != NULL` * * @returns El resultado de base^exponente si ok es true, o 0 en caso de error. * * @post Si exponente == 0, el retorno es 1 y *ok == true. */ int calcular_potencia_entera(int base, int exponente, bool *ok) { if (ok == NULL || exponente < 0) { if (ok != NULL) { *ok = false; } return 0; } *ok = true; int resultado = 1; for (int i = 0; i < exponente; i++) { resultado *= base; } return resultado; } void test_potencia_contrato(void) { bool ok = false; assert(calcular_potencia_entera(2, 3, &ok) == 8 && ok); assert(calcular_potencia_entera(5, 0, &ok) == 1 && ok); calcular_potencia_entera(2, -1, &ok); assert(ok == false); }
Ejercicio 4.38 - Inicialización obligatoria de variables ⭐☆☆☆☆¶
Solution to Exercise 38
Al no inicializarse, contador contiene un valor residual indeterminado
proveniente de la memoria de la pila. El incremento contador++ genera
comportamiento indefinido y resultados erróneos impredecibles.
Corrección:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16int contar_multiplos(const int arr[], int n, int divisor) { if (divisor == 0) { return -1; } int contador = 0; for (int i = 0; i < n; i++) { if (arr[i] % divisor == 0) { contador++; } } return contador; }
Ejercicio 4.39 - Organización de Archivo .c Estándar ⭐☆☆☆☆¶
Solution to Exercise 39
El orden estándar según la Regla 0x5005h es:
Inclusiones de bibliotecas estándar (
#include <stdio.h>).Inclusiones de cabeceras del proyecto (
#include "modulo.h").Definición de macros y constantes (
#define).Prototipos de funciones privadas (
static int auxiliar(void);).Función
main(si es el archivo principal) o funciones públicas.Implementación de funciones privadas (
static).
Ejercicio 4.40 - Red de Seguridad: Default en Switch ⭐⭐☆☆☆¶
Solution to Exercise 40
El bloque default actúa como una red de seguridad defensiva ante datos
imprevistos, corrupción de memoria o adición futura de nuevos valores en el
dominio que no fueron contemplados.
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#include <assert.h> int obtener_dias_trimestre(int trimestre) { switch (trimestre) { case 1: return 90; case 2: return 91; case 3: return 92; case 4: return 92; default: return -1; // Captura de valores inválidos } } void test_trimestres(void) { assert(obtener_dias_trimestre(1) == 90); assert(obtener_dias_trimestre(5) == -1); assert(obtener_dias_trimestre(-1) == -1); }
Ejercicio 4.41 - Nombres de Variables Autoexplicativos ⭐☆☆☆☆¶
Solution to Exercise 41
1 2 3 4 5 6 7 8 9 10 11 12 13#include <assert.h> double calcular_precio_final(double precio_base, double porcentaje_descuento, double porcentaje_impuesto) { double precio_con_descuento = precio_base - (precio_base * (porcentaje_descuento / 100.0)); double precio_final = precio_con_descuento + (precio_con_descuento * (porcentaje_impuesto / 100.0)); return precio_final; } void test_precio_final(void) { assert(calcular_precio_final(100.0, 10.0, 21.0) == 108.9); }
Ejercicio 4.42 - Prohibición de Reutilización de Variables ⭐⭐☆☆☆¶
Solution to Exercise 42
Reutilizar la misma variable para propósitos disímiles dificulta la lectura, impide agregar aserciones claras sobre estados intermedios y propaga errores si un bloque asume que la variable conserva su valor anterior.
Ejercicio 4.43 - Flags de Compilación Obligatorios ⭐☆☆☆☆¶
Solution to Exercise 43
-Wall: Habilita todas las advertencias comunes sobre construcciones dudosas (variables no usadas, conversiones implícitas riesgosas).-Wextra: Habilita advertencias adicionales más rigurosas (comparaciones con signo/sin signo, parámetros no utilizados).-Werror: Trata todas las advertencias como errores de compilación, impidiendo generar el binario si existen problemas.-Wpedantic: Exige estricto apego al estándar ISO de C, rechazando extensiones propietarias del compilador.
Ejercicio 4.44 - Funciones Auxiliares Privadas static ⭐⭐☆☆☆¶
Solution to Exercise 44
El calificador static en una función a nivel de archivo restringe su
visibilidad exclusivamente a la unidad de traducción actual (internal
linkage). Esto encapsula los detalles de implementación internos y previene
errores de símbolos duplicados en tiempo de enlace (linker collision).
5. Suites de Pruebas Integradoras y Diagnóstico¶
Ejercicio 4.45 - Suite de Pruebas: Validador de Fecha ⭐⭐⭐☆☆¶
Solution to Exercise 45
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57#include <assert.h> #include <stdbool.h> bool es_bisiesto_aux(int anio) { return (anio % 4 == 0 && anio % 100 != 0) || (anio % 400 == 0); } bool es_fecha_valida(int dia, int mes, int anio) { if (anio <= 0 || mes < 1 || mes > 12 || dia < 1) { return false; } int dias_max = 31; if (mes == 4 || mes == 6 || mes == 9 || mes == 11) { dias_max = 30; } else if (mes == 2) { dias_max = es_bisiesto_aux(anio) ? 29 : 28; } return dia <= dias_max; } void test_fecha_31_dias(void) { assert(es_fecha_valida(31, 1, 2024) == true); assert(es_fecha_valida(32, 1, 2024) == false); } void test_fecha_30_dias(void) { assert(es_fecha_valida(30, 4, 2024) == true); assert(es_fecha_valida(31, 4, 2024) == false); } void test_febrero_bisiesto(void) { assert(es_fecha_valida(29, 2, 2024) == true); assert(es_fecha_valida(29, 2, 2023) == false); } void test_fecha_mes_invalido(void) { assert(es_fecha_valida(15, 13, 2024) == false); assert(es_fecha_valida(15, 0, 2024) == false); } void correr_pruebas_fechas(void) { test_fecha_31_dias(); test_fecha_30_dias(); test_febrero_bisiesto(); test_fecha_mes_invalido(); }
Ejercicio 4.46 - Suite de Pruebas: Tarifa de Estacionamiento ⭐⭐⭐☆☆¶
Solution to Exercise 46
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 40 41 42 43 44 45 46 47 48 49#include <assert.h> #include <math.h> #include <stdbool.h> double calcular_estacionamiento(int minutos) { if (minutos <= 0) { return 0.0; } int horas = (minutos + 59) / 60; // Redondeo hacia arriba a la hora siguiente double total = 200.0; if (horas > 1) { total += (horas - 1) * 150.0; } if (horas > 6) { total *= 0.90; } if (total > 1200.0) { total = 1200.0; } return total; } void test_estacionamiento_primera_hora(void) { assert(calcular_estacionamiento(30) == 200.0); assert(calcular_estacionamiento(60) == 200.0); } void test_estacionamiento_horas_adicionales(void) { assert(calcular_estacionamiento(61) == 350.0); // 2 horas assert(calcular_estacionamiento(180) == 500.0); // 3 horas } void test_estacionamiento_descuento_larga_estadia(void) { // 7 horas = 200 + 6*150 = 1100. Con 10% desc = 990. assert(calcular_estacionamiento(420) == 990.0); } void test_estacionamiento_tope_maximo(void) { assert(calcular_estacionamiento(1440) == 1200.0); }
Ejercicio 4.47 - Diagnóstico de Aserción Fallida ⭐⭐☆☆☆¶
Solution to Exercise 47
Si se pasa a = 0 y b = 5, la primera llamada no entra en b == 0. Llama a
mcd(5, 0 % 5) que es mcd(5, 0), el cual entra en b == 0 y retorna 5.
Si la función original no contempla a < 0 o b < 0, los módulos con signo en
C pueden generar números negativos. La corrección robusta es:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15#include <assert.h> #include <stdlib.h> int mcd(int a, int b) { a = abs(a); b = abs(b); while (b != 0) { int temp = b; b = a % b; a = temp; } return a; }
Ejercicio 4.48 - Test de Regresión ⭐⭐☆☆☆¶
Solution to Exercise 48
Procedimiento:
Reproducir el bug reportado.
Escribir una prueba unitaria específica que falle debido a ese bug.
Modificar el código fuente hasta que la prueba pase.
Incorporar la nueva prueba a la suite continua del proyecto.
Escribir la prueba primero confirma científicamente la existencia del bug y asegura que la corrección realmente solucione la causa raíz sin depender de verificaciones manuales.
Ejercicio 4.49 - Suite de Pruebas: Desglose de Billetes ⭐⭐⭐☆☆¶
Solution to Exercise 49
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#include <assert.h> #include <stdbool.h> bool desglosar_monto(int monto, int *b1000, int *b500, int *b200, int *b100) { if (monto <= 0 || monto % 100 != 0 || !b1000 || !b500 || !b200 || !b100) { return false; } *b1000 = monto / 1000; monto %= 1000; *b500 = monto / 500; monto %= 500; *b200 = monto / 200; monto %= 200; *b100 = monto / 100; return true; } void test_desglose_combinado(void) { int mil = 0, quin = 0, dosc = 0, cien = 0; bool ok = desglosar_monto(3800, &mil, &quin, &dosc, &cien); assert(ok == true); assert(mil == 3 && quin == 1 && dosc == 1 && cien == 1); } void test_desglose_monto_invalido(void) { int mil = 0, quin = 0, dosc = 0, cien = 0; assert(desglosar_monto(350, &mil, &quin, &dosc, &cien) == false); assert(desglosar_monto(-100, &mil, &quin, &dosc, &cien) == false); }
Ejercicio 4.50 - Suite de Pruebas: Validador de Números Primos ⭐⭐⭐☆☆¶
Solution to Exercise 50
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 40 41 42 43 44 45 46 47 48#include <assert.h> #include <stdbool.h> bool es_primo(int n) { if (n <= 1) { return false; } if (n == 2) { return true; } if (n % 2 == 0) { return false; } for (int i = 3; i * i <= n; i += 2) { if (n % i == 0) { return false; } } return true; } void test_primos_menores_o_iguales_uno(void) { assert(es_primo(0) == false); assert(es_primo(1) == false); assert(es_primo(-7) == false); } void test_primos_basicos(void) { assert(es_primo(2) == true); assert(es_primo(3) == true); assert(es_primo(13) == true); assert(es_primo(997) == true); } void test_compuestos_impares(void) { assert(es_primo(9) == false); assert(es_primo(25) == false); assert(es_primo(49) == false); }
Ejercicio 4.51 - Testeo de Funciones con Códigos de Error ⭐⭐⭐☆☆¶
Solution to Exercise 51
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 40 41 42 43 44 45 46 47 48 49 50#include <assert.h> #include <stddef.h> int calcular_velocidad(double distancia, double tiempo, double *velocidad) { if (velocidad == NULL) { return 3; } if (distancia < 0.0) { return 1; } if (tiempo <= 0.0) { return 2; } *velocidad = distancia / tiempo; return 0; } void test_velocidad_exito(void) { double v = 0.0; int codigo = calcular_velocidad(100.0, 2.0, &v); assert(codigo == 0); assert(v == 50.0); } void test_velocidad_distancia_invalida(void) { double v = -99.0; int codigo = calcular_velocidad(-10.0, 2.0, &v); assert(codigo == 1); assert(v == -99.0); } void test_velocidad_tiempo_invalido(void) { double v = -99.0; int codigo = calcular_velocidad(100.0, 0.0, &v); assert(codigo == 2); assert(v == -99.0); } void test_velocidad_puntero_nulo(void) { int codigo = calcular_velocidad(100.0, 2.0, NULL); assert(codigo == 3); }
Ejercicio 4.52 - Suite de Pruebas: Palíndromo Numérico ⭐⭐☆☆☆¶
Solution to Exercise 52
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 40#include <assert.h> #include <stdbool.h> int invertir_entero(int n) { int invertido = 0; while (n > 0) { invertido = (invertido * 10) + (n % 10); n /= 10; } return invertido; } bool es_numero_capicua(int n) { if (n < 0) { return false; } if (n < 10) { return true; } return n == invertir_entero(n); } void test_capicua_un_digito(void) { assert(es_numero_capicua(0) == true); assert(es_numero_capicua(7) == true); } void test_capicua_multiples_digitos(void) { assert(es_numero_capicua(1221) == true); assert(es_numero_capicua(12321) == true); assert(es_numero_capicua(123) == false); assert(es_numero_capicua(-121) == false); }
Ejercicio 4.53 - Suite de Pruebas: Validador de Triángulo ⭐⭐⭐☆☆¶
Solution to Exercise 53
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 40 41 42#include <assert.h> #include <math.h> #include <stdbool.h> bool lados_cercanos(double x, double y) { return fabs(x - y) < 1e-6; } int clasificar_triangulo(double a, double b, double c) { if (a <= 0.0 || b <= 0.0 || c <= 0.0) { return 0; } if (a + b <= c || a + c <= b || b + c <= a) { return 0; // Desigualdad triangular violada } if (lados_cercanos(a, b) && lados_cercanos(b, c)) { return 1; // Equilátero } if (lados_cercanos(a, b) || lados_cercanos(a, c) || lados_cercanos(b, c)) { return 2; // Isósceles } return 3; // Escaleno } void test_triangulo_invalido(void) { assert(clasificar_triangulo(1.0, 2.0, 3.0) == 0); // Degenerado assert(clasificar_triangulo(-1.0, 2.0, 2.0) == 0); } void test_triangulo_tipos(void) { assert(clasificar_triangulo(5.0, 5.0, 5.0) == 1); assert(clasificar_triangulo(5.0, 5.0, 8.0) == 2); assert(clasificar_triangulo(3.0, 4.0, 5.0) == 3); }
Ejercicio 4.54 - Suite de Pruebas: Conversión de Segundos a Tiempo ⭐⭐☆☆☆¶
Solution to Exercise 54
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 <assert.h> #include <stdbool.h> #include <stddef.h> bool segundos_a_tiempo(int total_segundos, int *horas, int *minutos, int *segundos) { if (total_segundos < 0 || !horas || !minutos || !segundos) { return false; } *horas = total_segundos / 3600; int resto = total_segundos % 3600; *minutos = resto / 60; *segundos = resto % 60; return true; } void test_segundos_a_tiempo_normal(void) { int h = 0, m = 0, s = 0; bool ok = segundos_a_tiempo(3661, &h, &m, &s); assert(ok == true); assert(h == 1 && m == 1 && s == 1); } void test_segundos_a_tiempo_cero(void) { int h = 0, m = 0, s = 0; bool ok = segundos_a_tiempo(0, &h, &m, &s); assert(ok == true); assert(h == 0 && m == 0 && s == 0); } void test_segundos_a_tiempo_invalido(void) { int h = 0, m = 0, s = 0; assert(segundos_a_tiempo(-10, &h, &m, &s) == false); assert(segundos_a_tiempo(100, NULL, &m, &s) == false); }
Ejercicio 4.55 - Suite Integradora de Evaluación de Sistema de Calificaciones ⭐⭐⭐☆☆¶
Solution to Exercise 55
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97#include <assert.h> #include <stdbool.h> #include <stdio.h> bool validar_nota(int nota) { return (nota >= 1 && nota <= 10); } double calcular_promedio_curso(const int notas[], int cantidad) { if (notas == NULL || cantidad <= 0) { return -1.0; } int suma = 0; for (int i = 0; i < cantidad; i++) { if (!validar_nota(notas[i])) { return -1.0; } suma += notas[i]; } return (double)suma / cantidad; } int contar_aprobados(const int notas[], int cantidad, int nota_minima) { if (notas == NULL || cantidad <= 0 || !validar_nota(nota_minima)) { return -1; } int aprobados = 0; for (int i = 0; i < cantidad; i++) { if (notas[i] >= nota_minima) { aprobados++; } } return aprobados; } int estado_final_alumno(int p1, int p2, int rec) { if (!validar_nota(p1) || !validar_nota(p2)) { return -1; } if (p1 >= 4 && p2 >= 4) { return (p1 + p2) / 2; } if (!validar_nota(rec)) { return 0; // Reprobado sin recuperatorio válido } if (p1 < 4 && rec >= 4 && p2 >= 4) { return (rec + p2) / 2; } if (p2 < 4 && rec >= 4 && p1 >= 4) { return (p1 + rec) / 2; } return 0; } void test_sistema_calificaciones(void) { int notas[] = {7, 8, 4, 10, 6}; assert(calcular_promedio_curso(notas, 5) == 7.0); assert(contar_aprobados(notas, 5, 6) == 4); // Caso directo aprobado assert(estado_final_alumno(8, 6, 0) == 7); // Caso recupera primer parcial assert(estado_final_alumno(2, 8, 6) == 7); // Caso recupera segundo parcial assert(estado_final_alumno(7, 3, 5) == 6); // Caso reprueba recuperatorio assert(estado_final_alumno(2, 8, 3) == 0); // Caso notas inválidas assert(estado_final_alumno(12, 8, 5) == -1); } int main(void) { test_sistema_calificaciones(); printf("¡Suite integradora ejecutada con 100%% de éxito!\n"); return 0; }