Mar*_*rek 11 c# readonly-attribute autofixture
让我们考虑同一个非常简单的实体的两个版本(一个带有只读属性):
public class Client
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
与
public class Client
{
public Client(Guid id, string name)
{
this.Id = id;
this.Name = name;
}
public Guid Id { get; }
public string Name { get; }
}
Run Code Online (Sandbox Code Playgroud)
当我尝试使用Autofixture时,它们将正常工作,并且可以同时使用它们。当我尝试使用预先定义参数之一时,问题就开始了。with()方法:
var obj = this.fixture.Build<Client>().With(c => c.Name, "TEST").Build();
Run Code Online (Sandbox Code Playgroud)
这将引发错误
System.ArgumentException:属性“名称”是只读的。
但是似乎Autofixture知道如何使用构造函数!而且看来实际Build<>()方法不是创建对象的实例Create()!如果构建仅准备具有设置属性的构建器,然后创建将实例化对象,则它将与只读属性一起正常工作。
那么为什么在这里使用这种(误导)策略呢?我在这里找到了一个答案,指出它是通过测试来放大反馈,但是我看不出使用的用处,FromFactory()尤其是当参数列表很广时。在Build()方法之间移动对象实例化Create()会更直观吗?
Fab*_*eco 14
我也为此苦苦挣扎,因为我的大部分课程通常都是只读的。一些库(如 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;
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
这使我能够使用:
var obj =
new Fixture()
.For<Client>()
.With(x => x.Name, "TEST")
.Create();
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助
实际上,AutoFixture能够创建构造函数参数并调用构造函数。如何控制一个特定的构造函数参数是一个FAQ,因此,如果这是唯一的问题,我会关闭它作为指定单个构造函数参数值的Easy方法的重复方法?
但是,这篇文章还询问了BuildAPI 行为背后的设计选择,我将在这里回答。
在第二个示例中,Name是一个只读属性,您不能更改只读属性的值。这是.NET(和大多数其他语言)的一部分,而不是AutoFixture的设计选择。
我们对此要绝对清楚:Name是一个属性。从技术上讲,它与类的构造函数无关。
我假设您认为Name与构造函数的name参数相关联,因为一个参数公开了另一个参数,但我们仅知道这一点,因为我们拥有源代码。外部观察者没有技术上安全的方法来确保这两者都已连接。外部观察者(例如AutoFixture)可以尝试猜测存在这种连接,但不能保证。
从技术上讲,可以编写如下代码:
public class Person
{
public Person(string firstName, string lastName)
{
this.FirstName = lastName;
this.LastName = firstName;
}
public string FirstName { get; }
public string LastName { get; }
}
Run Code Online (Sandbox Code Playgroud)
即使将值切换了,这也可以正常编译。AutoFixture将无法检测到此类问题。
可能可以给AutoFixture启发式,当BuildAPI引用只读属性时,API会尝试猜测“你的意思”,但是回到我还是该项目的仁慈独裁者时,我认为这是一个功能具有不必要的复杂性。新的维护人员在该主题上的外观可能会有所不同。
作为一般观察,我认为整个BuildAPI都是错误的。在过去的几年中,我使用AutoFixture编写了测试,但从未使用过该API。如果我今天仍然运行该项目,那么我将弃用该API,因为它会使人们以脆弱的方式使用AutoFixture。
因此,这是一个明确的设计选择。