Организация отмены в продолжительном модульном тесте

Я пишу модульные тесты на слой "связующего звена" моего приложения и испытываю затруднения при создании детерминированных тестов для асинхронных методов, которые позволяют пользователю отменять операцию преждевременно.

А именно, в нескольких асинхронных методах у нас есть код, который реагирует на отмену вызова и гарантирует, что объект находится в надлежащем состоянии до завершения. Я хотел бы удостовериться, что эти пути выполнения кода покрыты тестами.

Некоторый псевдо код C#, иллюстрирующий типичный асинхронный метод этим сценарием, следующие:

public void FooAsync(CancellationToken token, Action<FooCompletedEventArgs> callback) 
{
   if (token.IsCancellationRequested) DoSomeCleanup0();

   // Call the four helper methods, checking for cancellations in between each
   Exception encounteredException;
   try
   {
      MyDependency.DoExpensiveStuff1();
      if (token.IsCancellationRequested) DoSomeCleanup1();

      MyDependency.DoExpensiveStuff2();
      if (token.IsCancellationRequested) DoSomeCleanup2();

      MyDependency.DoExpensiveStuff3();
      if (token.IsCancellationRequested) DoSomeCleanup3();

      MyDependency.DoExpensiveStuff4();
      if (token.IsCancellationRequested) DoSomeCleanup4();

   }
   catch (Exception e)
   {
      encounteredException = e;
   }

   if (!token.IsCancellationRequested)
   {
      var args = new FooCompletedEventArgs(a bunch of params);
      callback(args);
   }
}

Решение, которое я предложил до сих пор, включает насмешку базового MyDependency операции, которые перенесены слоем связующего звена, и вынуждающий каждого спать в течение произвольного промежутка времени. Я затем вызываю асинхронный метод и говорю моему модульному тесту спать для многих миллисекунд прежде, чем отменить асинхронный запрос.

Что-то вроде этого (использование Насмешек Носорога как пример):

[TestMethod]
public void FooAsyncTest_CancelAfter2()
{
   // arrange
   var myDependency = MockRepository.GenerateStub<IMyDependency>();

   // Set these stubs up to take a little bit of time each so we can orcestrate the cancels
   myDependency.Stub(x => x.DoExpensiveStuff1()).WhenCalled(x => Thread.Sleep(100));
   myDependency.Stub(x => x.DoExpensiveStuff2()).WhenCalled(x => Thread.Sleep(100));
   myDependency.Stub(x => x.DoExpensiveStuff3()).WhenCalled(x => Thread.Sleep(100));
   myDependency.Stub(x => x.DoExpensiveStuff4()).WhenCalled(x => Thread.Sleep(100));

   // act
   var target = new FooClass(myDependency);

   CancellationTokenSource cts = new CancellationTokenSource();
   bool wasCancelled = false;

   target.FooAsync(
      cts.Token,
      args =>
      {
         wasCancelled = args.IsCancelled;
         // Some other code to manipulate FooCompletedEventArgs
      });

   // sleep long enough for two operations to complete, then cancel
   Thread.Sleep(250);
   cts.Cancel();

   // Some code to ensure the async call completes goes here

   //assert
   Assert.IsTrue(wasCancelled);
   // Other assertions to validate state of target go here
}

Кроме факта тот Поток использования. Сон в модульном тесте делает меня тошнотворным, большая проблема состоит в том, что иногда тесты как этот сбой на нашем сервере сборки, если это, оказывается, является объектом значительной загрузки. Асинхронный вызов становится слишком далеким, и отмена происходит слишком поздно.

Кто-либо может обеспечить более надежный способ логики отмены поблочного тестирования для длительных операций как это? Любые идеи ценились бы.

8
задан Andrew Anderson 8 June 2010 в 13:23
поделиться

2 ответа

Я бы попытался использовать имитацию для "симуляции" асинхронного поведения синхронным способом. Вместо того, чтобы использовать

myDependency.Stub(x => x.DoExpensiveStuff1()).WhenCalled(x => Thread.Sleep(100));

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

myDependency.Stub(x => x.DoExpensiveStuff1());
myDependency.Stub(x => x.DoExpensiveStuff2());
myDependency.Stub(x => x.DoExpensiveStuff3()).WhenCalled(x => cts.Cancel());
myDependency.Stub(x => x.DoExpensiveStuff4());

с точки зрения вашего кода это будет выглядеть так, как если бы отмена произошла во время вызова.

6
ответ дан 5 December 2019 в 21:16
поделиться

Каждая из долго выполняющихся операций должна вызывать событие, когда она начинает выполняться.

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

1
ответ дан 5 December 2019 в 21:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: