WedX - журнал о программировании и компьютерных науках

Асинхронный метод модульного тестирования: как явно подтвердить, что внутренняя задача была отменена

Недавно я писал метод async, который вызывает внешний долго работающий метод async, поэтому я решил передать CancellationToken, разрешающий отмену. Метод может быть вызван одновременно.

Реализация сочетает методы экспоненциальной отсрочки и тайм-аута, описанные в Stephen Cleary в книге Concurrency in C# Cookbook следующим образом;

/// <summary>
/// Sets bar
/// </summary>
/// <param name="cancellationToken">The cancellation token that cancels the operation</param>
/// <returns>A <see cref="Task"/> representing the task of setting bar value</returns>
/// <exception cref="OperationCanceledException">Is thrown when the task is cancelled via <paramref name="cancellationToken"/></exception>
/// <exception cref="TimeoutException">Is thrown when unable to get bar value due to time out</exception>
public async Task FooAsync(CancellationToken cancellationToken)
{
    TimeSpan delay = TimeSpan.FromMilliseconds(250);
    for (int i = 0; i < RetryLimit; i++)
    {
        if (i != 0)
        {
            await Task.Delay(delay, cancellationToken);
            delay += delay; // Exponential backoff
        }

        await semaphoreSlim.WaitAsync(cancellationToken); // Critical section is introduced for long running operation to prevent race condition

        using (CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
        {
            cancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout));
            CancellationToken linkedCancellationToken = cancellationTokenSource.Token;

            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                bar = await barService.GetBarAsync(barId, linkedCancellationToken).ConfigureAwait(false);

                break;
            }
            catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
            {
                if (i == RetryLimit - 1)
                {
                    throw new TimeoutException("Unable to get bar, operation timed out!");
                }

                // Otherwise, exception is ignored. Will give it another try
            }
            finally
            {
                semaphoreSlim.Release();
            }
        }
    }
}

Интересно, должен ли я написать модульный тест, который явно утверждает, что внутренняя задача barService.GetBarAsync() отменяется всякий раз, когда отменяется FooAsync(). Если да, то как реализовать это чисто?

Кроме того, должен ли я игнорировать детали реализации и просто проверить, что касается клиента/вызывающего, как описано в сводке метода (панель обновлена, триггеры отмены OperationCanceledException, триггеры тайм-аута TimeoutException).

Если нет, должен ли я намочить ноги и начать реализовывать модульные тесты для следующих случаев:

  1. Тестирование потокобезопасности (монитор запускается только одним потоком за раз)
  2. Тестирование механизма повтора
  3. Проверка сервера не залита
  4. Тестирование, возможно, даже обычное исключение распространяется на вызывающую сторону

  • Это больше касается философии тестирования, поэтому однозначного ответа, вероятно, нет, но я склоняюсь к тестированию только общедоступного интерфейса. BarService — это собственный тип, поэтому вы должны тестировать его собственными тестами, а не этим. Тестирование комбинации, как вы предлагаете, - это скорее проверка механизма отмены CLR, чем вашего кода, и мы должны быть в состоянии предположить, что это нормально. Если, как я думаю, вы подразумеваете, BarService является внутренним, вы можете использовать InternalsVisibleTo (только для тестовой сборки), чтобы разрешить его тестирование (но некоторые наверняка не согласятся). 17.10.2016
  • @sellotape Я тестирую FooAsync() на собственном приспособлении (Foo) насмешливо BarService.GetBarAsync(). Тем не менее, он реагирует на поведение своих зависимостей, и это то, что я думаю о тестировании. Итак, в основном; если вы подразумеваете, что я должен тестировать только общее поведение, а не детали реализации, как вы думаете, мне следует протестировать потокобезопасность, учитывая, что я утверждаю, что метод является потокобезопасным. 17.10.2016
  • Если вы издеваетесь над ним, то на самом деле нечего тестировать конкретно, и вы можете протестировать его изолированно. Что касается безопасности потоков, да, вы, вероятно, должны протестировать этот аспект, если он является частью публичного контракта. Я оставлю вам более мелкие детали, но первоначальная мысль состоит в том, чтобы утверждать, что GetBarAsync() вызывается только один раз до определенного момента времени, когда FooAsync() вызывается из двух потоков примерно в одно и то же время. Вы можете настроить свой метод GetBarAsync() (издевательство), чтобы немного задержаться, чтобы избежать слишком большой гонки. 17.10.2016

Ответы:


1

Спасибо Стивену Клири за согласие на повторную попытку Polly. Возможно, будущим читателям будет интересно, что все функциональные возможности исходного примера кода плаката теперь могут быть построены из готовых примитивов Polly, которые уже прошли модульное тестирование:

  • Политика тайм-аута для тайм-аута с помощью токена отмены тайм-аута (включая объединение с пользователем - предоставленный токен отмены)
  • Политика Bulkhead для регулирования параллелизма/взаимного исключения
  • WaitAndRetry для повторной попытки, включая отмену во время ожидания
  • PolicyWrap для объединения.

Все политики Polly полностью протестированы, синхронизированы и асинхронно-совместимый, потокобезопасный для параллельных исполнений и имеет поддержку сквозной отмены.

Таким образом, цель исходного кода может быть достигнута примерно так:

Policy retry = Policy.Handle<WhateverExceptions>().WaitAndRetryAsync(RetryLimit, retryAttempt => TimeSpan.FromMilliseconds(250 * Math.Pow(2, retryAttempt)));
Policy mutex = Policy.BulkheadAsync(1);
Policy timeout = Policy.TimeoutAsync(/* define overall timeout */);

bar = await timeout.WrapAsync(retry).WrapAsync(mutex).ExecuteAsync(ct => barService.GetBarAsync(barId, ct), cancellationToken);

Я добавлю несколько комментариев о модульном тестировании (исходный вопрос ОП) к комментариям к ответу Стивена (гораздо более актуальному) на это.

01.04.2017
  • Спасибо вам за информацию, которой вы поделились. Также большой палец вверх для Полли, которая делает жизнь в параллельном контексте намного проще (: 01.04.2017

  • 2
  • Спасибо за ответ Стивен. Мне нравится этот подход, так как я стараюсь избегать нестабильных/ненадежных Delay решений. Итак, снова с примерами для тестирования, в этом конкретном примере, как далеко вы пойдете? 18.10.2016
  • Это зависит от нескольких факторов. Я бы сделал (1) в любом случае и подумал бы о том, чтобы сделать (2), если бы это был только я (у команд обычно нет подделок). Я еще не дошел до (3). 18.10.2016
  • Поли тестирует свою политику мьютекса (часть 3 в нумерации Стивена) по совпадению также демонстрируют некоторые методы тестирования, о которых упоминает Стивен / о которых спрашивал Саро. TaskCompletionSource‹object› используется как прокси для имитации завершенной/отмененной асинхронной работы. ... 01.04.2017
  • .. и AutoResetEvent используется для сигнал от тестируемой системы к набор тестов, чтобы избежать ненадежного введения задержки в тесты, о которой упоминал @SaroTaşciyan. 01.04.2017
  • Новые материалы

    Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что это выглядит сложно…
    Просто начните и учитесь самостоятельно Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что он кажется мне сложным, и я бросил его. Это в основном инструмент..

    Лицензии с открытым исходным кодом: руководство для разработчиков и создателей
    В динамичном мире разработки программного обеспечения открытый исходный код стал мощной парадигмой, способствующей сотрудничеству, инновациям и прогрессу, движимому сообществом. В основе..

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..


    Для любых предложений по сайту: [email protected]