使用 Moq 模拟不安全的界面
本文关键字:界面 不安全 模拟 Moq 使用 | 更新日期: 2023-09-27 18:20:23
是否可以使用最小起订量来模拟不安全的界面?例如,我有 (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# 规范中,您可以在第 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.Create
或It.Is
方法创建的自己的Match
对象将不起作用,因为这些方法也采用类型参数。
如果省略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(关闭( at Moq.MatcherFactory.CreateMatcher(Expression expression, Boolean isParams( 在Moq.MethodCall..ctor(Mock mock, Func'1 condition, Expression originalExpression, MethodInfo method, Expression[] 参数( at Moq.Mock.Verify(Mock mock, Expression'1 expression, Times times, String failMessage( at Moq.Mock'1.Verify(Expression'1 表达式(
我不太清楚这是从哪里来的,或者如何规避它,所以这也是一个死胡同。
这里最好的情况是,您可以将int*
更改为 IntPtr
,这可以正常模拟。 如果您无法更改类型,那么根据您要验证的内容,您可以制作存根对象而不是依赖 Moq:
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
提供了一个代码示例,说明我通常如何解决此问题。 它涉及以下内容:
创建一个新接口(派生自有问题的接口(,该接口声明接受或返回非托管指针的每个方法的重载,并将其替换为
IntPtr
(因为 Moq 可模拟IntPtr
(。(注意:如果有一个方法仅在返回类型上有所不同,那么我只需稍微更改名称 - 例如我把
Foo()
改成FooIntPtr
使用类实现该新接口(该类在其构造函数中接受上面创建的新接口(。
一个。对于此类中的每个有问题的方法(具有非托管指针的方法(,让它将调用委托给基于 IntPtr 的方法(通过执行适当的强制转换.
b.对于所有其他方法,只需将它们转发到通过构造函数传入的接口即可。在测试方法中:
a. 使用 Moq 模拟新创建的界面版本。b.模拟方法的
IntPtr
版本.
c. 从步骤 2 创建类的实例,并传入模拟。来自 3.a 的对象。d. 使用此实例传递到受测系统。
这样,您可以继续使用Moq来模拟所有方法。
[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; }
}
为了避免这种情况,你可以尝试使用包装类概念。您可以简单地包装原始类或接口。之后可以在 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 类包装原始类型,我们可以根据需要使用/模拟原始对象。
如果您不知道包装类的概念,那么首先要了解它的概念。
http://www.c-sharpcorner.com/Blogs/12038/wrapper-class-in-C-Sharp.aspx