01 · Punto de partida
¿El código obedece al modelo?
En el Tema 34 leímos tablas y gráficos como si el programa fuera fiel. Pero todo análisis pende de una pregunta previa: ¿el simulador implementa de verdad las reglas que escribimos en el modelo? Un signo cambiado, un tope olvidado o una agenda desordenada producen resultados nítidos, estables… y falsos.
Verificar es auditar el programa contra el modelo, con casos de respuesta conocida. No pregunta si el modelo es bueno (eso es validación, Tema 36): pregunta si el código lo ejecuta sin traicionarlo.
- ¿Cada regla del modelo tiene su caso testigo?
- ¿Los invariantes se cumplen en cada paso?
- ¿Dos motores distintos empatan ante lo mismo?
- ¿Un cambio mínimo rompe algún testigo?
02 · Vocabulario mínimo
Testigo, invariante y traza
Testigo
Caso chico de respuesta calculada a mano: si el programa no lo empata, hay bug.
Invariante
Condición que vale siempre: nivel entre 0 y tope, agenda ordenada, stock que conserva.
Traza
Paso a paso registrado de una corrida testigo: permite ver dónde se desvía el programa.
| Pieza | Testigo mínimo | Qué delata |
|---|---|---|
| Transición | Entradas [3, 3, 3] con salida 2 | Signos y acumulaciones mal hechas |
| Contorno | Nivel que intenta pasar el tope o bajar de 0 | Topes y pisos olvidados |
| Agenda | Eventos que entran desordenados | Cola que no ordena por tiempo |
| Azar | Misma semilla, misma historia | Generador global contaminado |
03 · Tres técnicas
Cómo se verifica un simulador
- 1Casos testigo.
Entradas chicas con salida a mano. Rápidos, exactos, automáticos con
assert. - 2Invariantes.
Afirmaciones que el programa chequea en cada paso: rangos, conservación, orden de la agenda.
- 3Doble vía.
Dos implementaciones (paso fijo vs eventos, o Python vs planilla) deben empatar ante lo mismo.
Probar a ojo
Lento y olvidadizo
Correr, mirar el gráfico y decir «parece bien». No se repite solo, no avisa cuando algo se rompe después.
Testigos automáticos
Rápido y memorioso
Asserts que corren en un segundo y gritan ante cualquier cambio. La verificación viaja con el código.
- Verificación
- Comprobar que el programa implementa fielmente el modelo.
- Testigo
- Caso de respuesta conocida que custodia una regla.
- Invariante
- Propiedad que debe valer en todo paso de toda corrida.
- Regresión
- Batería de testigos que se corre ante cada cambio.
04 · Protocolo
La auditoría en cinco pasos
Cada pieza nace con su testigo, cada corrida chequea invariantes, cada bug se persigue en la traza, cada motor se empata con su doble vía y todo queda en una regresión que corre ante cada cambio.
05 · En Python
Testigos con assert
El testigo más barato es un assert con historia conocida. Si alguien rompe la transición, grita al instante:
Python en tu navegador. Verificar es stdlib: asserts, chequeos y generadores propios.
def simular(n0, entradas, salida, tope=40):
n = n0
h = []
for e in entradas:
n = min(max(n + e - salida, 0), tope)
h.append(n)
return h
assert simular(10, [3, 3, 3, 3], 2) == [11, 12, 13, 14]
assert simular(39, [5], 0) == [40] # el tope recorta
assert simular(1, [0], 5) == [0] # el piso sostiene
print("testigos OK")
Invariantes dentro del motor
El invariante vive en el código y vigila cada paso, no solo los casos de prueba:
def simular_vigilada(n0, entradas, salida, tope=40):
n = n0
h = []
for e in entradas:
n = min(max(n + e - salida, 0), tope)
assert 0 <= n <= tope, f"invariante roto: {n}"
h.append(n)
return h
print(simular_vigilada(10, [3, 3, 3], 2))
06 · Exploración
Laboratorio: cazar el bug del tope
Este simulador trae un bug inyectable: con el interruptor en sin tope, el nivel crece sin cota y viola el invariante. Activá el bug, subí las entradas y mirá cómo los testigos lo delatan.
Testigos contra bug
invariante: 0 ≤ nivel ≤ tope
Con el tope puesto, los 3 testigos pasan y el invariante se cumple.
Curva del nivel en 12 pasos. Línea punteada: tope 40. Sin tope, la curva lo atraviesa y los testigos fallan.
Preguntas para explorar
- Sacá el tope con entrada 5. ¿Cuántos testigos fallan y cuál es el primero en gritar?
- Bajá la entrada a 1 sin tope. ¿El bug se esconde? ¿Qué enseña eso sobre testigos «cómodos»?
- ¿Por qué la semilla no cambia este resultado? ¿Cuándo sí importaría?
Ver respuestas sugeridas
- Fallan los que tocan el tope: el nivel supera 40 y el invariante se rompe. El testigo de recorte es el primero.
- Sí: con entradas chicas el nivel nunca llega al tope y el bug duerme. Los testigos deben forzar los bordes, no el caso cómodo.
- Porque este modelo es determinístico: misma entrada, misma salida. La semilla importaría con llegadas o ruidos aleatorios.
07 · Comprensión
Confusiones frecuentes
«Si corre, está verificado»
Correr prueba sintaxis, no fidelidad. Verificado es empatar testigos calculados a mano.
«Verificado es válido»
No: verificado obedece al modelo; válido representa a la realidad. Un modelo equivocado puede estar perfectamente verificado.
«Los asserts son para novatos»
Son la regresión del profesional: corren en un segundo y custodian cada cambio futuro.
«Con un testigo alcanza»
Un testigo cubre un camino. Hacen falta bordes: tope, piso, agenda desordenada, semilla repetida.
08 · Práctica guiada
Ejercicios con Python
Ejercicio 1: escribir el testigo del tope
Completá el assert que custodia el recorte en 40.
def simular(n0, entradas, salida, tope=40):
n = n0
h = []
for e in entradas:
n = min(max(n + e - salida, 0), tope)
h.append(n)
return h
assert simular(39, [5], 0) == [40]
print("tope OK")
Ver solución razonada
Sin min(..., 40) daría [44] y el assert fallaría: el testigo delata el tope olvidado.
Ejercicio 2: vigilar el invariante
Agregá el chequeo que rompe la corrida en cuanto el nivel sale de rango.
def simular_vigilada(n0, entradas, salida, tope=40):
n = n0
for e in entradas:
n = min(max(n + e - salida, 0), tope)
assert 0 <= n <= tope
return n
print(simular_vigilada(10, [3, 3, 3], 2))
Ver solución
Devuelve 13 sin protestar. Si el invariante se violara, el mensaje señalaría el paso exacto del crimen.
Ejercicio 3: doble vía con dos motores
Empatá el motor paso a paso con su versión «de una línea» ante el mismo escenario.
Ver una posible respuesta
def motor_a(n0, entradas, salida):
n = n0
return [n := max(n + e - salida, 0) for e in entradas]
def motor_b(n0, entradas, salida):
h, n = [], n0
for e in entradas:
n = max(n + e - salida, 0)
h.append(n)
return h
assert motor_a(10, [3, 3, 3], 2) == motor_b(10, [3, 3, 3], 2)
print("doble vía OK")
Si ambas vías empatan en [11, 12, 13], el motor pasa la doble vía; si difieren, al menos una miente.
09 · Síntesis
Ideas para recordar
- Verificar es comprobar que el programa obedece al modelo, no a la realidad.
- Cada regla tiene su testigo calculado a mano y automático.
- Los invariantes vigilan cada paso; las trazas localizan el desvío.
- La doble vía empata dos implementaciones ante lo mismo.
- En Python:
assertpara testigos e invariantes, regresión ante cada cambio.
En el próximo tema cruzaremos el puente hacia la realidad: la validación.