April 2011 - Artículos

En mi anterior entrada mostraba distintas alternativas que podíamos utilizar para volver al siguiente fragmento de código fácilmente testeable. Obviamente existe una infinidad de alternativas que no he abordado como los frameworks de aislamiento, los servidores de smtp que no envían los mails y muchas más.

La idea aquí  es mostrar el por qué este código no es un duro de testear. Obviamente es porque tiene una dependencia con la clase SmtpClient la cual se comunica mediante un socket con el ambiente externo.

image
Entonces, el problema radica en la sentencia:

image

Ahora bien, en algunos lenguajes como por ejemplo, Ruby. El mismo código es fácilmente testeable ya que las clases e incluso las instancias están abiertas para la modificación en tiempo de ejecución. Entonces en Ruby podemos sobre escribir el método “new” de manera que nos devuelva un tipo diferente al que le pedimos. ¿Podemos en C# hacer lo mismo? Ummm…. la respuesta correcta es no, pero podemos acercarnos bastante. Fijate si notas la diferencia entre el siguiente código y el primero:

image

¿La encontraste? Bien! Este código ahora si es testeable. De la siguiente manera:

image

A simple vista se parece mucho a una sobrecarga del operador “new”, también podría compararse con un contenedor de inversión de control o con una factoría. No es mi intención discutir ese tema sino mostrar como las características nuevas de c# 4 sobre el mundo dinámico nos puede ayudar a pensar en otros tipos de soluciones.

Les dejo todo el código (es solo una prueba de concepto):

using System;
using System.Collections.Generic;
using System.Net.Mail;
 
namespace CSharpDi
{
    public class Program
    {
        public static void Main()
        {
            typeof(SmtpClient).RegisterBuilder(p =>
                new
                {
                    Send = new Action<string, string, string, string>(
                    (from, to, subject, body) => { })
                }
            );
 
            var tm = new TranferManager();
            tm.Tranfer();
        }
    }
 
    public class TranferManager
    {
        public void Tranfer()
        {
            // Perform the required actions
            var smtpClient = New. SmtpClient();
            smtpClient.Send("info@bank.com", "from.Email", "Tranfer", "?");
        }
    }
 
 
    public static class New
    {
        public static dynamic SmtpClient(params object[] parameters)
        {
            return typeof(SmtpClient).New(parameters);
        }
    }
 
    public static class CreationExtensions
    {
        private static Dictionary<Type, Func<object, dynamic>> builders =
            new Dictionary<Type, Func<object, dynamic>>();
 
        public static dynamic New(this Type type, params object[] parameters)
        {
            if(builders.ContainsKey(type))
                return builders[type](parameters);
 
            return Activator.CreateInstance(type, parameters);
        }
 
        public static void RegisterBuilder(this Type type, Func<object, dynamic> builder)
        {
            builders.Add(type, builder);
        }
    }
}

Veamos el siguiente código:

image

Lo que buscamos crear al menos una prueba unitaria para este. ¿Cómo lo hacemos?. Bueno, antes quiero plantear algunos supuestos:

  • Si bien parece un TransactionScript, hagamos de cuenta que no lo es. El hecho de tener solo un método y ningún campo, propiedad o evento es solo para hacer de este un ejemplo ultra sencillo. Por lo tanto pensemos que sí tiene campos, propiedades y varios métodos.
  • Si bien usamos un System.Net.Mail.SmtpClient, la idea es no limitarse solo a esta clase sino que puede ser cualquier otra (en este caso una clase de la BCL). Quizás no tenemos el código para modificarlo (como en este caso) o quizás sí lo tenemos pero modificarlo no es buena idea.
  • En principio solo queremos verificar que la transferencia se realiza y NO que el mail se envía correctamente.

Las opciones que analizaremos aquí son:

  • Inyección de dependencias (DI)
  • Sub clasificación
  • Decoración

Inyección de dependencias

Usar un Gateway es uno de los caminos más habituales que podemos encontrar para “volver testeable nuestro código”. Hacer esto con el único objetivo de testear el código para mí no solo que no vale la pena sino que es incorrecto. Es una sobre ingeniería, a pequeña escala en este caso, y un despropósito puesto que difícilmente tendremos en el futuro otro tipo de cliente de mail. El único cliente de mail extra existe solo con motivo de testing. Y además tenemos 2 constructores, una interface y una clase extra, etc, etc… horrible. ¡Si tan solo el método Send del SmtpClient fuese virtual!

image

Si no quisiéramos pagar ese costo, ahorrarnos la interface y la clase extra pero seguir utilizando la DI como mecanismo, lo que podríamos hacer es darnos una vuelta por el mundo dinámico. Básicamente, cualquier tipo que responda a “Send(string, string, string, string)” nos va a servir.

image

Las dos principales objeciones a esta alternativa vienen de parte de aquellos a quienes les disgustan (¿o asustan?) las implicaciones del chequeo de tipos en tiempo de ejecución como de aquellos a quienes les disgusta el inyectar este tipo de dependencias. Por lo demás, esto facilita bastante la creación de las pruebas. Abajo hay dos pruebas que usan tipos anónimos para realizar dos tipos de pruebas distintas.

image

Una solución intermedia entre inyectar tipos estáticos y dinámicos es inyectar acciones. Es decir, inyectar la pieza de código que queremos que se ejecute para enviar los mails como vemos abajo. Esto también facilita enormemente la creación de pruebas y si bien es aceptable, tiene, además del mismo mal olor de todas las alternativas que requieren inyectar dependencias, el problema de ser poco intuitiva. Veamos un ejemplo:

image

Conclusión parcial: la inyección de dependencias, en cualquiera de sus formas, nos puede ayudar a crear código más testeable aunque requiere de cierto código extra y ciertos patrones que no siempre redundan en mejor código. Por esta razón, la DI, en casos similares a los de los ejemplos que he presentado, no es una buena alternativa.

Sub clasificación

Uno podría argumentar que quizás nuestro método Tranfer necesita una pequeña refactorización la cual nos podría ayudar a mejorar el código y de pasada, hacerlo más testeable… veamos:

image

Ahora podemos crear versiones testeables de esta clase, ya sea a mano o utilizando algún framework de aislamiento.

image

Esto parece algo más limpio ¿verdad? No necesitamos extrañas interfaces, constructores, tipos dinámicos ni nada de lo que vimos con anterioridad. Una objeción: ¿qué sucede si la clase necesita ser sealed? Este punto de extensión ha sido creado nuevamente con el único propósito de posibilitar las pruebas porque de lo contrario, el método no sería protected virtual sino simplemente private. Ok, pero es una alternativa válida y nos sirve.

Decoración

Quizás la responsabilidad de enviar las notificaciones no sea algo propio de nuestra clase TranferManager y por lo tanto podríamos sacar esa responsabilidad a otra clase usando el patrón decorador (o podríamos usar un wrapper también). Veamos:

image

Ahora podemos testear TranferManager sin dificultades. El problema que se nos presenta ahora es que tal vez lo que necesitamos testear ahora sea la nueva clase TranferManagerWithNotification.

Esta entrada se ha vuelto muy extensa así que la continuaré más adelante…