Модульный тест .NET без конструктора без параметров, чтобы облегчить внедрение зависимостей - PullRequest
12 голосов
/ 07 декабря 2010

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

В настоящее время я использую 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();
    }
}

Ответы [ 2 ]

12 голосов
/ 07 декабря 2010

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

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

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

4 голосов
/ 13 декабря 2010

Я просто позволяю моим тестам зависеть от 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);
    }
}
...