【问题标题】:Rhino mock unit testing (Parent class does not contain a constructor that takes 0 arguments)Rhino 模拟单元测试(父类不包含带 0 个参数的构造函数)
【发布时间】:2012-02-29 12:27:04
【问题描述】:

请犀牛模拟专家帮忙。

下面的犀牛模拟示例运行良好。

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));
    }
}

根据上述代码,我面临的问题是我需要在所有子类中创建一个构造函数,如下所示:-.

 internal class testclass1 : TestClass
    {
        protected testclass1(ITestInterface testInterface) : base(testInterface)
        {
        }
    }

我有大约 50 个子类,有什么解决方法吗?

【问题讨论】:

  • 如果我理解正确的话,问题不仅限于测试或模拟,而是一般的面向对象设计。
  • @AndreLoker 不完全是。我想知道rhino mock专家是如何处理这个问题的

标签: c# unit-testing mocking rhino-mocks


【解决方案1】:

正如我在对您的问题的评论中提到的,这实际上并不是由模拟或单元测试引起的问题。这是使用构造函数注入的自然结果(即提供Dependencies 作为构造函数参数)。另一种方法是使用属性注入,即创建一个可写属性,您可以通过该属性设置 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};
    }
}

您现在可以使用TestClassFactory.Create&lt;DerivedTestClass&gt;(mockedDep1, mockedDep2); 代替new DerivedTestClass().Initialize(...)。只要 TestClassFactory 在同一个程序集中(或在可以通过 InternalsVisibleTo 访问内部构造函数的程序集中),您就可以将实际的构造函数(受保护的)设为内部。

如果您使用依赖注入框架/IoC 容器,这一切都不是问题,因为一旦设置正确,它就不会忘记提供依赖。

【讨论】:

    猜你喜欢
    • 2012-08-21
    • 2014-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 2012-01-01
    • 2013-03-07
    • 2011-07-01
    相关资源
    最近更新 更多