Как и во многих других случаях, связанных с AutoFixture, все становится намного проще, если вы можете использовать более явное моделирование предметной области. Вместо того, чтобы моделировать StreetName
как string
, введите для него объект предметной области:
public sealed class StreetName
{
private readonly string value;
public StreetName(string streetName)
{
value = streetName ?? throw new ArgumentNullException(nameof(streetName));
}
public override bool Equals(object obj)
{
var other = obj as StreetName;
if (other == null)
return base.Equals(obj);
return Equals(value, other.value);
}
public override int GetHashCode()
{
return value.GetHashCode();
}
public override string ToString()
{
return value;
}
public static implicit operator string(StreetName streetAddress)
{
return streetAddress.value;
}
public static implicit operator StreetName(string streetAddress)
{
return new StreetName(streetAddress);
}
}
Это один из тех шагов моделирования, которые болезненны в C# и Java, но будут однострочными в F# или Haskell...
Предположим, однако, что у нас есть список предопределенных названий улиц:
public static class StreetNames
{
public static IEnumerable<string> Values = new[] {
"221 B Baker St.",
"1313 Webfoot Walk",
"420 Paper St.",
"42 Wallaby Way"
/* More addresses go here... */ };
}
Теперь вы можете тривиально указать AutoFixture выбирать только из этого списка, используя ElementsBuilder
:
var fixture = new Fixture();
fixture.Customizations.Add(
new ElementsBuilder<StreetName>(StreetNames.Values.Select(s => (StreetName)s)));
Однако на данном этапе это означает, что когда вы создаете значения StreetName
с помощью AutoFixture, он будет выбирать из StreetNames.Values
, но по-прежнему не будет этого делать, когда вы попросите его создать значения Address
. Вы можете решить (ха-ха) эту проблему с помощью небольшого ISpecimenBuilder
:
public class StreetNameBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi == null || pi.Name != "StreetName" || pi.PropertyType != typeof(string))
return new NoSpecimen();
var sn = context.Resolve(typeof(StreetName));
return (string)(StreetName)sn;
}
}
Теперь вы можете настроить свой Fixture
следующим образом:
var fixture = new Fixture();
fixture.Customizations.Add(
new ElementsBuilder<StreetName>(StreetNames.Values.Select(s => (StreetName)s)));
fixture.Customizations.Add(new StreetNameBuilder());
Теперь он создаст Address
значений из StreetName
значений, выбранных из предопределенного списка.
Если вы не можете изменить свою модель домена, вы все равно можете добавить класс, например StreetName
. Просто добавьте его в базу тестового кода, а не в базу производственного кода.
person
Mark Seemann
schedule
21.02.2018