Autofixture 和只读属性
Autofixture and read only properties
让我们考虑同一个非常简单的实体的两个版本(一个具有只读属性):
public class Client
{
public Guid Id { get; set; }
public string Name { get; set; }
}
对比
public class Client
{
public Client(Guid id, string name)
{
this.Id = id;
this.Name = name;
}
public Guid Id { get; }
public string Name { get; }
}
当我尝试使用 Autofixture 时,它会正常工作,并且符合预期。当我尝试使用 .with()
方法预定义其中一个参数时,问题就开始了:
var obj = this.fixture.Build<Client>().With(c => c.Name, "TEST").Build();
这将引发错误
System.ArgumentException: The property "Name" is read-only.
不过Autofixture好像知道怎么使用构造函数!似乎实际的 Build<>()
方法创建了一个对象的实例,而不是 Create()
!如果构建只是准备生成器,将设置属性,然后创建将实例化对象,它将与只读属性一起正常工作。
那么为什么要在这里使用这种(误导性的)策略呢?我找到了一个答案 here,指出它是为了通过测试放大反馈,但我看不到使用 FromFactory()
的用处,尤其是当参数列表很长时。将对象实例化从 Build()
方法移动到 Create()
方法不是更直观吗?
AutoFixture 确实能够创建构造函数参数并调用构造函数。如何控制特定的构造函数参数是一个常见问题解答,所以如果那是唯一的问题,我会把它作为
的副本关闭
然而,这个 post 也询问了 Build
API 行为背后的设计选择,我将在这里回答。
在第二个例子中,Name
是只读的属性,你不能改变只读的属性的值。这是 .NET(以及大多数其他语言)的一部分,而不是 AutoFixture 的设计选择。
让我们明确一点:Name
是 属性。从技术上讲,它与 class' 构造函数无关。
我假设您认为 Name
与构造函数的 name
参数相关联,因为一个暴露了另一个,但我们只知道这一点,因为我们有源代码。外部观察者没有技术上安全的方法来确保这两者已连接。外部观察者,例如 AutoFixture,可能会尝试猜测存在这样的连接,但是没有 gua运行tees。
像这样编写代码在技术上是可行的:
public class Person
{
public Person(string firstName, string lastName)
{
this.FirstName = lastName;
this.LastName = firstName;
}
public string FirstName { get; }
public string LastName { get; }
}
这编译得很好,即使值被调换了。 AutoFixture 将无法检测到此类问题。
当您引用只读的 属性 时,Build
API 会尝试猜测 'what you mean',但返回时,可能会给 AutoFixture 一个启发式当我还是这个项目的仁慈独裁者时,我认为这是一个具有非战争运行复杂性的特性。新的维护者可能对这个主题有不同的看法。
作为一般观察,我认为整个 Build
API 是一个错误。在过去的许多年里,我用 AutoFixture 编写测试,我从未使用过它 API。如果我今天仍然 运行 这个项目,我会反对 API 因为它会导致人们以脆弱的方式使用 AutoFixture。
所以这是一个非常明确的设计选择。
我也为此苦苦挣扎,因为我的大部分 类 通常都是只读的。 Json.Net 等一些库使用命名约定来理解影响每个 属性.
的构造函数参数是什么
确实有一种方法可以使用 ISpecimenBuilder
界面自定义 属性:
public class OverridePropertyBuilder<T, TProp> : ISpecimenBuilder
{
private readonly PropertyInfo _propertyInfo;
private readonly TProp _value;
public OverridePropertyBuilder(Expression<Func<T, TProp>> expr, TProp value)
{
_propertyInfo = (expr.Body as MemberExpression)?.Member as PropertyInfo ??
throw new InvalidOperationException("invalid property expression");
_value = value;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
return new NoSpecimen();
var camelCase = Regex.Replace(_propertyInfo.Name, @"(\w)(.*)",
m => m.Groups[1].Value.ToLower() + m.Groups[2]);
if (pi.ParameterType != typeof(TProp) || pi.Name != camelCase)
return new NoSpecimen();
return _value;
}
}
如您所见,尝试在 Build<>
api 上使用它是一条死胡同。所以我必须为自己创建扩展方法:
public class FixtureCustomization<T>
{
public Fixture Fixture { get; }
public FixtureCustomization(Fixture fixture)
{
Fixture = fixture;
}
public FixtureCustomization<T> With<TProp>(Expression<Func<T, TProp>> expr, TProp value)
{
Fixture.Customizations.Add(new OverridePropertyBuilder<T, TProp>(expr, value));
return this;
}
public T Create() => Fixture.Create<T>();
}
public static class CompositionExt
{
public static FixtureCustomization<T> For<T>(this Fixture fixture)
=> new FixtureCustomization<T>(fixture);
}
这使我能够像这样使用:
var obj =
new Fixture()
.For<Client>()
.With(x => x.Name, "TEST")
.Create();
希望这对您有所帮助
您好,我遇到了类似的问题,我使用 `Freeze
解决了它
_formFileMock = _fixture.Freeze<Mock<IFormFile>>();
_formFileMock.Setup(m => m.ContentType).Returns("image/jpeg");
_fixture.Create<P>
让我们考虑同一个非常简单的实体的两个版本(一个具有只读属性):
public class Client
{
public Guid Id { get; set; }
public string Name { get; set; }
}
对比
public class Client
{
public Client(Guid id, string name)
{
this.Id = id;
this.Name = name;
}
public Guid Id { get; }
public string Name { get; }
}
当我尝试使用 Autofixture 时,它会正常工作,并且符合预期。当我尝试使用 .with()
方法预定义其中一个参数时,问题就开始了:
var obj = this.fixture.Build<Client>().With(c => c.Name, "TEST").Build();
这将引发错误
System.ArgumentException: The property "Name" is read-only.
不过Autofixture好像知道怎么使用构造函数!似乎实际的 Build<>()
方法创建了一个对象的实例,而不是 Create()
!如果构建只是准备生成器,将设置属性,然后创建将实例化对象,它将与只读属性一起正常工作。
那么为什么要在这里使用这种(误导性的)策略呢?我找到了一个答案 here,指出它是为了通过测试放大反馈,但我看不到使用 FromFactory()
的用处,尤其是当参数列表很长时。将对象实例化从 Build()
方法移动到 Create()
方法不是更直观吗?
AutoFixture 确实能够创建构造函数参数并调用构造函数。如何控制特定的构造函数参数是一个常见问题解答,所以如果那是唯一的问题,我会把它作为
然而,这个 post 也询问了 Build
API 行为背后的设计选择,我将在这里回答。
在第二个例子中,Name
是只读的属性,你不能改变只读的属性的值。这是 .NET(以及大多数其他语言)的一部分,而不是 AutoFixture 的设计选择。
让我们明确一点:Name
是 属性。从技术上讲,它与 class' 构造函数无关。
我假设您认为 Name
与构造函数的 name
参数相关联,因为一个暴露了另一个,但我们只知道这一点,因为我们有源代码。外部观察者没有技术上安全的方法来确保这两者已连接。外部观察者,例如 AutoFixture,可能会尝试猜测存在这样的连接,但是没有 gua运行tees。
像这样编写代码在技术上是可行的:
public class Person
{
public Person(string firstName, string lastName)
{
this.FirstName = lastName;
this.LastName = firstName;
}
public string FirstName { get; }
public string LastName { get; }
}
这编译得很好,即使值被调换了。 AutoFixture 将无法检测到此类问题。
当您引用只读的 属性 时,Build
API 会尝试猜测 'what you mean',但返回时,可能会给 AutoFixture 一个启发式当我还是这个项目的仁慈独裁者时,我认为这是一个具有非战争运行复杂性的特性。新的维护者可能对这个主题有不同的看法。
作为一般观察,我认为整个 Build
API 是一个错误。在过去的许多年里,我用 AutoFixture 编写测试,我从未使用过它 API。如果我今天仍然 运行 这个项目,我会反对 API 因为它会导致人们以脆弱的方式使用 AutoFixture。
所以这是一个非常明确的设计选择。
我也为此苦苦挣扎,因为我的大部分 类 通常都是只读的。 Json.Net 等一些库使用命名约定来理解影响每个 属性.
的构造函数参数是什么确实有一种方法可以使用 ISpecimenBuilder
界面自定义 属性:
public class OverridePropertyBuilder<T, TProp> : ISpecimenBuilder
{
private readonly PropertyInfo _propertyInfo;
private readonly TProp _value;
public OverridePropertyBuilder(Expression<Func<T, TProp>> expr, TProp value)
{
_propertyInfo = (expr.Body as MemberExpression)?.Member as PropertyInfo ??
throw new InvalidOperationException("invalid property expression");
_value = value;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
return new NoSpecimen();
var camelCase = Regex.Replace(_propertyInfo.Name, @"(\w)(.*)",
m => m.Groups[1].Value.ToLower() + m.Groups[2]);
if (pi.ParameterType != typeof(TProp) || pi.Name != camelCase)
return new NoSpecimen();
return _value;
}
}
如您所见,尝试在 Build<>
api 上使用它是一条死胡同。所以我必须为自己创建扩展方法:
public class FixtureCustomization<T>
{
public Fixture Fixture { get; }
public FixtureCustomization(Fixture fixture)
{
Fixture = fixture;
}
public FixtureCustomization<T> With<TProp>(Expression<Func<T, TProp>> expr, TProp value)
{
Fixture.Customizations.Add(new OverridePropertyBuilder<T, TProp>(expr, value));
return this;
}
public T Create() => Fixture.Create<T>();
}
public static class CompositionExt
{
public static FixtureCustomization<T> For<T>(this Fixture fixture)
=> new FixtureCustomization<T>(fixture);
}
这使我能够像这样使用:
var obj =
new Fixture()
.For<Client>()
.With(x => x.Name, "TEST")
.Create();
希望这对您有所帮助
您好,我遇到了类似的问题,我使用 `Freeze
解决了它 _formFileMock = _fixture.Freeze<Mock<IFormFile>>();
_formFileMock.Setup(m => m.ContentType).Returns("image/jpeg");
_fixture.Create<P>