如何使用私有设置器将模拟接口分配给属性以进行单元测试?

发布于 2024-12-16 01:00:10 字数 476 浏览 5 评论 0原文

[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 的模拟接口,但是如何使用私有设置器将其真正放入其中? MEF 以某种方式能够处理它,并且我不确定我还可以如何测试我的 DoSomething 方法。

[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.
    }
}

Basically, I use MEF to export classes out and get "common" imports. When I want to test these classes I can create mock interfaces of IFoo, but how do I actually get it in there with the private setter? MEF somehow is able to handle it, and I'm not sure how else I can test my DoSomething method.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

娇纵 2024-12-23 01:00:10

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

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

If you want to preserve your MEF Imports, the easiest way to do so is to use the ImportingConstructorAttribute instead of ImportAttributes on each property.

[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.
}

}

The solution kind of stinks because now you have to have all classes that extend from MyAbstractClass each use an ImportingConstructorAttribute and call base(). This can get quite ugly if your abstract class is used all over and especially if it decides to add another imported property... now you have to change the constructor signature.

I'd stick with ugly reflection... better ugly unit tests than ugly code.

只有影子陪我不离不弃 2024-12-23 01:00:10

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

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

I believe you can accomplish this with the MS Moles framework:

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

樱娆 2024-12-23 01:00:10

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

I think the only way is using reflection.

嗼ふ静 2024-12-23 01:00:10

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

否则,你必须使用反射来获取私有设置器。太恶心了。

MyAbstractClass has a dependency on IFoo but you're not making it explicit. You should add a constructor to make the dependency explicit:

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

Now you can easily test it using your mock.

So, I'd rewrite your class like this:

[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) { }
}

Otherwise, you have to use reflection to get at the private setter. That's disgusting.

流殇 2024-12-23 01:00:10

反思是最好的方法。我喜欢在基本测试程序集中创建一个扩展方法,该方法具有有用的功能,例如访问/设置私有成员等。

另一种选择(如果可以接受将设置器设置为受保护而不是私有 - 这里可能是也可能不是这种情况,但如果您确实有一个具有类似愿望的受保护成员)将使您的测试子类化被测试的类。它感觉很脏,似乎不是一个好主意,但我想不出它为什么不好并且可以实现这里目标的实际原因。

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

reflection is the best way to do it. i like to create an extension method in a base test assembly with useful functions like accessing/setting private members and such.

another option (if it is acceptable to make the setter protected instead of private - which may or may not be the case here, but if you do have a protected member with a similar desire) would be have your test subclass the class under test. it feels dirty and doesn't seem like a good idea, but i can't think of a practical reason why it's bad and would accomplish the objective here.

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 });
    }
}
柠北森屋 2024-12-23 01:00:10

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

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

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

Try to pass it in the constructor:

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

and change the "private set" in your abstract class to "protected set".

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文