Número 62
12 de xullo de 2023

Visible for testing

Unha das miñas teimas cando reviso código é o das funcións que son públicas, ou visibles, só co obxecto de poder chamar por elas nos tests unitarios.

Un grupo de xente ollando por un telescopio.

Hai un tempo, Brais, que estaba a traballar na habitual aplicación de xestión de clientes, escribiu unha clase adicada a gravar os datos dun cliente na base de datos e envioume o commit para que llo revisara. Como era de esperar, ese commit incluía a propia clase e os seus tests unitarios.

Esa clase tiña unha habilidade moi especial: era capaz de descompoñer o enderezo postal do cliente para separalo na rúa, número, portal, piso, etc. e gravar os distintos compoñentes en campos distintos da base de datos (xa falei o outro día de se iso me parecía boa ou mala idea, pero neste caso, como era un requirimento do proxecto, os meus sentimentos eran irrelevantes).

Normalmente, a función que descompoñía os enderezos sería unha función privada, xa que só recibía chamadas de outras funcións da mesma clase; porén, como era tan complicada, Brais queríase asegurar de que funcionaba ben. E para se asegurar diso, escribiu uns poucos tests, e para podelos escribir, fixo pública esa función.

Ben se esforzou o home, que incluso lle puxo unha anotación “@VisibleForTesting” para que a xente que lera o código (ou sexa, eu) soubera por que esa función era pública no canto de privada. O malo é que, con anotación ou sen ela, esa clase de cousa non me chista moito.

Eu teño a firme opinión de que os tests unitarios deberían utilizar só a interface pública do módulo que proban. Ou sexa: non deben ser tests de “caixa branca” nos que o test mete as súas gadoupas nas interioridades do módulo para comprobar que todo o mecanismo traballa como se espera, senón que deberían ser tests de “caixa negra” nos que o test comproba que, cando chama a unha función desta maneira, o módulo produce o resultado esperado.

A única excepción que acepto é a dos construtores: como moitos tests unitarios falsean as dependencias do código que proban, ás veces é necesario construír as instancias das clases dunha maneira especial, e para iso hai que expoñer construtores específicos para os tests. Acéptoos aínda que me fagan doer o corazón.

O que non admito é escribir tests específicos para unha función que debía ser privada. Se é privada, non é externa (por definición) e o test unitario non ten por que saber que existe, e moito menos chamala directamente.

Ás veces, como Brais, alguén pensa que ten a escusa (“a razón”, din eles) perfecta: a operación desta función é moi complicada e é necesario escribir tests para ela. Podería facer tests que a exerciten dunha maneira e da outra a través da interface pública, pero logo os tests serían innecesariamente longos e difíciles de ler, e eu coñézote ben, Jacobo, e sei que aborreces iso, así que velaquí o dilema, Jacobo, escolle: tests simples que chaman á función que debía ser privada, ou tests que non a chaman directamente pero son moi complicados?

A resposta, obviamente, é: se a función desa clase privada é tan complexa que vedes a necesidade de escribir tests unitarios para ela, convertédea nunha “unidade”. É dicir: extraédea a un módulo ou a unha clase na que esa función sexa pública, e despois escribide tests unitarios para ese módulo ou clase.

Iso é o que fixo Brais e, despois diso, decatouse de que os tests unitarios separados eran máis simples e, polo tanto, podían ser máis exhaustivos con menos código. E, coa lección aprendida e aplicada, aprobei o commit.

E despois ocorreu o incidente co framework de inxección de dependencias, pero xa non queda sitio na Folla de hoxe.

A ilustración desta Folla procede dun gravado de Thomas Rowlandson.