Пустые параметры модульного теста в конструкторе

Я закодировал конструктор для класса, и я тестирую для каждого параметра, являющегося нулевым. Смотрите пример ниже:

public MyClass(IObjectA objA, IObjectB objB) : IMyClass
{
    if (objA == null)
    {
        throw new ArgumentNullException("objA");
    }

    if (objB == null)
    {
        throw new ArgumentNullException("objB");
    }

    ...
}

Обычно я тестирую модуль (используя Moq), отсекая IObjectA а также IObjectB и передать их. В приведенном выше примере будут созданы 2 модульных теста для тестирования каждого сценария.

У меня проблема, когда третий параметр передается в конструктор. Это требует, чтобы я изменил свои предыдущие тесты, поскольку я внезапно получаю исключение типа "Нет конструктора для MyClass имеет 2 параметра".

Я также использую AutoMockContainer. По сути, я хотел бы иметь возможность проверить конструктор, зарегистрировав нулевой объект в контейнере. Например:

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void ConstructionThrowsExceptionForNullObjA()
{
    // Arrange.
    var container = new AutoMockContainer(new MockRepository(MockBehavior.Default));

    container.Register<IObjectA>(null);

    // Act.
    var sut = container.Create<MyClass>();
}

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

К сожалению, вышеупомянутый модульный тест проходит. Но по неправильной причине. Register<T>() метод бросает ArgumentNullException не код, выполненный в разделе "Акт".

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

1 ответ

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

Упрощенный пример шаблона построителя будет:

public class Foo
{
    public string Prop1 { get; private set; }

    public Foo(string prop1)
    {
        this.Prop1 = prop1;
    }
}

[TestClass]
public class FooTests
{
    [TestMethod]
    public void SomeTestThatRequiresAFoo()
    {
        Foo f = new Foo("a");
        // testy stuff
    }

    [TestMethod]
    public void SomeTestThatRequiresAFooUtilizingBuilderPattern()
    {
        Foo f = new FooBuilder().Build();
    }

    [TestMethod]
    public void SomeTestThatRequiresAFooUtilizingBuilderPatternOverrideDefaultValue()
    {
        Foo f = new FooBuilder()
           .WithProp1("different than default")
           .Build();
    }
}

internal class FooBuilder
{

    public string Prop1 { get; private set; }

    // default constructor, provide default values to Foo object
    public FooBuilder()
    {
        this.Prop1 = "test";
    }

    // Sets the "Prop1" value and returns this, done this way to create a "Fluent API"
    public FooBuilder WithProp1(string prop1)
    {
        this.Prop1 = prop1;
        return this;
    }

    // Builds the Foo object by utilizing the properties created as BuilderConstruction and/or the "With[PropName]" methods.
    public Foo Build()
    {
        return new Foo(
            this.Prop1
        );
    }
}

Таким образом, если / когда ваш объект Foo изменится, вам будет проще обновить свои модульные тесты, чтобы учесть эти изменения.

Рассматривать:

public class Foo
{
    public string Prop1 { get; private set; }
    public string Prop2 { get; private set; }    

    public Foo(string prop1, string prop2)
    {
        this.Prop1 = prop1;
        this.Prop2 = prop2
    }
}

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

internal class FooBuilder
{

    public string Prop1 { get; private set; }
    public string Prop2 { get; private set; }

    // default constructor, provide default values to Foo object
    public FooBuilder()
    {
        this.Prop1 = "test";
        this.Prop2 = "another value";
    }

    // Sets the "Prop1" value and returns this, done this way to create a "Fluent API"
    public FooBuilder WithProp1(string prop1)
    {
        this.Prop1 = prop1;
        return this;
    }

    // Similar to the "WithProp1"
    public FooBuilder WithProp2(string prop2)
    {
        this.Prop2 = prop2;
        return this;
    }

    // Builds the Foo object by utilizing the properties created as BuilderConstruction and/or the "With[PropName]" methods.
    public Foo Build()
    {
        return new Foo(
            this.Prop1,
            this.Prop2
        );
    }
}

С этой новой реализацией Foo и FooBuilder, единственный модульный тест, который сломает это тот, который создал Foo вручную, FooBuilder, использующий модульные тесты, все еще будет работать без ошибок.

Это упрощенный пример, но представьте, если у вас было 20-30 модульных тестов, зависящих от конструкции объекта Foo. Вместо того, чтобы обновлять эти 20-30 модульных тестов, вы можете просто обновить ваш компоновщик, чтобы правильно построить объект Foo.

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

[TestMethod]
public void TestWithNullInFirstParam()
{
    Foo f = new FooBuilder()
        .WithProp1(null)
        .Build()

    // in this case "f" will have Prop1 = null, prop2 = "another value"
}  
Другие вопросы по тегам