¿Cuál es la mejor práctica para refactorizar un método estático para que sea más comprobable?

7

Supongamos que tiene un método estático que se parece a esto:

public static bool Foo()
{ 
    var bar = new Bar();
    //do some stuff here
}

Este método en su forma actual puede ser un verdadero dolor de cabeza para la prueba de la unidad.

¿Cuál es la mejor práctica para refactorizar esto para que pueda ser comprobable, sin convertirlo en un método de instancia o cambiar la firma del método?

    
pregunta Joseph 15.10.2010 - 20:32

3 respuestas

12

Realmente depende de qué es Bar . Si es algo simple, entonces su método Foo ya es comprobable, solo necesita especificar sus expectativas e invocarlo de manera estática, por ejemplo:

Assert.IsTrue( FooContainer.Foo() );

Pero si Bar encapsula, digamos, su capa de acceso a la base de datos, entonces no puede probar Foo sin una base de datos real, por eso (gracias @ysolik), los métodos estáticos son death to testability . O, en palabras de Michael Feathers, " no oculte un TUF en un TUC " (TUF significa un función antipática de prueba, TUC significa una construcción antipática de prueba). Si Bar es de hecho, no es amigable , entonces lo siento, no funciona bien sin hacer de Foo un método de instancia. Primero deberías rediseñar tu código:

public class FooContainer {
    public bool Foo() {
        var bar = new Bar();
        //...
    }
}

Cuando Foo ya no es estático, puede invocarlo en una instancia de FooContainer :

var container = new FooContainer();
Assert.IsTrue( container.Foo() );

El siguiente paso es extraer una interfaz de Bar (llamémosla IBar ) e inyectarla en FooContainer :

public class FooContainer {
    private readonly IBar m_bar;
    public FooContainer( IBar bar ) { m_bar = bar; }
    public bool Foo() {
        // don't create another Bar, use m_bar
    }
}

Ahora puede mock / stub IBar con su favorito framework de aislamiento y pruebe su código FooContainer en aislamiento de sus dependencias.

    
respondido por el azheglov 15.10.2010 - 21:54
1

¿Cuál es el punto de Foo? ¿Qué hace? ¿Qué es un bar?

Por tu pregunta, parece que Bar es una clase que presenta efectos secundarios, o Bar es un recurso.

De cualquier manera, en ambas circunstancias, sin cambiar la firma del método, usted está aro sin entrar en el mundo desafortunado de las directivas de preprocesadores (#if test var bar = FakeBar (); // = bad).

Si Bar es una clase que presenta efectos secundarios: sin inyectar esa dependencia o devolver lo que sea que afecte, estás en problemas.

Si se trata de un recurso (Stream, DBConnection, etc.), las únicas opciones realistas que puedo ver son:

  1. Extraiga una interfaz y tome una IBar como parámetro como @azheglov dice

  2. Crea un falso / código auxiliar que hereda de Bar y pasa que como parámetro

Todo se reduce a que es probable que tengas que cambiar la firma del método, reducir los efectos secundarios y no crear dependencias ocultas si quieres que sea más fácil de probar.

    
respondido por el Steven Evers 15.10.2010 - 22:43
0

En realidad, tienes muchas opciones. Hoy me encontré con un problema similar y pensé que todo depende de lo difícil que sea instanciar un objeto de tipo Bar .

Bar es fácilmente instanciable

Si el constructor Bar s no tiene Parámetros o todos están disponibles de alguna manera, puedes hacer lo siguiente:

  1. Agregue un parámetro del tipo Bar a la firma Foo s (corregiremos el cambio de la firma más adelante):

    public static bool Foo(Bar bar)
    {
        // ...
        var baz = bar.Baz();
        bar.Quxx(42);
        // more stuff
    }
    
  2. Crea una versión sobrecargada de Foo sin el parámetro Bar y deja que cree la instancia y la transmita:

    public static bool Foo()
    {
        return Foo(new Bar());
    }
    
  3. Extraiga una interfaz IBar y utilícela en la firma del nuevo método Foo . Utilice los errores del compilador para averiguar qué métodos y propiedades necesita para acceder a la interfaz:

    public interface IBar
    {
        object Baz();
    
        void Quxx(int q);
    }
    
    public class Bar : IBar
    {
        // Implementations of Baz, Quxx, and other stuff
    }
    
    public static class SomeStaticClass
    {
        public static bool Foo()
        {
            return Foo(new Bar());
        }
    
        public static bool Foo(IBar bar)
        {
            // ...
            var baz = bar.Baz();
            bar.Quxx(42);
            // more stuff
        }
    }
    
  4. Usa tu técnica / marco de burla favorito para probar el método Foo . Sigue siendo un método estático y no necesita cambiar sus llamadores.

Es muy difícil crear una instancia de un objeto Bar

Pero qué pasa si el constructor Bar s tiene algunos parámetros extraños. Tal vez necesitamos pasar valores que calculamos dentro de Foo . Encontré que el siguiente enfoque es bastante elegante.

  1. Necesitamos alguna forma de invocar al constructor Bar s en el código de producción, pero queremos proporcionar algo de simulacro en el código de prueba, así que tenemos que introducir algo de direccionamiento indirecto. Usemos el patrón de fábrica y creamos un BarFactory que crea la instancia Bar para nosotros. Luego pasamos la instancia de fábrica al método estático:

    public class Bar
    {
        public Bar(Baz baz, Quxx quxx)
        {
            // do stuff with baz and quxx, launch a nuclear bomb, etc.
        }
    }
    
    public class BarFactory
    {
        public Bar CreateBar(Baz baz, Quxx quxx)
        {
            return new Bar(baz, quxx);
        }
    }
    
    public static class SomeStaticClass
    {
        public static bool Foo(BarFactory factory)
        {
            // compute baz and quxx
            var bar = factory.CreateBar(baz, quxx);
            // use bar
        }
    
        public static bool Foo()
        {
            return Foo(new BarFactory());
        }
    }
    
  2. Como en el elemento 3 en el caso anterior, ahora podemos extraer la interfaz IBar y dejar que el compilador nos ayude a recuperar solo los miembros que necesitamos.

  3. Ahora también podemos extraer una interfaz para la fábrica. El código final se verá así:

    public interface IBar
    {
        // IBars methods
    }
    
    public class Bar : IBar
    {
        public Bar(Baz baz, Quxx quxx)
        {
            // do stuff with baz and quxx, launch a nuclear bomb, etc.
        }
    }
    
    public interface IBarFactory
    {
        IBar CreateBar(Baz baz, Quxx quxx);
    }
    
    public class BarFactory : IBarFactory
    {
        public IBar CreateBar(Baz baz, Quxx quxx)
        {
            return new Bar(baz, quxx);
        }
    }
    
    public static class SomeStaticClass
    {
        public static bool Foo(IBarFactory factory)
        {
            // compute baz and quxx
            var bar = factory.CreateBar(baz, quxx);
            // use bar
        }
    
        public static bool Foo()
        {
            return Foo(new BarFactory());
        }
    }
    
  4. Crea simulacros para IBarFactory y IBar y úsalos en tus pruebas. Habrá conservado la firma Foo s y aún es estática.

respondido por el Mathias Becher 08.05.2014 - 00:22

Lea otras preguntas en las etiquetas