使用AutoMoq创建控制器时,Fixture.CreateAnonymous方法会导致测试运行程序出现错误(AutoFixture)。

3
我正在尝试使用AutoFixture的AutoMoqCustomization来通过Fixture.CreateAnonymous方法在单元测试中创建一个ASP.NET MVC2控制器。我已经在TestDriven.NET下的xUnit,xUnit测试GUI和MSTest中尝试过,并且所有的结果都是一样的:测试运行失败。如果需要,在Windows 7 x64上重现这个问题。
要重现这个问题,只需创建一个新的ASP.NET MVC2项目,添加对AutoFixture、AutoMoq和Moq(根据AutoMoq源3.1)的引用,然后尝试以下操作(请参见下面的VS2010 MVC2项目链接)。
[TestMethod]
public void Index()
{
 var fixture = new Fixture().Customize(new AutoMoqCustomization());
    // here's where the error in the test host occurs:
 HomeController controller = fixture.CreateAnonymous<HomeController>();
}

在MSTest中,错误信息如下:
“运行时遇到致命错误。错误地址为0x6465f370,在线程0x2684上。错误代码为0xc0000005。此错误可能是CLR或用户代码的不安全或不可验证部分中的错误。导致该错误的常见源包括COM互操作或PInvoke的用户封送错误,这可能会损坏堆栈。” AfWithMvc重现项目(来自SkyDrive)
1个回答

4

建议解决方案

为了开始可能的解决方案,这应该停止崩溃:

var fixture = new Fixture().Customize(new AutoMoqCustomization());
// This should fix the problem for all Controllers
fixture.Customize<ViewDataDictionary>(c =>
    c.Without(x => x.ModelMetadata));

HomeController controller = fixture.CreateAnonymous<HomeController>();

解释

现在开始解释:

这个测试错误是由AutoFixture的AutoProperties功能引起的,它试图为HomeController.ViewData.ModelMetaData分配一个值。ModelMetaData类有这个构造函数:

public ModelMetadata(
    ModelMetadataProvider provider,
    Type containerType,
    Func<object> modelAccessor,
    Type modelType,
    string propertyName)

这里的罪魁祸首是modelAccessor参数。为了填充该属性,AutoFixture(相当盲目地)反射类型并找到以下单个构造函数:
public Func(object @object, IntPtr method)

深入挖掘后,AutoFixture 可以满足的第一个 IntPtr 构造函数是这个:
public unsafe IntPtr(int value)

默认情况下,Int32实例由确定性递增序列创建,因此在这种情况下,value可能是1或2或类似的小整数。换句话说,我们现在手头上有一个非常无效的不安全指针,这使得进程崩溃。 现在,在正常情况下,我们应该能够通过向Fixture注册Func<object>来修复这个问题,一切都应该很好:
fixture.Register<Func<object>>(() => () => new object());

然而,我已经尝试使用您的复制品,虽然该过程不再以同样的方式崩溃,但测试运行时间很长,最终会因OutOfMemoryException而崩溃。
我不知道ASP.NET MVC如何处理Func<object>,但显然它相当重要。
问题仍然存在,这是AutoFixture中的错误吗?
我认为不是。虽然这肯定不是理想情况,但AutoFixture并没有像其他类型一样特别对待Funcs或Actions,这就是我们看到这种行为的原因。
可能可以通过添加对Func<TResult>的特定支持来解决这个特定行为,但为了保持一致,它还应该支持Func<T, TResult>、Func<T1, T2, TResult>等。在.NET 4中有很多这些委托类型(也有Action等),所以这意味着需要添加对许多类型的支持。
但是那么所有其他在其构造函数中采用IntPtr的类型怎么办?AutoFixture不可能知道它们全部,因此这似乎不是一个可行的方向。
然而,它可能具有的是一个保护程序,防止其尝试在第一时间创建IntPtr实例。这很可能会在2.0 RTW之前添加。
感谢您报告此问题。

谢谢,这是一个很好的解释和修复。就我个人而言,我同意您的想法,即应该保护IntPtr,但不需要添加特定于委托的支持-似乎是不必要的。 - ZeroBugBounce
好的,我尝试了一下 fixture.Inject<Func<object>>(..) 的用法,结合 MVC2 的源代码,发现 MVC 框架需要从 Func<object> 中返回 null 作为终止符/信号。可以从一开始就返回 null,或者在返回 n 个字符串后再返回 null。 - ZeroBugBounce

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接