Autofixture: Предложение для GenerationDepthBehavior.

Созданный на 1 мая 2018  ·  12Комментарии  ·  Источник: AutoFixture/AutoFixture

Вот предложение по поведению ограничителя глубины.

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> requestsByThread
= новый 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 (
запрос объекта,
IEnumerableRegisterRequests, int depth)
{
вернуть новый OmitSpecimen ();
}
}
`` ''

question

Самый полезный комментарий

@ malylemire1 Не отложим это на данный момент и

Мне жаль, что мы не можем включить все желаемые функции в ядро ​​😟 Немного сложно сохранить баланс между предоставленной функцией и объемом кода, который мы здесь поддерживаем. Похоже, что для вас не составит большого труда реализовать эту специальную функцию в своем решении, поэтому вы не будете заблокированы.

Еще раз спасибо за ваше предложение!

Все 12 Комментарий

Здравствуйте, спасибо, что поделились идеей! : +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, является предпочтительным способом использования этого специального поведения. На данный момент я не думаю, что нам следует менять встроенный алгоритм.


Однако мы могли бы подумать о том, чтобы

  • используйте разделяемый PRNG (текущий не разделяемый)
  • добавить тесты на случайность (например, следуйте https://github.com/hedgehogqa/haskell-hedgehog/issues/125)
  • ориентиры

а затем мы могли бы подумать о дальнейшей оптимизации, подобной этой.

@moodmosaic Похоже, предлагается не изменять поведение по умолчанию, а сделать GenerationDepthBehavior частью библиотеки AutoFixture (чтобы вы могли включить ее по запросу). Итак, что вы думаете по этому поводу? :подмигивание:

@zvirja На самом деле это не из соображений производительности. Проблема с неопределенным распространением - это неудачный тест с исключением нехватки памяти.

@zvirja , @ malylemire1 , я не думаю, что это специальное поведение должно быть встроено в широкий спектр сценариев.


Я не могу определить MCVE , но, AFAICT, проблемы возникают из-за автоматических тестов, которые на самом деле не являются модульными тестами (например, пограничные / интеграционные тесты).

AutoFixture не в первую очередь предназначен для решения интеграционных тестов, поэтому некоторые Времнное поведение (как показано здесь) гарантировано: +1: Я не думаю , что это должно быть частью основной библиотеки, хотя.

@ malylemire1 Не отложим это на данный момент и

Мне жаль, что мы не можем включить все желаемые функции в ядро ​​😟 Немного сложно сохранить баланс между предоставленной функцией и объемом кода, который мы здесь поддерживаем. Похоже, что для вас не составит большого труда реализовать эту специальную функцию в своем решении, поэтому вы не будете заблокированы.

Еще раз спасибо за ваше предложение!

Закрытие этой проблемы в рамках очистки. Еще раз спасибо за то, что поделились идеей! 😉

@ malylemire1 Я на 100% понимаю этот вариант использования.
Это что-то, что вы можете сделать доступным в пакете nuget?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

zvirja picture zvirja  ·  3Комментарии

Accc99 picture Accc99  ·  4Комментарии

tiesmaster picture tiesmaster  ·  7Комментарии

gtbuchanan picture gtbuchanan  ·  3Комментарии

ecampidoglio picture ecampidoglio  ·  7Комментарии