Как смоделировать метод с жестко закодированным объектом?

11

Я работаю над приложением, которое имеет несколько слоев. Уровень доступа к данным для извлечения и сохранения данных из источника данных, бизнес-логика для управления данными, пользовательский интерфейс для отображения данных на экране.

Я также занимаюсь модульным тестированием уровня бизнес-логики. Единственное требование - проверить поток логики бизнес-уровня. Поэтому я использую инфраструктуру Moq для моделирования уровня доступа к данным и модульного тестирования уровня бизнес-логики с MS Unit.

Я использую программирование интерфейса, чтобы максимально отделить дизайн, чтобы можно было выполнить модульное тестирование. Бизнес-уровень вызова уровня доступа к данным через интерфейс.

Я сталкиваюсь с проблемой, когда пытаюсь проверить один из методов бизнес-логики. Этот метод выполняет некоторую работу, создает объект и передает его на уровень доступа к данным. Когда я пытаюсь смоделировать этот метод уровня доступа к данным, он не может успешно смоделировать.

Здесь я пытаюсь создать демонстрационный код, чтобы показать мою проблему.

Модель:

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

Уровень доступа к данным:

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

Уровень бизнес-логики:

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

Модульный тест:

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

В случае модульного теста во время моделирования я отправляю объект Employee, но при вызове метода бизнес-логики он создает другой объект Employee внутри метода. Вот почему я не могу издеваться над объектом.

В таком случае, как спроектировать, чтобы я мог решить проблему?

DeveloperArnab
источник
Обычно хитрость заключается в том, чтобы обернуть объект в интерфейс и заставить всех потребителей этого интерфейса использовать этот интерфейс, затем просто смоделировать интерфейс, или же можно сделать метод виртуальным, а затем moq может смоделировать метод без интерфейса. Не уверен насчет носорогов или других в этом случае, однако.
Джимми Хоффа

Ответы:

12

Вместо того чтобы создавать Employeeобъект напрямую с помощью new, ваш класс Bllможет использовать EmployeeFactoryдля этого класс с методом createInstance, который вводится через конструктор:

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

Конструктор должен передавать фабричный объект через интерфейс IEmployeeFactory, чтобы вы могли легко заменить «настоящую» фабрику фиктивной фабрикой.

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(); 
        // ...
    }
    //...
}

Поддельная фабрика может предоставить тесту любой Employeeобъект, который вам нужен для теста (например, createInstanceвсегда может вернуть один и тот же объект):

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

Теперь использование этого макета в вашем тесте должно помочь.

Док Браун
источник
Можете ли вы привести один пример кода, чтобы я мог представить вашу теорию?
DeveloperArnab
@DeveloperArnab: см. Мое редактирование.
Док Браун
Очень полезно ...
DeveloperArnab
4

Я бы отнесся к нему как к единому устройству для тестирования.

Пока вы контролируете все входные данные, из которых Employeeсоздается объект, сам факт его создания в тестируемом объекте не должен иметь значения. Вам просто нужен метод mock, чтобы вернуть ожидаемый результат, если содержимое аргумента соответствует ожидаемому.

Очевидно, это означает, что вам нужно предоставить собственную логику для метода макета. Продвинутая логика часто не может быть проверена только с помощью форм «for x return y».

На самом деле, вы не должны заставлять его возвращать другой объект в тестах, чем в производственном процессе, потому что если бы вы это сделали, вы бы не тестировали код, который должен его создать. Но этот код является неотъемлемой частью производственного кода и поэтому должен также охватываться тестовым примером.

Ян Худек
источник
Да, я не беспокоюсь о входных данных уровня доступа к данным, я только хочу смоделировать этот объект и вернуть жестко закодированные данные, чтобы я мог проверить бизнес-логику. Но проблема в том, что из-за двух разных объектов Employee, я не могу издеваться над методом уровня доступа к данным.
DeveloperArnab
@DeveloperArnab: объекты будут разными, но у них будет известное содержание. Так что все, что вам нужно сделать, это заставить mock делать пользовательское сравнение вместо идентификации объекта.
Ян Худек
@DeveloperArnab: Если вы вводите другой Employeeобъект в тестах, вы не будете тестировать код, который его обычно создает. Так что не стоит это менять.
Ян Худек
0

Это сбой некоторых инструментов тестирования, вы всегда должны использовать интерфейсы, и все должно быть создано таким образом, чтобы вы могли поменять объект на основе интерфейса на другой.

Однако есть и лучшие инструменты - Microsoft Fakes (называлась Moles), которая позволяет заменять любые объекты, даже статические и глобальные. Для замены объектов требуется более низкоуровневый подход, поэтому вам не нужно везде использовать интерфейсы, сохраняя при этом способ написания тестов, к которым вы привыкли.

gbjbaanb
источник