Из того, что я понимаю об этом сценарии, мне нужен интерфейс IEmployeeManager, который используется только для целей тестирования.Это не кажется правильным, так как интерфейс не имеет другого использования.
Стоит создать интерфейс.Также обратите внимание, что интерфейс на самом деле имеет несколько целей:
- Интерфейс идентифицирует роли или обязанности, предоставляемые субъектом. В этом случае интерфейс идентифицирует роли и обязанности
EmployeeManager
.Используя интерфейс, вы предотвращаете случайную зависимость от конкретной базы данных. - Интерфейс уменьшает связь. Поскольку ваше приложение не будет зависеть от
EmployeeManager
, вы свободныпоменять его реализацию без необходимости перекомпилировать остальную часть приложения.Конечно, это зависит от структуры проекта, количества сборок и т. Д., Но, тем не менее, допускает повторное использование этого типа. - Интерфейс способствует тестируемости. Когда вы используете интерфейс, он становится намногопроще создавать динамические прокси, которые позволяют более легко тестировать ваше программное обеспечение.
- Интерфейс заставляет задуматься 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 - Да, думать все еще необязательно, но я настоятельно рекомендую это;).