Для чего конкретно нужен интерфейс для шаблона хранилища?
Я полностью понимаю идею дизайна шаблона репозитория. Но зачем нам нужно реализовывать интерфейсный класс iDepository? Для чего конкретно это нужно? Сам класс репозитория работает без класса интерфейса.
Я думаю, что кто-то ответит мне, что это для отделения от бизнес-логики и логики данных. Но даже если нет класса интерфейса, разве логика данных не отделена от логики данных?
3 ответа
Это так, что вы можете внедрить двойной тест класса IRepository, когда вы тестируете модуль бизнес-уровня. Это имеет следующие преимущества:
- Это позволяет вам легко определить неудачные тесты как вызванные бизнес-уровнем, а не уровнем хранилища;
- Это делает ваши тесты уровня бизнес-логики быстрыми, так как они не зависят ни от доступа к данным, который имеет тенденцию быть медленным, ни от настройки структуры базы данных и тестовых данных, которая имеет тенденцию быть очень медленной.
Один способ внедрить тест удваивается, когда модульное тестирование - это Constructor Injection. Предположим, что ваш репозиторий имеет следующие методы:
void Add(Noun noun);
int NumberOfNouns();
И это код вашего бизнес-класса:
public class BusinessClass {
private IRepository _repository;
public BusinessClass(IRepository repository) {
_repository = repository;
}
// optionally, you can make your default constructor create an instance
// of your default repository
public BusinessClass() {
_repository = new Repository();
}
// method which will be tested
public AddNoun(string noun) {
_repository.Add(new Noun(noun));
}
}
Чтобы протестировать AddNoun без реального репозитория, вам нужно настроить двойной тест. Обычно вы делаете это с помощью фальшивой среды, такой как Moq, но я напишу фиктивный класс с нуля, чтобы проиллюстрировать эту концепцию.
public IRepository MockRepository : IRepository {
private List<Noun> nouns = new List<Noun>();
public void Add(Noun noun) {
nouns.Add(noun);
}
public int NumberOfNouns() {
return nouns.Count();
}
}
Теперь одним из ваших тестов может быть это.
[Test]
public void AddingNounShouldIncreaseNounCountByOne() {
// Arrange
var mockRepository = new MockRepository();
var businessClassToTest = new BusinessClass(mockRepository);
// Act
businessClassToTest.Add("cat");
// Assert
Assert.AreEqual(1, mockRepository.NumberOfNouns(), "Number of nouns in repository should have increased after calling AddNoun");
}
Это позволило вам протестировать функциональность вашего метода BusinessClass.AddNoun, не затрагивая базу данных. Это означает, что даже если есть проблема с вашим уровнем репозитория (скажем, проблема со строкой соединения), вы можете быть уверены, что ваш бизнес-уровень работает должным образом. Это охватывает пункт 1 выше.
Что касается пункта 2 выше, всякий раз, когда вы пишете тесты, которые тестируют базу данных, вы должны убедиться, что она находится в известном состоянии перед каждым тестом. Обычно это включает удаление всех данных в начале каждого теста и повторное добавление данных теста. Если этого не сделать, то вы не сможете запустить утверждения, скажем, против количества строк в таблице, потому что вы не будете уверены, что это должно быть.
Удаление и повторное добавление тестовых данных обычно выполняется с помощью сценариев SQL, которые работают медленно и уязвимы для поломки при изменении структуры базы данных. Поэтому рекомендуется ограничивать использование базы данных только тестами самого репозитория и использовать макетированные репозитории при модульном тестировании других аспектов приложения.
Что касается использования абстрактных классов - да, это обеспечило бы такую же возможность поставлять тестовые двойники. Я не уверен, какой код вы бы выбрали для абстрактной базы, а какую конкретную реализацию. Этот ответ на вопрос SO имеет интересную дискуссию об абстрактных классах и взаимодействиях.
Во-первых, вы должны понять, что такое шаблон репозитория. Это уровень абстракции, поэтому остальной части приложения не нужно заботиться о том, откуда берутся данные.
Абстракции в.NET обычно представлены интерфейсами, так как никакая логика (код) не может быть присоединена к интерфейсу.
В качестве бонуса этот интерфейс также упрощает тестирование приложения, поскольку вы можете легко смоделировать интерфейс (или создать заглушку).
Интерфейс также позволяет вам развивать ваш уровень данных. Например, вы можете начать с использования базы данных для всех классов репозитория. Но позже вы захотите перенести некоторую логику за веб-сервис. Тогда вам нужно только заменить хранилище БД на хранилище WCF. Вы также можете обнаружить, что репозиторий работает медленно, и вы захотите внедрить в него просто кэш памяти (используя memcache или что-то еще)
Я нашел очень полезную страницу msdn, демонстрирующую идею разработки через репозиторий и тестирование. http://blogs.msdn.com/b/adonet/archive/2009/12/17/walkthrough-test-driven-development-with-the-entity-framework-4-0.aspx