TDD - Хотите протестировать мой сервисный уровень с помощью поддельного репозитория, но как?

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

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

код >

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

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

Предложения?

Ответы

Ответ 1

Вам нужно использовать Injection Dependency. 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 теперь использует свой зависимый репозиторий пользователя в качестве входных данных для своего конструктора, вы можете создать mock-репозиторий и передать его вместо реального репозитория.

Ответ 2

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

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 for testing) IRepository для всего, что вам нужно. Вы также можете изменить область действия метода второго регистра в случае, если вы не хотите подвергать его всем.

Еще лучшей альтернативой может быть использование контейнера 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, которые перечислены другими людьми, или вы можете перевернуть свои собственные.

Ответ 3

Что вы должны делать, это использовать инверсию управления или инъекции зависимостей.

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

Очень много контейнеров IOC, Unity, Ninject, Castle Windsor и StructureMap, чтобы назвать несколько.


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

Тестирование вашего модуля должно, вероятно, оставаться "чистым" и просто работать с объектами, которые вы пытаетесь проверить. По крайней мере, то, что работает для меня.

Ответ 4

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

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

IoC - это забавный материал.

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

Ответ 5

Я работаю над чем-то, ну, почти дословно на данный момент. Я использую 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.

Ответ 6

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

Ответ 7

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