Вот предложение по поведению ограничителя глубины.
fixture.Behaviors.Add (новое GenerationDepthBehavior (2));
Пришлось повторно реализовать ComposeIfMultiple для тестирования, потому что он внутренний.
`` С #
открытый класс GenerationDepthBehavior: ISpecimenBuilderTransformation
{
private const int DefaultGenerationDepth = 1;
частное только для чтения int generationDepth;
public GenerationDepthBehavior() : this(DefaultGenerationDepth)
{
}
public GenerationDepthBehavior(int generationDepth)
{
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.generationDepth = generationDepth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth);
}
}
открытый интерфейс IGenerationDepthHandler
{
object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth);
}
открытый класс DepthSeededRequest: SeededRequest
{
public int Depth {получить; }
общедоступный DepthSeededRequest (запрос объекта, начальное значение объекта, глубина целого числа): база (запрос, начальное значение)
{
Глубина = глубина;
}
}
открытый класс GenerationDepthGuard: ISpecimenBuilderNode
{
частный только для чтения ThreadLocal
= новый ThreadLocal
private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;
public GenerationDepthGuard(ISpecimenBuilder builder)
: this(builder, EqualityComparer<object>.Default)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
int generationDepth)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
generationDepth)
{
}
public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer)
{
this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
this.GenerationDepth = 1;
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer)
: this(
builder,
depthHandler,
comparer,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer,
int generationDepth)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler));
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.Builder = builder;
this.GenerationDepthHandler = depthHandler;
this.Comparer = comparer;
this.GenerationDepth = generationDepth;
}
public ISpecimenBuilder Builder { get; }
public IGenerationDepthHandler GenerationDepthHandler { get; }
public int GenerationDepth { get; }
public int CurrentDepth { get; }
public IEqualityComparer Comparer { get; }
protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread();
public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth)
{
return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest(
request,
this.GetMonitoredRequestsForCurrentThread(), currentDepth);
}
public object Create(object request, ISpecimenContext context)
{
if (request is SeededRequest)
{
int currentDepth = -1;
var requestsForCurrentThread = this.GetMonitoredRequestsForCurrentThread();
if (requestsForCurrentThread.Count > 0)
{
currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1;
}
DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth);
if (depthRequest.Depth >= this.GenerationDepth)
{
return HandleGenerationDepthLimitRequest(request, depthRequest.Depth);
}
requestsForCurrentThread.Push(depthRequest);
try
{
return this.Builder.Create(request, context);
}
finally
{
requestsForCurrentThread.Pop();
}
}
else
{
return this.Builder.Create(request, context);
}
}
public virtual ISpecimenBuilderNode Compose(
IEnumerable<ISpecimenBuilder> builders)
{
var composedBuilder = ComposeIfMultiple(
builders);
return new GenerationDepthGuard(
composedBuilder,
this.GenerationDepthHandler,
this.Comparer,
this.GenerationDepth);
}
internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders)
{
ISpecimenBuilder singleItem = null;
List<ISpecimenBuilder> multipleItems = null;
bool hasItems = false;
using (var enumerator = builders.GetEnumerator())
{
if (enumerator.MoveNext())
{
singleItem = enumerator.Current;
hasItems = true;
while (enumerator.MoveNext())
{
if (multipleItems == null)
{
multipleItems = new List<ISpecimenBuilder> { singleItem };
}
multipleItems.Add(enumerator.Current);
}
}
}
if (!hasItems)
{
return new CompositeSpecimenBuilder();
}
if (multipleItems == null)
{
return singleItem;
}
return new CompositeSpecimenBuilder(multipleItems);
}
public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
{
yield return this.Builder;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
открытый класс GenerationDepthHandler: IGenerationDepthHandler
{
публичный объект HandleGenerationDepthLimitRequest (
запрос объекта,
IEnumerable
Здравствуйте, спасибо, что поделились идеей! : +1:
Не могли бы вы описать причину этой функции? Есть ли у вас какой-то конкретный сценарий, в котором вы нашли его полезным?
Привет, я использую его в основном для объектов ORM или внешних классов api, где глубина может распространяться бесконечно, но не рекурсивна.
Я использую AutoFixture для модульных, интеграционных и функциональных тестов. Модульный тест для основных функций корпоративной инфраструктуры. Интеграционные тесты с тестами для сложных единиц работы с шаблоном репозитория и доступом к тестовой базе данных. Функциональный тест с доступом ко всему приложению.
Я поместил это прямо в AutoMoqDataAttribute
`` С #
открытый класс AutoMoqDataAttribute: AutoDataAttribute
{
///
/// Конструктор по умолчанию.
///
/// По умолчанию 0 для неограниченной глубины генерации.
общедоступный AutoMoqDataAttribute (int generationDepth = 0)
: база (() =>
{
IFixture fixture = new Fixture (). Настроить (новый AutoMoqCustomization ());
fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
if (generationDepth > 0)
{
fixture.Behaviors.Add(new GenerationDepthBehavior(generationDepth));
}
return fixture;
})
{
}
}
`` ''
Я также предлагаю сделать ComposeIfMultiple общедоступным. Это упростило бы реализацию настраиваемого поведения.
Я понимаю, что делает поведение, главный вопрос - цель и сценарии.
Привет, я использую его в основном для объектов ORM или внешних классов api, где глубина может распространяться бесконечно, но не рекурсивна.
Итак, насколько я понимаю, основная цель, которую вы преследуете, - это производительность. С функциональной точки зрения все работает нормально, и вы просто хотите сэкономить несколько тиков ЦП.
Сложно судить, хотим ли мы поставлять эту функцию прямо из коробки или нет. Я понимаю, что у вас действительно может быть типовая модель с очень глубокой вложенностью, и там выигрыш в производительности значительный. Но у него есть и несколько недостатков:
Совершенно нормально, что в вашей команде вам нужна эта функция, и вы соглашаетесь ее использовать. Тем не менее, я бы поспорил, чтобы использовать его повсеместно, поскольку это сильно усложняет ситуацию.
Существует множество потенциальных сценариев, и AutoFixture пытается предоставить только наиболее распространенные функции. Более продвинутые биты охватываются возможностями расширяемости. В данном конкретном случае я бы сказал, что эта функция не настолько распространена, чтобы сделать ее частью продукта. Другой момент - реализация очень крошечная (см. Ниже), поэтому реализовать ее в тестовом проекте локально не составит труда.
Я также предлагаю сделать ComposeIfMultiple общедоступным. Это упростило бы реализацию настраиваемого поведения.
Об этом уже говорилось # 657.
Кстати, реализацию можно немного упростить (если я чего-то не упускаю):
`` С #
открытый класс GenerationDepthBehavior: ISpecimenBuilderTransformation
{
public int Depth {получить; }
public GenerationDepthBehavior(int depth)
{
Depth = depth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth);
}
private class IsSeededRequestComparer : IEqualityComparer
{
bool IEqualityComparer.Equals(object x, object y)
{
return x is SeededRequest && y is SeededRequest;
}
int IEqualityComparer.GetHashCode(object obj)
{
return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj);
}
}
}
`` ''
@moodmosaic Не могли бы вы также поделиться своим мнением? 😉
Пример, показанный в https://github.com/AutoFixture/AutoFixture/issues/1032#issuecomment -385724150, является предпочтительным способом использования этого специального поведения. На данный момент я не думаю, что нам следует менять встроенный алгоритм.
Однако мы могли бы подумать о том, чтобы
а затем мы могли бы подумать о дальнейшей оптимизации, подобной этой.
@moodmosaic Похоже, предлагается не изменять поведение по умолчанию, а сделать GenerationDepthBehavior
частью библиотеки AutoFixture (чтобы вы могли включить ее по запросу). Итак, что вы думаете по этому поводу? :подмигивание:
@zvirja На самом деле это не из соображений производительности. Проблема с неопределенным распространением - это неудачный тест с исключением нехватки памяти.
@zvirja , @ malylemire1 , я не думаю, что это специальное поведение должно быть встроено в широкий спектр сценариев.
Я не могу определить MCVE , но, AFAICT, проблемы возникают из-за автоматических тестов, которые на самом деле не являются модульными тестами (например, пограничные / интеграционные тесты).
AutoFixture не в первую очередь предназначен для решения интеграционных тестов, поэтому некоторые Времнное поведение (как показано здесь) гарантировано: +1: Я не думаю , что это должно быть частью основной библиотеки, хотя.
@ malylemire1 Не отложим это на данный момент и
Мне жаль, что мы не можем включить все желаемые функции в ядро 😟 Немного сложно сохранить баланс между предоставленной функцией и объемом кода, который мы здесь поддерживаем. Похоже, что для вас не составит большого труда реализовать эту специальную функцию в своем решении, поэтому вы не будете заблокированы.
Еще раз спасибо за ваше предложение!
Закрытие этой проблемы в рамках очистки. Еще раз спасибо за то, что поделились идеей! 😉
@ malylemire1 Я на 100% понимаю этот вариант использования.
Это что-то, что вы можете сделать доступным в пакете nuget?
Самый полезный комментарий
@ malylemire1 Не отложим это на данный момент и
Мне жаль, что мы не можем включить все желаемые функции в ядро 😟 Немного сложно сохранить баланс между предоставленной функцией и объемом кода, который мы здесь поддерживаем. Похоже, что для вас не составит большого труда реализовать эту специальную функцию в своем решении, поэтому вы не будете заблокированы.
Еще раз спасибо за ваше предложение!