¿Lo que huele mal es la inyección de dependencias?

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);

        }

    }

}

Sin categoría

5 thoughts on “¿Lo que huele mal es la inyección de dependencias?

  1. Hola Lucas,

    supongo que esto va a gustos, pero a mi me sigue gustando más la opción de inyección de dependecias. Ten en cuenta que en todos los ejemplos que has mostrado añades la misma o más complejidad a tu código que con la inyección de dependencias.

    Otra ventaja que me parece muy interesante es que la inyección de dependencias es agnostica a la tecnología. Es decir, se puede utilizar tanto con .Net 4 como con .Net 1.1 u otros lenguajes que no soporten Dynamics.

    Y la última. En el ejemplo de la sublcasificación, estás creando una clase que no deja de ser una especie de mock creado a mano. ¿No crees que añade menos complejidad crearte ya hacer el setup del mock en la clase de test? A parte, en escenarios más complejos, creo que esta solución te complicaría mucho el testeo.

    Saludos!

  2. Lucas,
    No veo la ventaja de esta especie de sobrecarga forzada de “new” respecto al usao clásico de DI. En ambos casos quien realiza el código original debe saber cuales son los puntos de expansión, para o bien declarar interfaces o bien usar esa clase New que has creado.

    Es decir, este código “puede” ser fácil de testear (porque comparto las dudas de Vicenç al respecto del uso en escenarios complejos), sólo porque puedes modificarlo para usar New, en lugar de new. Si puedes hacer esto, seguramente estás creando el código. Y si lo estás creando, no es mejor intentar desacoplar desde un buen principio, dejar de reinventar la rueda y usar frameworks de mocks junto don DI?

    saludos!

  3. @Vicenç: partamos de la base que esto es solo un código para pensar acerca de lo que la programacón dinámica nos posibilita y no una recomendación de mi parte. Creo que inyectar ciertos tipos no tiene sentido. Lo que digo es que si en c# se pudiese sobreescrivir el operador new podríamos testear cierto código de manera mucho mas sencilla. En cuanto a tu pregunta sobre crear el mock: sí, sería mejor pero ese no es el punto, el punto es que hubo que refactorizar el código para luego poder crear ese mock. Lo que busco es no tener que modificar el código para probarlo.

    @Eduard: La DI me obliga ha hacer ciertas modificaciones con las que no estoy de acuerdo como inyectar una abstracción del SmtpClient en mi código, creo que nadie puede sentirse feliz de hacer tales cosas (en mi entrada anterior expongo mi punto de vista).
    Tampoco estoy del todo convencido de que la DI sea un “buen principio” (al menos no para todo). Ni estoy del todo seguro de si los puntos de extensión deben estar acotados a solo los escenarios planteados o si bien “todo” debería ser objeto de extensión.
    Por esto, mi respuesta a tu pregunta es “no”.

  4. Hace un tiempo tuvimos que hacer algo similar. Cual fue nuestra solucion? Encapsular la clase smtpClient en una nueva clase, que leia el valor de una propiedad y en funcion de esto, enviaba o no enviaba el email. Por lo tanto, en produccion seteamos en “true” esta propiedad, y en “test”, la seteabamos en false.
    Encapsulando una clase, solucionamos un monton de problemas relacionados con envio de email y test unitarios, sin usar mocking ni inyeccion de dependencias ni depender de patrones que agregan mas codigo y ruido.

  5. Simplemente declara en tu clase TransferManager:

    public static Func NewSmtpClient = () => new SmtpClient();

    En Transfer lo usas remplazando
    new SmtpClient()
    Por
    NewSmtpClient()

    Y en TransferTest:

    TransferManager.SmtpClient = () => new NullSenderMock()

    Vamos que simplemente usando un Func en una variable global ya te quitas la inyección de dependencia. No puede ser más fácil.

    Y en tu unit e

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *