如何使用私有setter为具有单元测试的属性分配模拟接口?

时间:2011-11-15 16:16:25

标签: c# unit-testing reflection moq mef

[InheritedExport(typeof(MyAbstractClass))
public abstract class MyAbstractClass
{
    [Import] protected IFoo Foo { get; private set; }
}

public sealed class MyClass : MyAbstractClass
{
    public void DoSomething()
    {
        if (Foo == null) throw new Exception();

        var = Foo.GetBar();
        //etc.
    }
}

基本上,我使用MEF导出类并获得“常见”导入。当我想测试这些类时,我可以创建IFoo的模拟接口,但是如何使用私有setter实际获得它? MEF以某种方式能够处理它,我不知道我还能怎样测试我的DoSomething方法。

6 个答案:

答案 0 :(得分:3)

如果您想保留MEF Imports,最简单的方法是在每个媒体资源上使用ImportingConstructorAttribute代替ImportAttribute

[InheritedExport(typeof(MyAbstractClass))
public abstract class MyAbstractClass
{
    [ImportingConstructor]
    protected MyAbstractClass(IFoo foo)
    {
        //BONUS! Null check here instead...
        if (foo == null) throw new NullArgumentException("foo");

        Foo = foo;
    }

    protected IFoo Foo { get; private set; }
}

public sealed MyClass : MyAbstractClass
{
    [ImportingConstructor]
    public MyClass(IFoo foo) : base(foo) { }

public void DoSomething()
{
    var = Foo.GetBar();
    //etc.
}

}

解决方案有点臭,因为现在你必须让所有从MyAbstractClass扩展的类都使用ImportingConstructorAttribute并调用base()。如果你的抽象类被全部使用,尤其是如果它决定添加另一个导入的属性,那么这会非常难看......现在你必须改变构造函数签名。

我坚持用丑陋的反思......比丑陋的代码更好的丑陋单元测试。

答案 1 :(得分:1)

我认为唯一的方法是使用反射。

答案 2 :(得分:1)

MyAbstractClass依赖于IFoo,但您并未将其明确化。您应该添加构造函数以使依赖项显式:

public MyAbstractClass(IFoo foo) { this.Foo = foo; }

现在您可以使用模拟轻松测试它。

所以,我会像这样改写你的课程:

[InheritedExport(typeof(MyAbstractClass))
public abstract class MyAbstractClass {
    private readonly IFoo foo;
    public IFoo Foo {
        get {
            Contract.Ensures(Contract.Result<IFoo>() != null);  
            return this.foo;
        }
    }

    protected MyAbstractClass(IFoo foo) {
        Contract.Requires(foo != null);
        this.foo = foo;
    }
}

public class MyClass : MyAbstractClass
{
    [ImportingConstructor]
    public MyClass(IFoo foo) : base(foo) { }
}

否则,您必须使用反射来获取私有的setter。那太恶心了。

答案 3 :(得分:1)

我相信你可以通过MS Moles框架实现这一目标:

http://research.microsoft.com/en-us/projects/moles/

答案 4 :(得分:1)

反射是最好的方法。我想在基础测试程序集中创建一个扩展方法,其中包含访问/设置私有成员等有用的功能。

另一个选项(如果可以使setter受保护而不是私有 - 这可能是或不是这里的情况,但如果你确实有一个有类似愿望的受保护成员)将让你的测试子类成为类在测试中。它感觉很脏,看起来不是一个好主意,但我想不出一个实际的原因,为什么它很糟糕,并将在这里实现目标。

public static class ReflectionExtensions
{
    public static T GetPrivateFieldValue<T>(this object instance, string fieldName)
    {
        var field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        if (field != null)
        {
            return (T) field.GetValue(instance);
        }
        throw new ArgumentException("The field specified could not be located.", "fieldName");
    }

    public static void SetReadonlyProperty(this object instance, string propertyName, object value)
    {
        instance.GetType().GetProperty(propertyName).SetValue(instance, value, null);
    }

    public static void SetStaticReadonlyProperty(this Type type, string propertyName, object value)
    {
        type.GetProperty(propertyName).GetSetMethod(true).Invoke(null, new[] { value });
    }
}

答案 5 :(得分:-1)

尝试在构造函数中传递它:

class MyClass : MyAbstractClass
{
    public MyClass (IFoo foo)
    {
        Foo = foo;
    }
}

并将抽象类中的“私有集”更改为“受保护集”。