Модульное тестирование Rhino (родительский класс не содержит конструктора, который принимает 0 аргументов) - PullRequest
2 голосов
/ 29 февраля 2012

Rhino Mock Эксперты, пожалуйста, помогите.

Ниже приведен пример использования Rhino Mock.

internal class TestClass
{
    private ITestInterface testInterface;

    public TestClass(ITestInterface testInterface)
    { 
       this.testInterface = testInterface;
    }

    public bool Method1(int x)
    {
        testInterface.Method1(x);
        return true;
    }

}

[TestClass]
public class UnitTest2
{
    [TestMethod]
    public void TestMethod1()
    {
        ITestInterface mockProxy = MockRepository.GenerateMock<ITestInterface>();

        TestClass tc = new TestClass(mockProxy);
        bool result = tc.Method1(5);           

        Assert.IsTrue(result);


        mockProxy.AssertWasCalled(x => x.Method1(5));
    }
}

Проблема, с которой я столкнулся в соответствии с приведенным выше кодом, заключается в том, что мне нужно создать конструктор во всех дочерних классах, например так:у меня около 50 детских классов?

1 Ответ

4 голосов
/ 29 февраля 2012

Как уже упоминалось в моем комментарии к вашему вопросу, на самом деле это не проблема, вызванная насмешкой или юнит-тестированием. Это естественное следствие использования инжектора конструктора (т. Е. Предоставление Зависимости в качестве аргументов конструктора). Альтернативой является использование инъекции свойства, то есть создание свойства с возможностью записи, с помощью которого вы можете установить свой объект ITestInterface, например:

public class TestClass {
    public ITestInterface FooBar {protected get; set; }
}

Таким образом, все производные классы имеют доступ к FooBar без необходимости создавать свой собственный конструктор, который передает ITestInterface в базовый класс.

Однако это имеет и другие последствия: внедрение в конструктор обычно выражает обязательные зависимости. Без этой зависимости класс не будет работать. Однако внедрение свойства часто используется для выражения необязательных зависимостей или способа переопределения поведения по умолчанию. Однако это различие не установлено, и если у вас действительно есть десятки классов, производных от внедрения конструктора TestClass, это может стать настоящим бременем для обслуживания.

Если объект требует аргументов конструктора, для пользователя совершенно очевидно, что он должен передать требуемые зависимости. При внедрении свойства гораздо проще забыть установить свойство для правильной настройки объекта (если требуется зависимость). Кроме того, при внедрении конструктора вам нужно только проверить, была ли зависимость предоставлена ​​один раз (в конструкторе), тогда как при внедрении свойства более вероятно, что вам нужно проверять достоверность зависимости каждый раз, когда вы ее используете.

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

public static class TestClassInitializer{
    public T Initialize<T>(this T t, ITestInterface dependency) where T:TestClass{
        t.FooBar = dependency;
        return t;
    }
}

Если вы можете заставить себя создавать такие объекты:

var tc = new DerivedTestClass().Initialize(mockTestInterface);

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

public static class TestClassInitializer{
    public T Initialize<T>(this T t, ITestInterface dependency, IOtherDependency other) where T:TestClass{
        t.FooBar = dependency;
        t.OtherDependency = other;
        return t;
    }
}

И все места, где вы вызываете Initialize, прервутся во время компиляции. Это хорошо (!), Потому что вам нужно для предоставления необходимых зависимостей. Однако вам нужно только обновить эти места, а не 50 производных классов, чтобы добавить дополнительную зависимость. Если вы использовали конструктор-инъекцию, вам нужно обновить все 50 классов и во всех местах, где вы их создаете.

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

public static class TestClassFactory{
    public static T Create<T>(ITestInterface dep1, ITestInterface dep2) where T :TestClass, new(){
        return new T{FooBar = dep1, OtherDependency = dep2};
    }
}

Вместо new DerivedTestClass().Initialize(...) теперь вы можете использовать TestClassFactory.Create<DerivedTestClass>(mockedDep1, mockedDep2);. Вы можете сделать фактические конструкторы (защищенными) внутренними, если хотите, если TestClassFactory находится в той же сборке (или в сборке, которая может обращаться к внутренним конструкторам через InternalsVisibleTo).

Все это не является проблемой, если вы используете инфраструктуру внедрения зависимостей / контейнер IoC, так как не забудете предоставить зависимости после правильной настройки.

...