如何使用用于单元测试的私有设置器将模拟接口分配给属性

本文关键字:模拟 接口 分配 属性 设置 用于 何使用 单元测试 | 更新日期: 2023-09-27 18:14:47

[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方法。

如何使用用于单元测试的私有设置器将模拟接口分配给属性

如果您想保留您的MEF导入,最简单的方法是在每个属性上使用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()。如果你的抽象类被到处使用,特别是当它决定添加另一个导入属性时,这可能会变得非常难看……现在您必须更改构造函数签名。

我会坚持使用丑陋的反射…丑陋的单元测试胜过丑陋的代码。

我相信您可以使用MS mole框架完成此任务:

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

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

MyAbstractClass依赖于IFoo,但您没有明确表示。你应该添加一个构造函数来明确依赖:

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

现在您可以使用mock轻松地对它进行测试。

所以,我重写你的类,像这样:

[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。这是恶心。

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

另一个选项(如果可以将setter设置为protected而不是private——这里可能是这种情况,也可能不是这种情况,但如果您确实有一个具有类似愿望的protected成员)将使测试子类成为被测试类。它感觉很脏,看起来不像是一个好主意,但我想不出一个实际的理由为什么它不好,并且可以实现这里的目标。

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

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

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

并将抽象类中的"private set"更改为"protected set"