我们有带有 Entity Framework 的 MVC4 项目用于存储。 对于我们的测试,我们最近开始使用 Autofixture,它真的很棒。
我们的模型图非常深,通常通过 AutoFixture 创建一个对象会创建整个图:Person -> Team -> Departments -> Company -> Contracts -> .... etc.
问题是时间。 创建对象最多需要一秒钟。这会导致测试缓慢。
我发现自己经常做的事情是这样的:
var contract = fixture.Build<PersonContract>()
.Without(c => c.Person)
.Without(c => c.PersonContractTemplate)
.Without(c => c.Occupation)
.Without(c => c.EmploymentCompany)
.Create<PersonContract>();
这行得通而且很快。但是这种过度规范使得测试难以阅读,有时我会在不重要的
.With(c => c.PersonId, 42)
列表中丢失重要的细节,如 .Without()
。
所有这些被忽略的对象都是实体框架的导航属性,并且都是虚拟的。
是否有全局方式告诉 AutoFixture 忽略虚拟成员?
我试过创建
ISpecimentBuilder
,但没有运气:
public class IgnoreVirtualMembers : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (request.GetType().IsVirtual // ?? this does not exist )
{
return null;
}
}
}
我似乎无法在
ISpecimenBuilder
中找到一种方法来检测我们正在构造的对象是另一个类中的虚拟成员。可能ISpecimenBuilder
这不是做这件事的正确地方。还有其他想法吗?
在 Mark 的博客上阅读更多内容(特别是)我找到了做我想做的事的方法:
/// <summary>
/// Customisation to ignore the virtual members in the class - helps ignoring the navigational
/// properties and makes it quicker to generate objects when you don't care about these
/// </summary>
public class IgnoreVirtualMembers : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var pi = request as PropertyInfo;
if (pi == null)
{
return new NoSpecimen(request);
}
if (pi.GetGetMethod().IsVirtual)
{
return null;
}
return new NoSpecimen(request);
}
}
您可以将这些包装到定制中:
public class IgnoreVirtualMembersCustomisation : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new IgnoreVirtualMembers());
}
}
所以在你的测试中你只需做:
var fixture = new Fixture().Customize(new IgnoreVirtualMembersCustomisation());
然后去创建你的复杂模型。
享受!
我遇到了同样的问题,决定更进一步,创建自定义以延迟加载导航属性。该项目在 Github 和 NuGet.
考虑下面的简单对象图,它具有循环依赖:
class Foo
{
public int Id { get; set; }
public int BarId { get; set; }
public virtual Bar Bar { get; set; }
}
class Bar
{
public int Id { get; set; }
public int FooId { get; set; }
public virtual Foo Foo { get; set; }
}
通过此自定义,调用
var foo = fixture.Create<Foo>()
将创建一个 Foo
类型的对象。调用 foo.Bar
getter 将使用 DynamicProxy 和 AutoFixture 即时创建 Bar
的实例并将其分配给该属性。对 foo.Bar
的后续调用返回相同的对象。
注意定制不够智能,无法设置
foo.Bar.Foo = foo
- 如果需要,必须手动完成
我在使用这段代码时遇到了一个奇怪的问题,因为我所有的实体都继承自这样一个基础模型:
public interface IBaseModel
{
Guid Id { get; set; }
DateTime? DateCreated { get; set; }
Guid? CreatedBy { get; set; }
DateTime? DateLastModified { get; set; }
Guid? LastModifiedBy { get; set; }
}
[ExcludeFromCodeCoverage]
public class BaseModel : IBaseModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public DateTime? DateCreated { get; set; }
public Guid? CreatedBy { get; set; }
public DateTime? DateLastModified { get; set; }
public Guid? LastModifiedBy { get; set; }
}
一旦你实现了这样的接口,你就会发现你所有的
Guid
值都是一个空的guid。
据我所知(在同事的帮助下)它与这篇文章有关,因为Guid属性方法的
get_id()
现在实际上是一个接口方法。
需要添加
!pi.GetGetMethod().IsFinal
的加法检查,区分接口方法和具体类版本。所以我对@trailmax 的解决方案的修改版本如下所示:
public class IgnoreVirtualMembers : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var pi = request as PropertyInfo;
if (pi == null)
{
return new NoSpecimen();
}
if (pi.GetGetMethod().IsVirtual && !pi.GetGetMethod().IsFinal)
{
return null;
}
return new NoSpecimen();
}
}