Как оптимизировать тесты, проверяющие асинхронный код? - PullRequest
2 голосов
/ 25 января 2012

Мы разрабатываем приложение WPF с использованием TDD. Поскольку мы уже работаем над этим решением в течение почти двух лет, мы написали огромное количество тестов (сейчас почти 2000 юнит-тестов).

Есть несколько классов, которые должны реализовывать многопоточные и асинхронные функции. Например, коммуникационный компонент, который может отправлять и получать сообщения и анализировать их. Зависимости всегда проверяются с помощью RhinoMocks.

Наши тестовые методы, предназначенные для этих классов, выглядят очень похоже, как показано ниже:

[TestMethod]
public void Method_Description_ExpectedResult(){
  // Arrange
  var myStub = MockRepository.GenerateStub<IMyStub>();
  var target = new MyAsynchronousClass(myStub);

  // Act
  var target.Send("Foo");
  Thread.Sleep(200);

  //Assert
  myStub.AssertWasCalled(x => x.Bar("Foo"));
}

Как видите, этот тест выполняется не менее 200 мс из-за Thread.Sleep (). Мы оптимизировали тест, заменив AssertWasCalled активным методом опроса, s.th. как это:

public static bool True(Func<bool> condition, int times, int waitTime)
{
    for (var i = 0; i < times; i++)
    {
        if (condition())
            return true;
        Thread.Sleep(waitTime);
    }

    return condition();
}

Теперь мы можем использовать этот метод WaitFor.True (...), изменив AssertWasCalled на:

var fooTriggered = false;
myStub.Stub(x => x.Bar("Foo")).Do((Action)(() => fooTriggered = true)));

WaitFor.True(() => fooTriggered, 20, 20);
Assert.IsTrue(fooTriggered);

Эта конструкция завершится раньше, если условие будет соответствовать, но в любом случае - это займет слишком много времени для нас. На выполнение всех наших 2000 тестов требуется около 5 минут (сборка и запуск).

Есть ли хитрый трюк, как мы могли бы оптимизировать код, подобный этому?

1 Ответ

2 голосов
/ 25 января 2012

Вы можете использовать монитор. Я придумываю это, поэтому, пожалуйста, извините, если это не совсем компилируется, но это будет выглядеть примерно так:

[TestMethod]
public void Method_Description_ExpectedResult(){
  // Arrange
  var waitingRoom = new object();
  var myStub = MockRepository.GenerateStub<IMyStub>();
  myStub.Setup(x => x.Bar("Foo")).Callback(x => 
      {
          Monitor.Enter(waitingRoom);
          Monitor.Pulse(waitingRoom);
          Monitor.Exit(waitingRoom);
      }

  var target = new MyAsynchronousClass(myStub);

  // Act

  Monitor.Enter(waitingRoom);
  target.Send("Foo");
  Monitor.Wait(waitingRoom);
  Monitor.Exit(waitingRoom);

  //Assert
  myStub.AssertWasCalled(x => x.Bar("Foo"));
}

Код, написанный на мониторе, не может быть запущен, пока он не станет бесплатным. Тест заставит действующий поток ждать, пока не будет вызван Monitor.Wait. Затем обратный вызов может войти и пульсировать монитор. Затем тест «просыпается», и после того, как обратный вызов вышел из монитора, он снова получает управление и выходит из него, что позволяет вам подтвердить.

Единственное, что я не рассмотрел, так это то, что, если Bar ("Foo") не вызывается, он зависнет, поэтому вы можете захотеть, чтобы таймер пульсировал поток.

Вы можете создать класс, который будет выполнять сложные биты мониторинга за вас, если вы часто его используете. Это тот, который я написал для работы с асинхронными проверками в автоматизации пользовательского интерфейса; адаптация его к тому, что вы делаете, может вам помочь.

...