TDD - Хотите проверить мой уровень обслуживания с помощью поддельного репозитория, но как? - PullRequest
13 голосов
/ 01 июня 2009

Я разработал приложение, которое использует шаблон хранилища, а затем отдельный сервисный уровень, такой как:

</p> <pre><code>public class RegistrationService: IRegistrationService { public void Register(User user) { IRepository<User> userRepository = new UserRepository(); // add user, etc } }

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

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

Предложения

Ответы [ 7 ]

16 голосов
/ 01 июня 2009

Вам нужно использовать Dependency Injection. UserRepository - это зависимость вашего класса RegistrationService. Чтобы сделать ваши классы правильно тестируемыми модулями (т. Е. В изоляции их зависимостей), вам необходимо «инвертировать» то, что контролирует создание ваших зависимостей. В настоящее время у вас есть прямой контроль, и вы создаете их внутри. Просто инвертируйте этот элемент управления и позвольте чему-то внешнему (например, контейнеру IoC, например, Castle Windsor) внедрить их:

public class RegistrationService: IRegistrationService
{
    public RegistrationService(IRepository<User> userRepo)
    {
        m_userRepo = userRepo;
    }

    private IRepository<User> m_userRepo;

    public void Register(User user)
    {
        // add user, etc with m_userRepo
    }
}

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

9 голосов
/ 01 июня 2009

Инъекция зависимости может очень пригодиться для таких вещей:

public class RegistrationService: IRegistrationService
{
    public void Register(User user)
    {
        this(user, new UserRepository());
    }

    public void Register(User user, IRepository<User> userRepository)
    {
        // add user, etc
    }

}

Таким образом, вы получаете возможность использовать UserRepository по умолчанию и передавать пользовательский (Mock или Stub для тестирования) IRepository для всего, что вам нужно. Возможно, вы также захотите изменить область действия метода 2-го регистра, если вы не хотите открывать его всем.

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

public class RegistrationService: IRegistrationService
{
    public void Register(User user)
    {
        this(user, IoC.Resolve<IRepository<User>>());
    }

    public void Register(User user, IRepository<User> userRepository)
    {
        // add user, etc
    }

}

Существует множество IoC-контейнеров, перечисленных в ответах других людей, или вы можете свернуть свои .

2 голосов
/ 01 июня 2009

То, что вы должны делать, - это использование Inversion of Control или Dependency инъекций.

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

Существует очень много контейнеров IOC, Unity, Ninject, Castle Windsor и StructureMap.


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

Ваши юнит-тесты, вероятно, должны оставаться «чистыми» и просто работать с объектами, которые вы пытаетесь протестировать. По крайней мере, это то, что работает для меня.

1 голос
/ 03 июня 2009

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

С точки зрения тестирования:

//arrange
var mockrepository = new Mock<IUserDataRepository>();
// InsertUser method takes a MembershipUser and a string Role
mockrepository.Setup( repos => repos.InsertUser( It.IsAny<MembershipUser>(), It.IsAny<string>() ) ).AtMostOnce();

//act
var service = new UserService( mockrepository.Object );
var createResult = service.CreateUser( new MembershipUser(), "WebUser" );

//assert
Assert.AreEqual( MembershipCreateUserResult.Success, createResult );
mockrepository.VerifyAll();

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

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

1 голос
/ 01 июня 2009

Просто чтобы добавить немного ясности (или, возможно, нет) к тому, что все говорили, способ, которым в основном работает IOC, - вы говорите ему заменить X на Y. Итак, вы бы сделали, если бы приняли IRepository (как заявили другие) в качестве параметра, а затем в IOC вы можете указать, что IRepository следует заменить на MockedRepository (например) для этого конкретного класса.

Если вы используете IoC, который допускает инициализацию web.config, это делает переход от dev к prod очень плавным. Вы просто изменяете соответствующие параметры в файле конфигурации, и вы уже в пути - без необходимости прикасаться к коду. Позже, если вы решите перейти к другому источнику данных - просто отключите его и вы уже в пути.

IoC - это забавная штука.

Кстати, вы можете сделать это и без введения IOC, если все, что вы хотите сделать, это Unit Test. Вы можете сделать это, перегрузив конструктор, чтобы он принял IRepository, и просто передайте имитированное репо таким образом.

0 голосов
/ 01 июня 2009

Мое предложение по-прежнему состоит в том, чтобы переместить хранилище в параметр класса и использовать контейнер IoC для внедрения хранилища при необходимости. Таким образом, код прост и тестируем.

0 голосов
/ 01 июня 2009

Вы можете использовать контейнер IOC, а затем зарегистрировать другой репозиторий.

...