如何使用用于单元测试的私有设置器将模拟接口分配给属性
本文关键字:模拟 接口 分配 属性 设置 用于 何使用 单元测试 | 更新日期: 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"