c#如何测试对接口的引用

时间:2012-12-11 08:29:01

标签: c# unit-testing interface moq

之前我曾问过如何set value of interface in testing a method 。我已成功将Moq框架实现到我的项目中,测试运行正常。

这是我推荐的示例代码:

public void PostEvent(
            eVtCompId inSenderComponentId, 
            eVtEvtId inEventId, 
            long inEventReference, 
            IF_SerializableData inEventData)
{
    if(mEventMap.ContainsKey(inEventId))
    {
        mEventMap[inEventId](inSenderComponentId, inEventReference, inEventData);
    }
}

这里我有4个参数:1st:enum,2nd:另一个enum,3rd:long,4th:一个接口。 然而 ,我错误地认为第4个参数(接口)不应该是接口,而是接口的reference

所以看起来应该是这样的:

public void PostEvent(
       eVtCompId inSenderComponentId, 
       eVtEvtId inEventId, 
       long inEventReference, 
       ref IF_SerializableData inEventData)

给我的样本Moq测试代码(就是这个)......

var serializable = new Mock<IF_SerializableData>();
target.PostEvent(..., serializable.Object);

......不起作用。我已经尝试ref serializable.Object但它仍然无效,因为我收到一条错误,指出ref参数需要引用变量,而不是对象。

有关如何正确测试此问题的任何提示或示例?

1 个答案:

答案 0 :(得分:3)

您需要将Object模拟中的serializable引用复制到本地变量中,然后将其作为ref传递。

IF_SerializableData localRef = serializable.Object;
target.PostEvent(..., ref localRef);

你不能传递ref Serializable.Object因为它是一个属性 - 另见Is it possible to pass properties as "out" or "ref" parameters?,它提供了一个很好的讨论,为什么会这样,以及其他链接的来源。

我的解释

这最终是因为属性不是变量。读/写属性是一对getset访问器方法,提供类似于变量的功能,但至关重要的是,当您get属性时,总是获取基础变量的副本 - 即使该变量具有引用类型。

所以:

public class MyClass {
  private object _property;
  public object Property {
    get { return _property; } //<-- _property reference copied on to stack & returned
                              //    here as a new variable.  Therefore a ref
                              //    on that is effectively meaningless.
                              //    for a ref to be possible you'd need a pointer 
                              //    to the _property variable (in C++ terms)
    set { _property = value; }
  }
}

在这个例子中 - 如果你可以ref MyClass.Property传递给一个方法 - 那将毫无意义,因为它会将一个引用传递给堆栈上的一个瞬态变量 - 即副本Property get accessor; it would not be passing the property by reference. So C# doesn't allow it, even though it could, because it would imply that Property`返回的引用可以通过该方法进行修改 - 当它根本无法修改时。

因此,为什么我们需要从堆栈中捕获该值并将其复制到您的案例中的局部变量中。现在 - 请注意,为了使ref方法设置的新值显示在Mock<T>上,您需要再次将其Object属性设置为局部变量值(如果您可以 - 我不使用Moq,但我认为Mocks是不可变的。)

关于C#是否应该以这种方式自动处理ref Property已经有很多争论(参见前面提到的SO我链接)。在我看来,它类似于ref Derivedref Base不兼容 - 是的,有一种方法可以让语言自动为您处理,但应该如何?在我看来,没有。我对此感到沮丧吗?哦,当然是 - 但我经常发现它突出了真正应该修复的架构弱点(例如,依赖于refout参数,其中返回值可能更好。)

C#允许您通过引用传递属性的唯一方法是将get和set访问器传递给目标方法 - 这根本不会与ref兼容(因为这只是一个记忆位置)。

作为一个品尝者,你必须写下这样的方法:

 public static void MyUglyRefMethod(Func<object> refGet, Action<object> refSet)
 {
   var read = refGet();
   var newValue = new object();
   refSet(newValue);
 }

有了这个,我们可以现在提供ref语义而不是MyClass

 MyClass a = new MyClass();
 MyUglyRefMethod(() => a.Property, (newValue) => a.Property = newValue);
 Assert.IsNotNull(a.Property);

但这简直太难看了。

制作一个方法来获取ref MyClass更简单 - 然后它可以直接写入任何属性。