Saltar al contenido
Development

Probar la parte del código que documenta su propia trampa

Por Victor Da Luz
astrotestingdev-logastro-tools

Mi paquete de enlaces de afiliados tiene un plugin de remark que reescribe los enlaces [texto](affiliate:key) a sus URLs reales y etiquetadas en tiempo de build. Su README trae una advertencia en negrita: usar la tupla [plugin, options], no la forma pre-invocada, porque Astro y unified llaman ellos mismos a la función del plugin con las opciones. Pasar un transformador ya invocado hace que unified llame a eso sin argumentos como si fuera el attacher, lo cual no hace nada en silencio en vez de reescribir algo. El build se mantiene en verde con los enlaces affiliate:key intactos en la salida.

Yo mismo escribí esa advertencia, por experiencia. Y hasta esta semana, el plugin del que trata la advertencia tenía cero cobertura de pruebas. Otros dos módulos del mismo paquete tenían pruebas sólidas. El plugin de remark, la única pieza que ya había documentado como poseedora de un modo de fallo silencioso, no tenía ninguna.

Notar el vacío

Esta vez no fue un bug misterioso, sino un repaso honesto del backlog: qué parte de este código tiene menos pruebas de las que su capacidad de fallar amerita. El plugin de remark resaltó por un motivo simple. Ya había escrito, en prosa, en el README, meses atrás, cómo se ve su peor modo de fallo. Un build que se mantiene en verde mientras publica en silencio texto affiliate:atomicHabits sin reescribir, directo en la página renderizada. No es un crash, es un no-op silencioso, el tipo de bug que un lector nota antes que yo porque nada en el CI se queja.

Documentar un modo de fallo y después nunca escribir una prueba para él es una medida a medias bastante rara. La advertencia protege contra alguien que hace lo incorrecto a mano. No sirve de nada para atrapar un refactor futuro que rompa el mismo código de una forma parecida.

Lo que en realidad era fácil de probar

El plugin resultó ser una función común y corriente en cuanto dejé de lado el marco de “plugin de remark”: recibe un objeto pequeño con forma de árbol y un objeto tipo archivo con frontmatter.affiliates, recorre el árbol buscando enlaces, reescribe sus URLs, y lanza un error si algo es inconsistente. Nada de eso necesita un parser real de Markdown ni correr el pipeline de unified. Un objeto simple { type: 'link', url: 'affiliate:atomicHabits', children: [] } ejercita la misma ruta de código.

const link = { type: 'link', url: 'affiliate:atomicHabits', children: [] };
const tree = { type: 'root', children: [{ type: 'paragraph', children: [link] }] };

remarkAffiliate(config)(tree, fileWithAffiliates(['amazon']));

assert.equal(link.url, 'https://www.amazon.com/dp/B07RFSSYBH/ref=nosim?tag=example-20');

Anidé el enlace dentro de un párrafo a propósito, no en el nivel superior del árbol. El plugin recorre node.children de forma recursiva para encontrar enlaces de afiliados donde sea que estén, y un fixture plano en el nivel superior nunca ejercitaría esa recursión. Habría pasado incluso si el caso recursivo estuviera roto, la misma trampa que una prueba que no prueba nada.

Los cuatro casos que importaban

La reescritura en sí sobre un enlace anidado. Que una clave de catálogo desconocida lance un error, para que un affiliate:atomicHabbits con una errata rompa el build en vez de publicar un enlace roto. Que un programa usado pero no declarado en el affiliates: del frontmatter del post lance un error, que es el mecanismo real de cumplimiento que hace imposible olvidar una divulgación de la FTC. Y que un árbol sin ningún enlace de afiliado no haga nada, para que el plugin no termine exigiendo frontmatter por accidente en cada post de un sitio.

Cuatro pruebas cortas, sin mocks, sin archivos de fixture, sin configurar el pipeline de unified(). Quince minutos de trabajo para un fragmento de código que ya había marcado, por escrito, como riesgoso.

Qué haría distinto

Cuando escribo una advertencia en el README sobre un modo de fallo específico, esa es la señal para escribir la prueba en ese mismo momento, no para agregar un ítem al backlog y volver más tarde. La advertencia es prueba de que ya entiendo exactamente qué podría salir mal, lo cual significa que ya sé exactamente qué debería verificar la prueba. Dejar pasar tiempo entre “sé que esto puede fallar así” y “escribí la prueba para eso” solo deja que ese conocimiento se deteriore antes de usarse.

Lecturas relacionadas