使用最小起订量模拟不安全的接口

问题描述 投票:0回答:3

是否可以使用 Moq 来模拟不安全的接口?例如我有(MCVE):

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public unsafe void TestMethod1()
    {
        Mock<IMyUnsafeInterface> mockDependency = new Mock<IMyUnsafeInterface>();
        mockDependency.Setup(i => i.DoWork(It.IsAny<int*>())); // error on this line

        systemUnderTest.Dependency = mockDependency.Object;
        ...
    }
}

public unsafe interface IMyUnsafeInterface
{
    void DoWork(int* intPtr);
    byte* MethodNotRelevantToThisTest1(byte* bytePtr);
    ComplexObject MethodNotRelevantToThisTest2();
    ...
}

但是我不能使用不安全的类型作为类型参数,我得到了错误:

Error   1   The type 'int*' may not be used as a type argument  

是否可以在不使用类型参数的情况下设置模拟来避免这个问题?

(我知道明显的解决方案是不使用不安全的接口,我想知道是否有解决方案可以处理不安全的接口。)

编辑/更新:可以使用存根类,但如果可以使用 Moq,我想避免这种情况,因为 Moq 提供的代码要少得多。

public unsafe class MyUnsafeClass : IMyUnsafeInterface
{
    public void DoWork(int* intPtr) {
        // Do something
    }
    public byte* MethodNotRelevantToThisTest1(byte* bytePtr) {
        throw new NotImplementedException();
    }
    public ComplexObject MethodNotRelevantToThisTest2() {
        throw new NotImplementedException();
    }
    ...
}
c# unit-testing pointers moq unsafe
3个回答
5
投票

快速回答如果你的类型在方法签名中有指针类型是不可能的。


你看到的错误是因为你不能使用指针作为类型参数。实际上,在 C# 规范 中,您会在第 4.4.1 节(类型参数)中找到:

在不安全的代码中,类型参数可能不是指针类型。

您可以通过更改代码以期望特定指针来避免此特定错误:

Mock<IMyUnsafeInterface> mockDependency = new Mock<IMyUnsafeInterface>();

mockDependency.Setup(i => i.DoWork(any));

// use the mock
mockDependency.Object.DoWork(any);

mockDependency.Verify(p => p.DoWork(any));

但是,Moq 在

Setup
调用中失败,因为它试图为参数创建一个
Matcher
对象(用于将设置与调用匹配),该对象使用参数的类型作为类型参数。这会导致与上述相同的错误。传递通过
Match
Match.Create
方法创建的您自己的
It.Is
对象将不起作用,因为这些方法也采用类型参数。

如果您省略

Setup
调用,利用松散的模拟行为,则 Moq 会因同样的问题而在
Verify
调用中失败。它试图根据参数参数的类型创建一个对象,以便它可以将记录的调用与传入的表达式相匹配。

Moq 还在提供

It
类之前提供了一种较旧的匹配参数的方法,您可以在其中使用
Matcher
属性标记方法:

[Test]
public unsafe void TestMethod1()
{
    int* any = stackalloc int[4];

    Mock<IMyUnsafeInterface> mockDependency = new Mock<IMyUnsafeInterface>();

    // use the mock
    mockDependency.Object.DoWork(any);

    mockDependency.Verify(p => p.DoWork(Is(any)));
}

[Matcher]
public static unsafe int* Is(int* expected) { return null; }
public static unsafe bool Is(int* actual, int* expected)
{
    return actual == expected;
}

这似乎可行,但它失败了,但有一个例外:

System.Security.VerificationException:操作可能会破坏运行时的稳定性。
   在 lambda_method(闭包)
   在 Moq.MatcherFactory.CreateMatcher(表达式表达式,布尔值 isParams)
   在 Moq.MethodCall..ctor(模拟模拟,Func`1 条件,表达式 originalExpression,MethodInfo 方法,表达式 [] 参数)
   在 Moq.Mock.Verify(Mock 模拟,Expression`1 表达式,Times times,String failMessage)
   在 Moq.Mock`1.Verify(Expression`1 expression)

我不太清楚这是从哪里来的或如何规避它,所以这也是一个死胡同。

这里最好的情况是,如果您可以将

int*
更改为
IntPtr
,这可以正常模拟。如果您不能更改类型,那么根据您要验证的内容,您可以制作一个存根对象而不是依赖起订量:

unsafe class StubMyUnsafeInterface : IMyUnsafeInterface
{
    readonly int* expectedIntPtr;

    public StubMyUnsafeInterface(int* expectedIntPtr)
    {
        this.expectedIntPtr = expectedIntPtr;
    }

    public unsafe void DoWork(int* intPtr)
    {
        Assert.That(intPtr == expectedIntPtr);
    }
}

然后在你的测试中:

int* any = stackalloc int[4];
int* other = stackalloc int[4];
var stubMyUnsafeInterface = new StubMyUnsafeInterface(any);

stubMyUnsafeInterface.DoWork(any);   // passes
stubMyUnsafeInterface.DoWork(other); // fails

0
投票

以下是我针对这种情况使用的方法的文字说明。但是,向下滚动到代码并查看其工作原理可能更容易:

  1. 创建一个新接口(派生自有问题的接口),它声明每个采用或返回非托管指针的方法的重载,并将其替换为

    IntPtr
    (因为 IntPtr 可由 Moq 模拟)。

    (注意:如果有一个方法仅在返回类型上有所不同,那么我只是稍微更改名称——例如,我更改了

    Foo()
    to FooIntPtr

  2. 用一个类实现新接口(它的构造函数接受上面创建的新接口)。

    一个。对于此类中的每个有问题的方法(那些具有非托管指针的方法),让它将调用委托给基于 IntPtr 的方法(通过进行适当的转换。
    b.对于所有其他方法,只需将它们转发到通过构造函数传入的接口即可。

  3. 在你的测试中:

    一个。使用 Moq 模拟新创建的界面版本。 b.模拟

    IntPtr
    版本的方法。
    C。从步骤 2 创建您的类的实例,并传入来自 3.a 的 mock.Object。 d.使用此实例传递给被测系统。

这样就可以继续使用Moq来mock所有的方法了

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public unsafe void TestMethod1()
    {
        Mock<IMyUnsafeInterfaceForMoq> mockDependency = new Mock<IMyUnsafeInterfaceForMoq>();
        mockDependency.Setup(i => i.DoWork(It.IsAny<IntPtr>())); // use IntPtr instead of int*

        // Use this wrapper whenever passing it to the system under test:
        var dependency = new MyUnsafeInterfaceMoqWrapper(mockDependency.Object);

        var systemUnderTest = new SystemUnderTest();
        systemUnderTest.Dependency = dependency;
    }
}

public unsafe interface IMyUnsafeInterface
{
    void DoWork(int* intPtr);
    byte* MethodNotRelevantToThisTest1(byte* bytePtr);
    bool MethodNotRelevantToThisTest2();
}

// Interface to work around unsafe pointer types by using IntPtr
internal interface IMyUnsafeInterfaceForMoq : IMyUnsafeInterface
{
    void DoWork(IntPtr intPtr);
    IntPtr MethodNotRelevantToThisTest1(IntPtr bytePtr);
}

// Wrapper to workaround unsafe pointer types by using IntPtr
sealed class MyUnsafeInterfaceMoqWrapper : IMyUnsafeInterfaceForMoq
{
    readonly IMyUnsafeInterfaceForMoq _wrapped;
    public MyUnsafeInterfaceMoqWrapper(IMyUnsafeInterfaceForMoq wrapped)
    {
        _wrapped = wrapped ?? throw new ArgumentNullException(nameof(wrapped));
    }

    // For the methods that don't work with Moq, delegate to the methods that *do* work with moq
    // and cast appropriately.
    public unsafe void DoWork(int* intPtr) => DoWork((IntPtr)intPtr);
    public unsafe byte* MethodNotRelevantToThisTest1(byte* bytePtr) => (byte*)MethodNotRelevantToThisTest1((IntPtr)bytePtr);

    // The rest of the methods just forward on.
    public void DoWork(IntPtr intPtr) => _wrapped.DoWork(intPtr);
    public IntPtr MethodNotRelevantToThisTest1(IntPtr bytePtr) => _wrapped.MethodNotRelevantToThisTest1(bytePtr);
    public bool MethodNotRelevantToThisTest2() => _wrapped.MethodNotRelevantToThisTest2();
}

class SystemUnderTest
{
    public IMyUnsafeInterface Dependency { get; set; }
}

-1
投票

为避免这种情况,您可以尝试使用 Wrapper 类概念。您可以简单地包装您的原始类或接口。之后可以在 Wrapper 类函数中使用您的原始函数。如下例 -

//Wrapper class
public class MyWrapperClass
{
     public YourReturnType MyFunction()
     {
         OriginalClass obj = new OriginalClass();

         //And inside this function call your original function
         return obj.OriginalFunction();
     }

}

Wrapper 类包装了原始类型,我们可以根据需要使用/模拟原始对象。

如果你不知道 Wrapper 类的概念,那么首先要了解它的概念。

http://www.c-sharpcorner.com/Blogs/12038/wrapper-class-in-C-Sharp.aspx

© www.soinside.com 2019 - 2024. All rights reserved.