嗨,我希望能够使用ISpecimenBuilder.CreateMany
基于ICustomization
生成不同的值。 我想知道最好的解决方案是什么,因为 AutoFixture 将为所有实体生成相同的值。
```c#
公共类 FooCustomization : ICustomization
{
公共无效定制(IFixture装置)
{
var 标本 = fixture.Build
.OmitAutoProperties()
.With(x => x.CreationDate, DateTime.Now)
.With(x => x.Identifier, Guid.NewGuid().ToString().Substring(0, 6)) // 应该生成不同的值
.With(x => x.Mail, $"[email protected]")
。创建();
fixture.Register(() => specimen);
}
}
``
PS.:主要目标是减少我的测试代码量,所以我必须避免调用With()
来自定义每个测试的值。 有没有正确的方法来做到这一点?
更新:我刚刚从@ploeh读到了这个回复: https :
这种方法有很多缺点:首先,调用Create<List<Foo>>()
似乎真的违反直觉,因为它有点违背了对CreateMany<Foo>
期望; 这将生成一个硬编码大小的列表> (?)。 另一个缺点是我必须为每个实体进行两次自定义; 一个创建自定义集合,另一个创建单个实例,因为我们正在覆盖
Create<T>
的行为来创建一个集合。
这似乎是Stack Overflow问题的一个很好的候选者。 在这里提出问题并没有错,但我认为这个问题的答案也可能使其他人受益,而且与 GitHub 问题相比,在 Stack Overflow 上更容易找到问题和答案。 一方面,谷歌在索引 Stack Overflow 问题方面做得很好,而它似乎将(关闭的)GitHub 问题排在较低的位置……
@ploeh我同意你的看法....
我刚刚基于此问题创建了一个 SO 问题。 这是未来参考
@ploeh感谢您回答关于 SO 的问题:脸红:
@thiagomajesk实际上,在这里回答问题没有问题。 Mark 建议 SO 的原因是它提供的搜索功能。 目前马克也在监控 SO,所以如果你在那里发布问题,有更多机会获得他的专业知识(就像你这次得到的一样):sweat_smile:
@zvirja感谢您澄清这一点😄
@zvirja我将根据@ploeh的精彩回答结束关于 SO 的问题。
但我想请教一下关于这个实现细节的更多信息。
因为我不想离题更远,我认为最好在这里询问这个细节。 所以我有这个实现:
```c#
公共类 UniqueShortGuidBuilder
{
私有只读字符串 propName;
私有只读长度;
public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
propName = ((MemberExpression)expr.Body).Member.Name;
this.lenght = lenght;
}
公共对象创建(对象请求,ISpecimenContext 上下文)
{
var pi = 请求为 PropertyInfo;
if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
return new NoSpecimen();
return Guid.NewGuid().ToString().Substring(0, lenght);
}
``
我期望在使用 AutoFixture 创建许多对象时, ISpecimenBuilder.Create
会被多次调用,为我的实体生成不同的 Guid; 但显然这不是真的,他们实际上共享同一个。
好吧,我刚刚测试了你的代码,它完全符合预期:
```c#
公开课 Foo
{
公共字符串 PropertyToCustomize { 获取; 放; }
}
公共类 UniqueShortGuidBuilder
{
私有只读字符串 propName;
私有只读长度;
public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
propName = ((MemberExpression) expr.Body).Member.Name;
this.lenght = lenght;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
return new NoSpecimen();
return Guid.NewGuid().ToString().Substring(0, lenght);
}
}
[事实]
public void TestCustomization()
{
var fixture = new Fixture();
fixture.Customizations.Add(新的UniqueShortGuidBuilder
var manyFoos = fixture.CreateMany<Foo>(10);
var uniqueIds = manyFoos
.Select(x => x.PropertyToCustomize)
.Where(x => x.Length == 5)
.Distinct()
.ToArray();
Assert.Equal(10, uniqueIds.Length);
}
``
因此,我想再次请您看看您的实际代码有何不同,以便我们了解为什么这种差异很重要 😟
@zvirja这很奇怪。 下面的代码是我遇到的问题的最小重现。
如果我删除Fixture.Customize(new FooCustomization());
测试将通过,否则不会。
```c#
使用系统;
使用 Microsoft.VisualStudio.TestTools.UnitTesting;
使用 AutoFixture.Kernel;
使用 System.Linq.Expressions;
使用 System.Reflection;
使用 AutoFixture;
使用 System.Linq;
命名空间 UnitTestProject1
{
公开课 Foo
{
公共字符串 PropertyToCustomize { 获取; 放; }
}
public class FooCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
var specimen = fixture.Build<Foo>()
.OmitAutoProperties()
.With(x => x.PropertyToCustomize)
.Create();
fixture.Register(() => specimen);
}
}
public class UniqueShortGuidBuilder<TEntity> : ISpecimenBuilder
{
private readonly string propName;
private readonly int lenght;
public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
propName = ((MemberExpression)expr.Body).Member.Name;
this.lenght = lenght;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
return new NoSpecimen();
return Guid.NewGuid().ToString().Substring(0, lenght);
}
}
[TestClass]
public class UnitTest1
{
public static Fixture Fixture { get; private set; }
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
Fixture = new Fixture();
Fixture.Customizations.Add(new UniqueShortGuidBuilder<Foo>(x => x.PropertyToCustomize, 5));
Fixture.Customize(new FooCustomization());
}
[TestMethod]
public void TestMethod1()
{
var manyFoos = Fixture.CreateMany<Foo>(10);
var uniqueIds = manyFoos
.Select(x => x.PropertyToCustomize)
.Where(x => x.Length == 5)
.Distinct()
.ToArray();
Assert.AreEqual(10, uniqueIds.Length);
}
}
}
``
建议:如果您依赖于从 GUID 字符串中取出前 _n_ 个字符,请不要将类命名为UniqueShortGuidBuilder
。 当长度为 5 时,这些值可能相当独特,但如果将其设置为 1,您可能会看到冲突。 在任何情况下,都不能保证唯一性。
如果你只关心字符串的长度,加上非保证的唯一性,为什么不简单地向 AutoFixture 询问一个介于 0 和 99,999 之间的数字,然后把它变成一个字符串? IIRC,数字在范围耗尽之前是唯一的。
@ploeh感谢您的提示,但该代码只是用于测试目的的粗略示例。 对于实际情况,长度不会短到会影响 Guids 的唯一性😄。
如果你只关心字符串的长度,加上非保证的唯一性,为什么不简单地向 AutoFixture 询问一个介于 0 和 99,999 之间的数字,然后把它变成一个字符串?
我敢打赌,对于大多数情况来说,这是一个不错的解决方案,但由于我的系统中的字母数字验证,我无法针对这种特定情况执行此操作。 除此之外,我还有涉及该领域的更复杂的验证。
好吧,事实上,如果您更仔细地查看情况,这就是预期的行为
@zvirja在您在 #962 中回复后很清楚。 但对我来说,这种行为并不明显,因为没有关于 AutoFixture 的前期(明确)文档(就像我在这里指出的那样)😁。
最后,感谢你们的耐心和所有出色的解释。
我相信这对未来的其他人也很有用😉。
好吧,很公平,我可能倾向于把事情看得太字面化 😄