Как проверить шаблон репозитория с помощью ADO.NET Entity Framework? - PullRequest
9 голосов
/ 01 октября 2009

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

Чтобы прояснить мою точку зрения, я приведу пример:

У меня в доменной модели следующий интерфейс:

public interface IUserRepository
{
    IQueryable<User> FindAllUsers();
    void AddUser(User newUser);
    User GetUserByID(int userID);
    void Update(User userToUpdate);
}

У меня есть следующая реализация интерфейса для тестирования:

public class FakeUserRepository : IUserRepository
{
    private IList<User> _repository;

    public FakeUserRepository()
    {
        _repository = new List<User>();
        ... //create all users for testing purposes

    }

    public IQueryable<User> FindAllUsers()
    {
        return _repository.AsQueryable<User>(); //returns all users
    }

Теперь я создаю несколько тестов:

  1. Can_Add_User
  2. Can_Add_Account для пользователя
  3. Can_Add_ShareSpace для учетной записи пользователя с другим пользователем.

Мой вопрос заключается в том, что после того, как я протестировал все это с моей реализацией FakeUserRepository, мне нужно вернуться и реализовать IUserRepository в моем фактическом наборе данных персистентности (например, SQL), и я должен снова реализовать код, так что мое модульное тестирование фактически не проверяю код, который я на самом деле использую в своем приложении.

Может быть, я что-то упустил.

Спасибо как всегда!

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

public class SQLUserRepository : IUserRepository
{
    private BusinessDomainModel.EntityModel.BusinessHelperAccountDBEntities _repository;

    public SQLUserRepository()
    {
        _repository = new BusinessHelperAccountDBEntities();
    }

    #region IUserRepository Members

    public IQueryable<User> FindAllUsers()
    {
        return _repository.UserSet.AsQueryable();
    }

Ответы [ 3 ]

12 голосов
/ 01 октября 2009

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

5 голосов
/ 15 ноября 2009

Я объясню, что я делаю, почему и сколько я получу от этого.

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

  • MyProject.Repositories.IUserRepository
  • MyProject.Repositories.Fake.UserRepository
  • MyProject.Repositories.SqlServer.UserRepository

С моим поддельным UserRepository я также просто создаю и заполняю коллекцию private IEnumerable<User> (которая является List<User>). Почему у меня это? Я использую этот репозиторий для моей начальной ежедневной разработки (потому что это быстро -> нет доступа к БД == быстро!). Затем я переключаюсь на поддельные респираторы для репозиториев sql (т. Е. Изменяю мою инъекцию зависимостей (ооооооооо!)). Вот почему этот класс / пространство имен существует, в отличие от использования Mocks в моем модульном тесте для «поддельных» вещей. (Такое бывает, но при других обстоятельствах).

Я использую LinqToSql для моего sql-сервера UserRepository. Что касается вашего вопроса, то неважно, что я использую LinqToSql ... это может быть любая другая оболочка базы данных. Здесь важно то, что есть стороннее что-то , в которое я интегрирую с.


Хорошо, отсюда мне нужно убедиться в двух вещах

  1. Ложный пользовательский репозиторий работает
  2. Работает sql-сервер UserRepository.

Прежде всего, большинство людей не создают модульный тест для фальшивых вещей. Это поддельный кусок какашки, так зачем тратить энергию? Правда --- за исключением того, что я использую этот фальшивый кусок какашки в своем повседневном развитии (см. Мой комментарий об этом выше). Так что я быстро соберу несколько базовых юнит-тестов. ПРИМЕЧАНИЕ: В мои глаза это модульные тесты, хотя они repository классов. Зачем? Они не интегрируют с третьей стороной / инфраструктурой.

Далее (наконец-то я дошел до сути) я делаю отдельный тестовый класс, который является интеграционным тестом. Это модульный тест, который интегрируется с чем-то вне системы. Это может быть настоящий API Twitter. Это может быть настоящий API S3 Amazon. Обратите внимание, что я использовал слово real . Это ключ, здесь. Я интегрируюсь с реальным сервисом вне моего. Как таковой -> это медленно. Каждый раз, когда мне нужно оставить свой компьютер для каких-то данных, это называется , объединяющим , и вы автоматически предполагаете (и ожидаете), что это будет медленно.

Итак, я интегрируюсь с базой данных.

(Nae sayers, пожалуйста, не говорите об этом с дерзкими предположениями, что у вас есть база данных на том же компьютере ... вы покидаете свой ПРИЛОЖЕНИЕ "мир").

Ничего себе. это какой-то роман "Война-и-мир" ... время для каких-то жестких действий, хулиганский код. Давай принесем!

namespace MyProject.Tests.Repositories.SqlServer
{
    // ReSharper disable InconsistentNaming

    [TestClass]
    public class UserRepositoryTests : TestBase
    {
        [ClassInitialize]
        public static void ClassInitialize(TestContext testContext)
        {
            // Arrange.
            // NOTE: this method is inherited from the TestBase abstract class.
            // Eg. protected IUserRepository = 
            //     new MyProject.Respositories.SqlServer
            //         .UserRespository(connectionString);
            InitializeSqlServerTestData();
        }

        [TestMethod]
        public void GetFirst20UsersSuccess()
        {
            // Act.
            var users = _users.GetUsers()
                .Take(20)
                .ToList();

            // Assert.
            Assert.IsNotNull(users);
            Assert.IsTrue(users.Count() > 0);
        }
    }
}

Хорошо, давайте пробежимся по этому щенку.

Прежде всего, это использует модульное тестирование Microsoft - встроенное в бета-версию VS2010 или версию Team2008 VS2008 (или что бы это ни было ... я просто устанавливаю копию, купленную нашей работой).

Во-вторых, всякий раз, когда класс инициализируется впервые (будь то один тест или несколько), он создает context. В моем случае это мой репозиторий Sql Server, который будет использовать контекст LinqToSql. (Ваш будет контекстом EF). Это Arrange часть TDD.

В-третьих, я называю метод -> это часть Act TDD.

Наконец, я проверяю, вернул ли я то, что ожидал -> это часть Assert TDD.


А как насчет обновления БД?

Просто следуйте той же схеме, за исключением того, что вы, возможно, захотите обернуть свой код вызова в транзакцию и откатить ее обратно. В противном случае вы можете получить сотни строк данных, которые могут быть одинаковыми. Недостаток этого? Любые поля identity не будут иметь всю красивую и красивую последовательность нумерации (потому что откат будет «использовать» это число). Не имеет смысла? не волнуйся это продвинутый совет, который я решил добавить, чтобы проверить вас, но это означает приседание для этого адского длинного поста.


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

НТН.

3 голосов
/ 01 октября 2009

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

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

Вот хорошее введение в макетирование и тестирование в C #:

http://refact.blogspot.com/2007/01/mock-objects-and-rhino.html

...