Как я высмеиваю это? - PullRequest
       14

Как я высмеиваю это?

13 голосов
/ 11 августа 2011

В приложении Windows .NET у меня есть класс с именем EmployeeManager. При создании этот класс загружает сотрудников в список из базы данных, которые еще не завершили регистрацию. Я хотел бы использовать EmployeeManager в модульном тестировании. Однако я не хочу привлекать базу данных.

Из того, что я понимаю об этом сценарии, мне нужен интерфейс IEmployeeManager, который используется только для целей тестирования. Это не кажется правильным, так как интерфейс не имеет другого использования. Тем не менее, это позволит мне создать некоторый тестовый класс EmployeeManager, который загружает сотрудников без использования базы данных. Таким образом, я могу присвоить значения, которые в противном случае поступили бы из базы данных.

Правильно ли вышеприведенное, и нужно ли мне это делать? Mocking (Moq framework), похоже, использует много кода просто для выполнения простых вещей, таких как присвоение свойства. Я не понимаю, в чем суть. Зачем издеваться, когда я могу просто создать простой тестовый класс из IEmployeeManager, который обеспечит то, что мне нужно?

Ответы [ 5 ]

10 голосов
/ 11 августа 2011

Инверсия контроля - это ваше решение, а не ложные объекты. И вот почему:

Вы смоделируете интерфейс, чтобы убедиться, что некоторый код, который использует ваш IEmployeeManager, использует его правильно. Вы не используете тестовый код, чтобы доказать, что IEmployeeManager работает. Поэтому должен быть другой класс, который, например, использует IEmployeeManager, который вы на самом деле будете тестировать с помощью своего фиктивного объекта.

Если вы на самом деле просто тестируете EmployeeManager, вы можете сделать намного лучше. Рассмотрим внедрение зависимостей. Таким образом, вы предоставите конструктор для EmployeeManager, который примет хотя бы один параметр, который является интерфейсом. Ваш код EmployeeManager будет внутренне использовать этот интерфейс для любых вызовов, специфичных для реализации, которые он должен сделать.

См. Шаблон стратегии

Это приведет вас в целый захватывающий мир Инверсия контроля . И, углубившись в это, вы обнаружите, что подобные проблемы были эффективно решены с помощью контейнеров IoC, таких как AutoFac , Ninject и Структура карты , для назвать несколько.

Моделирование интерфейсов великолепно, и вы можете смоделировать интерфейс, который затем передаете в IoC. Но вы обнаружите, что IoC - гораздо более надежное решение вашей проблемы. И да, хотя вы можете реализовать только второй вариант только для тестирования, все же важно сделать именно эту причину - отделить тестируемую стратегию от бизнес-логики EmployeeManager.

5 голосов
/ 11 августа 2011

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

Стоит создать интерфейс.Также обратите внимание, что интерфейс на самом деле имеет несколько целей:

  1. Интерфейс идентифицирует роли или обязанности, предоставляемые субъектом. В этом случае интерфейс идентифицирует роли и обязанности EmployeeManager.Используя интерфейс, вы предотвращаете случайную зависимость от конкретной базы данных.
  2. Интерфейс уменьшает связь. Поскольку ваше приложение не будет зависеть от EmployeeManager, вы свободныпоменять его реализацию без необходимости перекомпилировать остальную часть приложения.Конечно, это зависит от структуры проекта, количества сборок и т. Д., Но, тем не менее, допускает повторное использование этого типа.
  3. Интерфейс способствует тестируемости. Когда вы используете интерфейс, он становится намногопроще создавать динамические прокси, которые позволяют более легко тестировать ваше программное обеспечение.
  4. Интерфейс заставляет задуматься 1 .Хорошо, я уже упоминал об этом, но это стоит сказать еще раз.Одно только использование интерфейса должно заставить вас задуматься о ролях и обязанностях объекта.Интерфейс не должен быть кухонной раковиной.Интерфейс представляет собой сплоченный набор ролей и обязанностей.Если методы интерфейса не являются связными или почти не всегда используются вместе, вероятно, объект имеет несколько ролей.Хотя это и не обязательно плохо, это означает, что несколько различных интерфейсов лучше.Чем больше интерфейс, тем сложнее сделать его ковариантным или контравариантным и, следовательно, более гибким в коде.

Однако это позволит мне создать некоторый тестовый класс EmployeeManager, который будет загружать сотрудниковбез привлечения базы данных .... Я не понимаю, суть.Зачем издеваться, когда я могу просто создать простой тестовый класс из IEmployeeManager, который будет предоставлять то, что мне нужно?

Как указал один плакат , звучит так, будто вы говорите о созданиитестовый класс заглушки.Для создания заглушек можно использовать фреймворк-фреймворки, но одна из наиболее важных функций в них заключается в том, что они позволяют вам тестировать поведение, а не состояние.Теперь давайте посмотрим на некоторые примеры.Предположим следующее:

interface IEmployeeManager {
    void AddEmployee(ProspectiveEmployee e);
    void RemoveEmployee(Employee e);
}

class HiringOfficer {
    private readonly IEmployeeManager manager
    public HiringOfficer(IEmployeeManager manager) {
        this.manager = manager;
    }
    public void HireProspect(ProspectiveEmployee e) {
        manager.AddEmployee(e);
    }
}

Когда мы проверяем поведение HiringOfficer HireEmployee, мы заинтересованы в проверке того, правильно ли он сообщил руководителю сотрудника, что этот перспективный сотрудник будет добавлен в качестве сотрудника.,Вы часто будете видеть что-то вроде этого:

// you have an interface IEmployeeManager and a stub class
// called TestableEmployeeManager that implements IEmployeeManager
// that is pre-populated with test data
[Test]
public void HiringOfficerAddsProspectiveEmployeeToDatabase() {
    var manager = new TestableEmployeeManager(); // Arrange
    var officer = new HiringOfficer(manager); // BTW: poor example of real-world DI
    var prospect = CreateProspect();
    Assert.AreEqual(4, manager.EmployeeCount());

    officer.HireProspect(prospect); // Act

    Assert.AreEqual(5, manager.EmployeeCount()); // Assert
    Assert.AreEqual("John", manager.Employees[4].FirstName);
    Assert.AreEqual("Doe", manager.Employees[4].LastName);
    //...
}

Приведенный выше тест является разумным ... но не хорошим.Это основанный на состоянии тест.То есть он проверяет поведение, проверяя состояние до и после некоторого действия.Иногда это единственный способ проверить вещи;иногда это лучший способ что-то проверить.

Но , поведение при тестировании часто лучше, и именно здесь высвечиваются насмешливые рамки:

// using Moq for mocking
[Test]
public void HiringOfficerCommunicatesAdditionOfNewEmployee() {
    var mockEmployeeManager = new Mock<EmployeeManager>(); // Arrange
    var officer = new HiringOfficer(mockEmployeeManager.Object);
    var prospect = CreateProspect();

    officer.HireProspect(prospect); // Act

    mockEmployeeManager.Verify(m => m.AddEmployee(prospect), Times.Once); // Assert
}

В приведенном вышепроверил единственное, что действительно имело значение - то, что сотрудник по найму сообщил менеджеру работника, что необходимо добавить нового работника (один раз и только один раз ... хотя я на самом деле не стал бы проверять счет в этом случае).Мало того, я подтвердил, что сотрудник , которого я попросил нанять сотрудника, был добавлен менеджером сотрудника.Я проверил критическое поведение.Мне не нужно было даже простой тестовой заглушки.Мой тест был короче.Реальное поведение было гораздо более очевидным - становится возможным видеть взаимодействие и проверять взаимодействие между объектами.

Возможно сделать ваши записи-заглушки тестовыми взаимодействиями класса, но тогда вы эмулируете фальшивые рамки.Если вы собираетесь проверить поведение - используйте фальшивые рамки.

Как уже упоминалось, важны внедрение зависимости (DI) и инверсия управления (IoC). Мой пример выше не является хорошим примером этого, но оба должны быть тщательно продуманы и разумно использованы. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1. 1. * * *;

1 - Да, думать все еще необязательно, но я настоятельно рекомендую это;).

1 голос
/ 11 августа 2011

Создание интерфейса IEmployeeManager для возможности имитации - так большинство разработчиков .NET могут сделать такой класс тестируемым.

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

1 голос
/ 11 августа 2011

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

На мой взгляд, стоит учиться. Это сэкономит вам много печатать. Они также гибки и не всегда требуют интерфейса для генерации тестовой реализации. RhinoMocks, например, может издеваться над конкретными классами, если у них пустой конструктор и методы являются виртуальными. Еще одно преимущество заключается в том, что в API-интерфейсах для имитации используются согласованные имена, поэтому вы познакомитесь с «Mocks», «Stubs» и т. Д. В вашем сценарии вам понадобится заглушка , а не mock . Написание фактического макета вручную может быть более трудоемким, чем использование фреймворка.

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

Это хорошее чтение на тему рукописных и сгенерированных заглушек

0 голосов
/ 11 августа 2011

Заставляя ваши классы реализовывать интерфейсы, вы не только делаете их более тестируемыми, вы делаете свое приложение более гибким и обслуживаемым. Когда вы говорите: «Это кажется неправильным, поскольку интерфейс больше не используется», вы ошибаетесь, так как это позволяет вам свободно соединять ваши классы.

Если бы я мог предложить пару книг Head First Design Patterns и Head First Development Software сделает гораздо лучшую работу по объяснению концепций, чем я мог бы в SO-ответе.

Если вы не хотите использовать фальшивый фреймворк, такой как Moq, достаточно просто свернуть свои собственные макеты / заглушки, вот краткое сообщение в блоге об этом Прокрутка ваших собственных макетов

...