<assert> — una configuración que comprueba su propia salida
Úsalo cuando la forma de los datos le importa a quien los recibe y prefieres que la ejecución se detenga antes que entregar un fichero que se ha desviado en silencio.
Una aserción declara una propiedad que la ejecución terminada debe cumplir. Si se cumple, no pasa nada. Si no, la ejecución se detiene con tu propia frase, antes de escribir una sola línea.
<assert that="Tracked == 700" says="cada pedido enviado debe llevar número de seguimiento"/>
Vive en <env>, junto a <uniq> y <distinct>, porque como ellos
enuncia algo que la ejecución entera debe cumplir, y no algo que una columna es.
Hay dos. that= se lee una vez, sobre números de la ejecución entera. each= se
responde en cada fila — véase Cada fila más abajo.
Qué merece la pena afirmar
No lo que la configuración ya dice. Escribiste percent="70" y afirmas el 70 por ciento:
has comprobado que TDC sabe contar.
El valor está en lo que la configuración no dice. Aquí un filtro y una condición se acumulan, y la proporción que llega al fichero no aparece en ningún sitio del texto:
<tdc>
<env count="1000" seed="orders" local="en">
<sequence name="Status"><gen type="text" value="shipped,pending" percent="70,30"/></sequence>
<sequence name="Tracking" parent="Status.shipped">
<gen type="regex" value="[A-Z]{2}[0-9]{9}" if="Status == 'shipped' && _count % 4 != 0"/>
</sequence>
<sequence name="Tracked"><gen type="stat" of="Tracking" op="count"/></sequence>
<assert that="Tracked == 700" says="every shipped order should carry a tracking number"/>
</env>
<block>
<line><data>${{Status}},${{Tracking}}</data></line>
</block>
</tdc>
tdcv2: assert failed: every shipped order should carry a tracking number Tracked == 700 with Tracked = 522
Nada más en TDC tiene una opinión sobre esta configuración. Se analiza, se valida, se ejecuta, y 178 pedidos enviados salen con el número de seguimiento vacío. Ese es exactamente el fallo para el que existe una aserción.
El código de salida es 1, así que CI se detiene ahí.
De un vistazo
| Atributo | Obligatorio | Qué hace |
|---|---|---|
that | uno de los dos | Una condición sobre valores de la ejecución entera, leída una vez, en el lenguaje de if= |
each | uno de los dos | Una condición que toda fila debe cumplir, en el mismo lenguaje |
says | sí | La frase que recibe quien lee cuando la condición no se cumple |
says= es obligatorio en ambos casos. Una aserción que salta mostrando solo su expresión
obliga a quien la lee a reconstruir, meses después y en un log de CI, para qué estaba.
that= y each= no pueden escribirse en la misma etiqueta: se comprueban un número
distinto de veces, y quien lee no podría saber a cuál de las dos se refiere la frase de
says=.
No hay bandera. Una aserción se ejecuta porque está escrita: una comprobación que hay que recordar activar es una comprobación que nadie ejecutó, sobre una configuración que parece verificada.
De dónde salen los números
that= lee columnas, y las que suele merecer la pena leer son
<gen type="stat">: un número para toda la ejecución.
<env count="500" seed="clinic" local="en">
<sequence name="Visit"><gen type="date" from="2026-01-01" to="2026-06-30" format="YYYY-MM-DD"/></sequence>
<sequence name="Follow"><gen type="date" of="Visit" plus="7..30d" format="YYYY-MM-DD"/></sequence>
<sequence name="Ward"><gen type="text" value="A,B,C" percent="50,30,20"/></sequence>
<sequence name="Rows"><gen type="stat" of="Visit" op="count"/></sequence>
<sequence name="Wards"><gen type="stat" of="Ward" op="count"/></sequence>
<assert that="Rows == _total" says="cada fila tiene fecha de visita"/>
<assert that="Wards == _total" says="cada fila tiene planta"/>
</env>
_total es el número de filas, y es el único valor interno que una aserción puede leer.
La regla que la mantiene honesta
Cada nombre en that= debe ser el mismo en todas las filas. Una columna stat lo es
por construcción. Una columna text de un solo valor lo es de hecho. Una columna sorteada
no lo es, y se rechaza:
<sequence name="Amount"><gen type="number" value="1..500"/></sequence>
<assert that="Amount > 0" says="every amount is positive"/>
tdcv2: assert ("Amount > 0"): "Amount" is not the same on every row, so this would have checked the first row and called the run verified. A whole-run assertion reads whole-run values: give it a <gen type="stat" of="Amount" op="…"/> column, or _total. To state it of every row instead, write each= rather than that=.Sin esta regla, that="Amount > 0" leería la fila 0 e informaría sobre una fila de
quinientas: una comprobación que pasó porque apenas miró, con una etiqueta que dice
«verificado». Esa es justo la enfermedad que esta función viene a curar.
Una columna que un filtro parent= deja vacía en parte de las filas se rechaza por lo
mismo: la ejecución no tiene un valor único para ella, y la condición compararía contra lo
que la fila 0 tuviera por casualidad. Resúmela con op="count".
Cada fila — each=
El rechazo de arriba no es un callejón sin salida, sino una señal. «Cada importe es
positivo» es una afirmación real, y su sitio es each=:
<sequence name="Amount"><gen type="number" value="1..500"/></sequence>
<assert each="Amount > 0" says="cada importe es positivo"/>
La condición se responde en cada fila, en el mismo lenguaje que habla if= y sobre los
mismos nombres: las columnas de esta fila más los
valores internos. Nada nuevo que aprender, y ninguna columna
stat que construir antes.
Cuando una fila la rompe, la ejecución se detiene en esa fila y dice cuál es:
<sequence name="Fee"><gen type="number" value="-3..20"/></sequence>
<assert each="Fee >= 0" says="una comisión nunca es negativa"/>
tdcv2: assert failed on row 16: una comisión nunca es negativa Fee >= 0 with Fee = -3
El código de salida es 1, igual que con that=.
Se detiene en la primera fila que falla en vez de contarlas todas. En un motor de flujo las filas anteriores ya están en disco, así que «comprobar el archivo entero y luego informar» es algo que no todos los motores podrían prometer con honestidad; y una ejecución cuyos datos ya están mal no mejora porque averigüemos cuánto. La fila que nombra es siempre la primera: las filas se comprueban en orden, cada una antes de escribirse.
Por eso una configuración con una aserción each= corre en un solo hilo. Cada worker
se queda con un rango de filas y cada uno pararía en su propia primera fila fallida, así que
la fila mostrada sería aquella a la que llegase antes algún hilo: un número distinto con la
misma configuración y la misma semilla. Un --jobs explícito lo dice y sigue en un hilo.
Por lo demás no hay consecuencia de motor: a diferencia de that=, una aserción por fila no
necesita una columna stat, así que una configuración con una sigue transmitiéndose en
flujo. Véase salidas grandes.
Lo que todavía no hace
- Afirmar un dígito de control. Ahí está la trampa: lo calculó
<compute>, y recalcularlo solo afirma que el mismo código está de acuerdo consigo mismo.
Motores
Una aserción that= lee columnas stat, y stat ya envía la configuración al motor en
memoria, así que no añade consecuencias propias. Una aserción each= tampoco, más allá de
quedarse en un hilo. Véase salidas grandes.
Véase también
stat— de dónde salen los números- Expresiones — el lenguaje en el que se escribe
that= - Unicidad — las otras declaraciones sobre la ejecución entera