Модульный тест .NET без конструктора без параметров для облегчения внедрения зависимостей.

Я пытаюсь, чтобы модульные тесты не полагались на вызов container.Resolve‹T›() для своих зависимостей.

В настоящее время я использую AutoFac 2.2.4 и пробовал xUnit.NET и NUnit, но у обоих есть эта проблема:

Для этого объекта не определен конструктор без параметров

Как обойти эту проблему? Поддерживает ли это конкретная среда модульного тестирования или просто то, как она настроена?

Я не должен этого делать? Или я могу настроить тестовый класс для работы с конструктором, который имеет только зависимость?

Вот часть кода:

public class ProductTests : BaseTest
{
    readonly private IProductRepository _repo;

    public ProductTests(IProductRepository r)
    {
        _repo = r;
    }

    //working unit tests here with default constructor
} 

Я решил неправильно инициализировать контейнер в конструкторе базового класса?

public abstract class BaseTest
{
    protected BaseTest()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<ProductRepository>().As<IProductRepository>();
        builder.Build();
    }
}

person Nick Josevski    schedule 07.12.2010    source источник
comment
Зачем тестовому классу нужен конструктор? Поместите инъекцию в метод настройки.   -  person Adam Vandenberg    schedule 07.12.2010


Ответы (2)


Первоначальная проблема действительно связана с тем, как спроектированы среды тестирования. Все они требуют конструктора без параметров для создания экземпляров тестов. И это правильно. В этих средах нельзя полагаться на конструктор для инициализации теста. Это цель метода SetUp. В общем, сами тестовые классы для инъекции не годятся.

И ИМО, это становится не проблемой, когда вы разрабатываете свои тесты, чтобы они не зависели от контейнера. В конце концов, каждый тестовый класс должен сосредоточиться на одной «тестируемой системе» (SUT). Почему бы методу установки не создать экземпляр этой системы напрямую и не предоставить каждую зависимость (обычно в виде подделок)? Сделав это таким образом, вы фактически удалили из своих тестов еще одну ненужную зависимость, а именно инфраструктуру IoC.

На заметку: единственный раз, когда я использую инфраструктуру IoC в своих тестах, это мои «контейнерные тесты». Эти тесты направлены на проверку того, что определенные службы могут быть разрешены из контейнера после того, как контейнер был инициализирован с помощью модулей приложений или сборок< /а>.

person Peter Lillevold    schedule 07.12.2010
comment
+1. Я полностью согласен с Петром. Не используйте контейнер в своих методах тестирования, а создайте свою SUT вручную (или с помощью фиктивной среды). - person Steven; 07.12.2010
comment
+1 также - если у компонента слишком много зависимостей, чтобы его было удобно создавать вручную в тестовом приспособлении, у него слишком много обязанностей. - person Nicholas Blumhardt; 08.12.2010
comment
спасибо Питеру, Стивену и Николасу за обоснование того, почему такой подход не нужен. Я согласен с тем, что этот подход может поддерживать создание тестов, которые могут легко выйти из-под контроля и превысить границы своих единиц. - person Nick Josevski; 08.12.2010
comment
Просто добавьте, что фреймворки для тестирования можно использовать не только для модульного тестирования, но и для автоматизации тестов более высокого уровня. В этом случае использование DI-контейнера может быть полезно для сопоставления с производственными зависимостями и упрощения подключения SUT. - person Marc Climent; 28.05.2015

Я просто позволяю своим тестам зависеть от Autofac, хотя и инкапсулирую его. Все мои TestFixtures наследуются от Fixture, который определен как таковой:

public class Fixture
{
    private static readonly IContainer MainContainer = Ioc.Build();
    private readonly TestLifetime _testLifetime = new TestLifetime(MainContainer);

    [SetUp]
    public void SetUp()
    {
        _testLifetime.SetUp();
    }

    [TearDown]
    public void TearDown()
    {
        _testLifetime.TearDown();
    }

    protected TService Resolve<TService>()
    {
        return _testLifetime.Resolve<TService>();
    }

    protected void Override(Action<ContainerBuilder> configurationAction)
    {
        _testLifetime.Override(configurationAction);
    }
}

public class TestLifetime
{
    private readonly IContainer _mainContainer;

    private bool _canOverride;
    private ILifetimeScope _testScope;

    public TestLifetime(IContainer mainContainer)
    {
        _mainContainer = mainContainer;
    }

    public void SetUp()
    {
        _testScope = _mainContainer.BeginLifetimeScope();
        _canOverride = true;
    }

    public void TearDown()
    {
        _testScope.Dispose();
        _testScope = null;
    }

    public TService Resolve<TService>()
    {
        _canOverride = false;
        return _testScope.Resolve<TService>();
    }

    public void Override(Action<ContainerBuilder> configurationAction)
    {
        _testScope.Dispose();

        if (!_canOverride)
            throw new InvalidOperationException("Override can only be called once per test and must be before any calls to Resolve.");

        _canOverride = false;
        _testScope = _mainContainer.BeginLifetimeScope(configurationAction);
    }
}
person Jim Bolla    schedule 13.12.2010