2010-01-18 42 views
39

He preparado algunas pruebas automáticas con el marco de prueba de Visual Studio Team Edition. Quiero una de las pruebas para conectarse a la base de datos después de la forma normal que se realiza en el programa:Pruebas unitarias con singletons

string r_providerName = ConfigurationManager.ConnectionStrings["main_db"].ProviderName; 

Pero estoy recibiendo una excepción en esta línea. Supongo que esto está sucediendo porque el ConfigurationManager es un singleton. ¿Cómo se puede solucionar el problema del singleton con las pruebas unitarias?


Gracias por las respuestas. Todos ellos han sido muy instructivos.

+1

¿Cuál es el mensaje de error exacto? – Kane

+0

Excepción de puntero nulo – yeyeyerman

Respuesta

69
+1

Todas esas referencias abordan el problema más profundamente que podría resumir en una respuesta. Definitivamente son geniales. –

+2

+1! Ver también el libro "Trabajar eficazmente con código heredado" de Michael Feathers; él proporciona técnicas para probar con Singletons. http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 – TrueWill

+0

Especialmente interesante es el enlace * Performant Singletons *, que lleva a una página de error ** 403 Forbidden **. Una manera bastante sutil de expresar rechazo. – derM

13

Puede usar la inyección de dependencia de constructor. Ejemplo:

public class SingletonDependedClass 
{ 
    private string _ProviderName; 

    public SingletonDependedClass() 
     : this(ConfigurationManager.ConnectionStrings["main_db"].ProviderName) 
    { 
    } 

    public SingletonDependedClass(string providerName) 
    { 
     _ProviderName = providerName; 
    } 
} 

Eso permite pasar cadena de conexión directamente al objeto durante la prueba.

Además, si utiliza el marco de prueba de Visual Studio Team Edition, puede hacer que el constructor con parámetro privado y probar la clase a través del descriptor de acceso.

En realidad resuelvo ese tipo de problemas con la burla. Ejemplo:

tiene una clase que depende de Singleton:

public class Singleton 
{ 
    public virtual string SomeProperty { get; set; } 

    private static Singleton _Instance; 
    public static Singleton Insatnce 
    { 
     get 
     { 
      if (_Instance == null) 
      { 
       _Instance = new Singleton(); 
      } 

      return _Instance; 
     } 
    } 

    protected Singleton() 
    { 
    } 
} 

public class SingletonDependedClass 
{ 
    public void SomeMethod() 
    { 
     ... 
     string str = Singleton.Insatnce.SomeProperty; 
     ... 
    } 
} 

En primer lugar los SingletonDependedClass necesita ser reprogramado para tomar Singleton ejemplo como parámetro constructor:

public class SingletonDependedClass 
{  
    private Singleton _SingletonInstance; 

    public SingletonDependedClass() 
     : this(Singleton.Insatnce) 
    { 
    } 

    private SingletonDependedClass(Singleton singletonInstance) 
    { 
     _SingletonInstance = singletonInstance; 
    } 

    public void SomeMethod() 
    { 
     string str = _SingletonInstance.SomeProperty; 
    } 
} 

Prueba de SingletonDependedClass (Moq mocking library se usa):

[TestMethod()] 
public void SomeMethodTest() 
{ 
    var singletonMock = new Mock<Singleton>(); 
    singletonMock.Setup(s => s.SomeProperty).Returns("some test data"); 
    var target = new SingletonDependedClass_Accessor(singletonMock.Object); 
    ... 
} 
5

Aquí se enfrenta a un problema más general. Si se usa mal, los Singleton impiden la estabilidad.

He realizado un detailed analysis de este problema en el contexto de un diseño desacoplado.Intentaré resumir mis puntos:

  1. Si su Singleton tiene un estado global significativo, no use Singleton. Esto incluye almacenamiento persistente como bases de datos, archivos, etc.
  2. En los casos en que la dependencia de un Objeto Singleton no es obvia por el nombre de las clases, la dependencia debe ser inyectada. La necesidad de inyectar instancias de Singleton en clases demuestra un uso incorrecto del patrón (consulte el punto 1).
  3. Se supone que el ciclo de vida de Singleton es el mismo que el de la aplicación. La mayoría de las implementaciones de Singleton están utilizando un mecanismo de carga lenta para crear instancias. Esto es trivial y es poco probable que su ciclo de vida cambie, de lo contrario no debería usar Singleton.
7

Ejemplo de libro: Working Effectively with Legacy Code

Además se ha dado misma respuesta aquí: https://stackoverflow.com/a/28613595/929902

para ejecutar código que contiene únicos en un instrumento de prueba, tenemos que relajar la propiedad Singleton. Así es como lo hacemos. El primer paso es agregar un nuevo método estático a la clase singleton. El método nos permite reemplazar la instancia estática en el singleton. Lo llamaremos setTestingInstance.

public class PermitRepository 
{ 
    private static PermitRepository instance = null; 
    private PermitRepository() {} 
    public static void setTestingInstance(PermitRepository newInstance) 
    { 
     instance = newInstance; 
    } 
    public static PermitRepository getInstance() 
    { 
     if (instance == null) { 
      instance = new PermitRepository(); 
     } 
     return instance; 
    } 
    public Permit findAssociatedPermit(PermitNotice notice) { 
    ... 
    } 
    ... 
} 

Ahora que tenemos que colocador, podemos crear una instancia de prueba de un PermitRepository y configurarlo. Nos gustaría escribir un código como este en nuestra configuración de prueba:

public void setUp() { 
    PermitRepository repository = PermitRepository.getInstance(); 
    ... 
    // add permits to the repository here 
    ... 
    PermitRepository.setTestingInstance(repository); 
} 
+4

Parece que falta algo aquí en el método setUp(). PermitRepository tiene un constructor privado, por lo que no puede usar el nuevo allí ... – leogtzr

+0

En su método de configuración, llama a get instance antes de configurar su instancia de prueba. Desea establecer su instancia de prueba primero. –