¿Cómo burlarse del método con un objeto codificado?

11

Estoy trabajando en una aplicación que tiene varias capas. Capa de acceso a datos para recuperar y guardar datos de la fuente de datos, lógica de negocios para manipular datos, interfaz de usuario para mostrar los datos en pantalla.

También estoy haciendo pruebas unitarias de la capa de lógica de negocios. El único requisito es probar el flujo de la lógica de la capa empresarial. Así que uso el marco Moq para simular la capa de acceso a datos y probar la capa de lógica de negocios con la Unidad MS.

Estoy usando la programación de interfaz para hacer que el diseño se desacople tanto como sea posible para que se pueda hacer una prueba unitaria. Capa de acceso a datos de llamada de capa empresarial a través de la interfaz.

Tengo un problema cuando intento probar uno de los métodos de lógica de negocios. Ese método funciona y crea un objeto y lo pasa a la capa de acceso a datos. Cuando intento burlarme de ese método de capa de acceso a datos, entonces no puedo burlarme con éxito.

Aquí estoy tratando de crear un código de demostración para mostrar mi problema.

Modelo:

public class Employee
{
    public string Name { get; set; }
}

Capa de acceso a datos:

public interface IDal
{
    string GetMessage(Employee emp);
}

public class Dal : IDal
{
    public string GetMessage(Employee emp)
    {
        // Doing some data source access work...

        return string.Format("Hello {0}", emp.Name);
    }
}

Capa lógica empresarial:

public interface IBll
{
    string GetMessage();
}

public class Bll : IBll
{
    private readonly IDal _dal;

    public Bll(IDal dal)
    {
        _dal = dal;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method.
        Employee emp = new Employee(); 

        string msg = _dal.GetMessage(emp);
        return msg;
    }
}

Prueba de unidad:

[TestMethod]
    public void Is_GetMessage_Return_Proper_Result()
    {
        // Arrange.
        Employee emp = new Employee; // New object.

        Mock<IDal> mockDal = new Mock<IDal>();
        mockDal.Setup(d => d.GetMessage(emp)).Returns("Hello " + emp.Name);

        IBll bll = new Bll(mockDal.Object);

        // Act.

        // This will create another employee object inside the 
        // business logic method, which is different from the 
        // object which I have sent at the time of mocking.
        string msg = bll.GetMessage(); 

        // Assert.
        Assert.AreEqual("Hello arnab", msg);
    }

En el caso de prueba unitaria en el momento de la burla, estoy enviando un objeto Empleado, pero cuando invoco el método de lógica de negocios, está creando un objeto Empleado diferente dentro del método. Por eso no puedo burlarme del objeto.

En ese caso, ¿cómo diseñar para que pueda resolver el problema?

DesarrolladorArnab
fuente
Por lo general, el truco es envolver el objeto en una interfaz y hacer que todos los consumidores usen esa interfaz, luego simplemente se burla de la interfaz, alternativamente, puede hacer que el método sea virtual y luego moq puede burlarse del método sin la interfaz. Sin embargo, no estoy seguro acerca de los diamantes de imitación u otros en este caso.
Jimmy Hoffa

Respuestas:

12

En lugar de crear un Employeeobjeto directamente mediante el uso new, su clase Bllpodría usar una EmployeeFactoryclase para esto, con un método createInstance, que se inyecta a través del constructor:

 class EmployeeFactory : IEmployeeFactory
 {
       public Employee createInstance(){return new Employee();}
 }

El constructor debe llevar el objeto de fábrica a través de una interfaz IEmployeeFactory, para que pueda reemplazar la fábrica "real" fácilmente por una fábrica simulada.

public class Bll : IBll
{
    private readonly IDal _dal;
    private readonly IEmployeeFactory _employeeFactory;

    public Bll(IDal dal, IEmployeeFactory employeeFactory)
    {
        _dal = dal;
        _employeeFactory=employeeFactory;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method
        // *** using a factory ***
        Employee emp = _employeeFactory.createObject(); 
        // ...
    }
    //...
}

La fábrica simulada puede proporcionarle a la prueba cualquier tipo de Employeeobjeto que necesite para su prueba (por ejemplo, createInstancesiempre podría devolver el mismo objeto):

 class MockEmployeeFactory : IEmployeeFactory
 {
       private Employee _emp;

       public MockEmployeeFactory()
       {
          _emp = new Employee();
          // add any kind of special initializing here for testing purposes
       }

       public Employee createInstance()
       {
          // just for testing, return always the same object
          return _emp;
       }
 }

Ahora usar este simulacro en tu prueba debería ser el truco.

Doc Brown
fuente
¿Me puede dar un ejemplo de código, para que pueda visualizar su teoría?
DeveloperArnab
@DeveloperArnab: mira mi edición.
Doc Brown
Muy útil ...
DeveloperArnab
4

Lo trataría como una sola unidad para probar.

Siempre que controle todas las entradas a partir de las cuales Employeese crea el objeto, el hecho de que se cree en el objeto probado no debería importar. Solo necesita un método simulado para devolver el resultado esperado si el contenido del argumento coincide con las expectativas.

Obviamente, significa que debe proporcionar una lógica personalizada para el método simulado. La lógica avanzada a menudo no se puede probar con solo simulacros "para x retorno y".

De hecho, usted debe no hacerlo volver objeto diferente en las pruebas de lo que será en la producción, ya que si lo hiciera, no estaría probando el código que debe crearlo. Pero ese código es parte integral del código de producción y, por lo tanto, también debe estar cubierto por el caso de prueba.

Jan Hudec
fuente
Sí, no me preocupo por las entradas de la capa de acceso a datos, solo quiero burlarme de ese objeto y devolver datos codificados para poder probar la lógica empresarial. Pero el problema es debido a dos objetos empleados diferentes, no puedo burlarme del método de la capa de acceso a datos.
DeveloperArnab
@DeveloperArnab: los objetos serán diferentes, pero tendrán contenido conocido. Entonces, todo lo que necesita hacer es hacer que el simulacro haga una comparación personalizada en lugar de la identidad del objeto.
Jan Hudec
@DeveloperArnab: si inyecta diferentes Employeeobjetos en las pruebas, no probará el código que normalmente lo crea. Entonces no deberías cambiarlo.
Jan Hudec
0

Es una falla de algunas herramientas de prueba, siempre debe usar interfaces y todo debe crearse de una manera que le permita intercambiar el objeto basado en la interfaz por otro.

Sin embargo, hay mejores herramientas: tome Microsoft Fakes (se llamaba Moles) que le permite intercambiar cualquier objeto, incluso los estáticos y globales. Se necesita un enfoque de nivel más bajo para reemplazar objetos, de modo que no tenga que usar interfaces en todas partes mientras mantiene la forma de escribir las pruebas a las que está acostumbrado.

gbjbaanb
fuente