lunes, marzo 06, 2006

Pruebas

Hoy he probado a probar. He instalado la última versión de NUnit (la 2.2.7) y he decidido probar con uno de nuestros proyectos. La experiencia ha sido intensa para ser la primera vez que intentaba probar un sistema real ya construido (algunos me dirán, con razón, que eso debería haberlo pensado antes, pero equivocarse es una buena manera de aprender).

Bueno, pues sólo por haberlo intentado, ya me he encontrado con ciertas dificultades, que me han servido para reflexionar un poco y llegar a algunas conclusiones.

Diseña para probar
La primera es que el sistema tiene que estar diseñado para ser probado. Es muy difícil probar un sistema que no está pensando con el proceso de pruebas en mente. Los criterios de acoplamiento y cohesión son en mi opinión de especial importancia, y tener este proceso presente favorece su aplicación. En particular, disponer de clases poco acopladas facilita el diseño de los casos de prueba al no ser necesario simular demasiadas de esas clases. Para ilustrar esta reflexión y las siguientes, voy a describir el caso particular que me llevó a ellas.

El sistema es una aplicación web con arquitectura en tres capas (presentación, lógica y datos) con sus funciones habituales. Ciertas operaciones usadas habitualmente por varias clases de la capa de presentación fueron recogidas en varios métodos de clase. Una de estas funciones determina si la IP del navegador pertenece a la red local o no. Su signatura tal y como estaba programada en un principio es la siguiente (Visual Basic.NET):
Public Shared Function EsIPInterna() As Boolean
:
El principal problema era que ¡no recibía ninguna dirección IP! Examinándo el código, comprobe que la dirección IP se obtenía de la cabecera HTTP enviada por el navegador:
:
Dim direccionIp As String = Request.ServerVariables("HOST_ADDRESS") 'O algo así
:
La función dependía internamente de un objeto disponible en el contexto del Internet Information Server de Microsoft. Las pruebas deben ejecutarse en un entorno que desde luego no es el IIS y en el que por tanto no tenemos acceso al objeto Request. Para salvar este primer inconveniente, modifiqué la signatura de la función de esta forma:
Public Shared Function EsIPInterna(ByVal ip As String) As Boolean
:
Y a continuación, cree una nueva función que permitiría no modificar el resto del código:
Public Shared Function EsIPInterna() As Boolean
   Return EsIPInterna(Request.ServerVariables("..."))
End Function
La primera función me permitiría pasar varios argumentos a la función y poder probarla. Lo dicho, las pruebas tienen que estar siempre presentes en el proceso de diseño y construcción.

Prueba la reusabilidad
Como segunda reflexión, he percibido que el proceso de pruebas puede favorecer la reusabilidad. En efecto, el obligar a probar una u otra función con un juego de datos que cubra por ejemplo el criterio de pruebas de caja negra obliga a exponer aquellos parámetros que sean necesarios para ejercitar el código. Esta exposición En otro caso, el sistema debe cambiar para adecuar las pruebas. Mejor hacerlo entonces desde el principio. Lo hemos visto en el caso anterior. La función EsIPInterna que recibe el parámetro podría ser reusada en otros proyectos.

Prueba a desacoplar
Probar un sistema casi obliga a disminuir todo lo posible el acoplamiento. La necesidad de probar las clases por separado fuerza en cierta forma a diseñarlas de manera que dependan lo menos posible unas de otras. Para que el proceso de pruebas sea efectivo, es necesario en todo caso que dicha dependencia sea explícita (por ejemplo, en la forma de parámetros en la construcción). Además, se refuerza la esencia colaborativa del diseño orientado a objetos.

Castillos en el aire
Las pruebas son especialmente difíciles de diseñar en aquellos casos en los que el código depende de alguna plataforma tecnológica, precisamente porque es difícil simular dicha plataforma (salvo que, una vez más, el sistema esté diseñado de forma que lo permita). Ejemplos de dichas plataformas son el servidor web (un IIS en mi caso), o un middleware o monitor transaccional (COM+ en mi caso). En el ejemplo esta reflexión se hace evidente.

Conclusiones
Como resumen, probar te permite orientar tu diseño para permitirte precísamente poder probarlo, aumentar su reusabilidad, disminuir su acoplamiento y abstraer lo más posible tu código de la plataforma tecnológica que estés usando en cada momento. No se le puede pedir más a una función... Veremos a ver qué aprendo de las próximas cientos de miles...