2010-03-10 2 views
11

Основной вопрос заключается в том, как создать единичный тест, которому необходимо вызвать метод, дождаться события, которое произойдет в тестируемом классе, а затем вызвать другой метод (тот, который мы действительно хотим проверить)?Как TDD асинхронные события?

Вот сценарий, если у вас есть время, чтобы прочитать дальше:

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

Представьте, что класс, о котором идет речь, является Лифт, и я хочу проверить метод, который дает мне номер пола, который находится на лифте. Вот как мой фиктивный тест выглядит как прямо сейчас:

[TestMethod] 
public void TestGetCurrentFloor() 
{ 
    var elevator = new Elevator(Elevator.Environment.Offline); 
    elevator.ElevatorArrivedOnFloor += TestElevatorArrived; 

    elevator.GoToFloor(5); 

    //Here's where I'm getting lost... I could block 
    //until TestElevatorArrived gives me a signal, but 
    //I'm not sure it's the best way 

    int floor = elevator.GetCurrentFloor(); 

    Assert.AreEqual(floor, 5); 
} 

Edit:

Спасибо за все ответы. Вот как я это сделал:

[TestMethod] 
    public void TestGetCurrentFloor() 
    { 
     var elevator = new Elevator(Elevator.Environment.Offline); 
     elevator.ElevatorArrivedOnFloor += (s, e) => { Monitor.Pulse(this); }; 

     lock (this) 
     { 
      elevator.GoToFloor(5); 

      if (!Monitor.Wait(this, Timeout)) 
       Assert.Fail("Elevator did not reach destination in time"); 

      int floor = elevator.GetCurrentFloor(); 

      Assert.AreEqual(floor, 5); 
     } 
    } 
+0

Будут ли блокировки вызовов на целевой ОС? –

+0

№ Как только вы вызываете .GoToFloor (пол), он возвращается, но лифт меняет статус на «Перемещение», занимает некоторое время, чтобы добраться до этого этажа, а затем он поднимет событие (которое может быть в другом потоке) –

+0

Привет, это должно решить проблему, которую я испытываю асинхронную операцию. Я использовал ваш код выше в качестве отправной точки, однако Monitor.Pulse не вызывает Wait для повторной фиксации блокировки, и это время, из-за которого загорается Assert.Fail («Событие не пришло»). Любые идеи, почему это может быть ...!? – Kildareflare

ответ

5

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

Для этого вы можете использовать Monitor.Wait с тайм-аутом в своем тесте и сигнализировать его с помощью Monitor.Pulse при поступлении события.

 

[TestMethod] 
public void TestGetCurrentFloor() 
{ 
    var elevator = new Elevator(Elevator.Environment.Offline); 
    elevator.ElevatorArrivedOnFloor += TestElevatorArrived; 

    lock (this) 
    { 
     elevator.GoToFloor(5); // NOTE: this must hand off to a second thread, and the ElevatorArrivedOnFloor must be raised by this other thread otherwise the Monitor will be pulse before we've started waiting for it 

     if (!Monitor.Wait(this, TIMEOUT)) Assert.Fail("Event did not arrive in time."); 
    } 

    int floor = elevator.GetCurrentFloor(); 

    Assert.AreEqual(floor, 5); 
} 

private void TestElevatorArrived(int floor) 
{ 
    lock (this) 
    { 
     Monitor.Pulse(this); 
    } 
} 
 

(Assert.Fail() вызов здесь должен быть заменен на любой механизм ваш инструмент модульного тестирования используется для явного проваливать испытание - или вы могли бы бросить исключение.)

+0

Вот как я это делаю, кроме того, что я делаю это с WaitHandle и лямбдой вместо метода, назначенного событию, но идея точно такая же. –

+0

Привет, это должно решить проблему, которую я испытываю асинхронную операцию. Я использовал ваш код выше в качестве отправной точки, однако Monitor.Pulse не вызывает Wait для повторной фиксации блокировки, и это время, из-за которого загорается Assert.Fail («Событие не пришло»). Любые идеи, почему это может быть ...!? – Kildareflare

+0

@ Kildareflare: импульс заставляет Monitor.Wait() прекращать ожидание. Обратите внимание, что когда ожидается остановка Monitor.Wait() (из-за импульса или из-за того, что он был тайм-аут), он должен восстановить блокировку, поэтому может быть, что Monitor.Wait() уже был установлен, но вы видите сообщение позже когда он сможет вернуть замок. Я предполагаю, что вы установили слишком малый тайм-аут: он находится в миллисекундах, вместо этого вы указываете секунды? –

2

Может быть, это просто плохой пример, но ваш лифт звучит скорее как машина состояний, чем то, что просто обрабатывается асинхронно.

Итак, ваш первый набор тестов может проверить, что GoToFloor() установит состояние в движение и правильное направление движения.

Тогда следующий набор тестов будет на TestElevatorArrived() и будет проверять, что если ваше состояние двигалось к определенному полу, то фактическое движение (т. Е. Функция, вызываемая после асинхронного ожидания, или обработчик для аппаратного запуска «перемещенного» события) будет установлено состояние на ожидаемый этаж.

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

+1

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

+1

Ах, для этого конкретного случая, я бы сказал, нет. Не хотел, чтобы одеяло сказал «нет», потому что возможно (возможно?) Несколько хороших случаев для тестирования таймерами/таймаутами и еще много чего. Но здесь то, что вы «ждете», будет вашим аппаратным обеспечением, чтобы выполнить какую-то работу, а это не то, что вы пытаетесь выполнить модульным тестом. – Tanzelax

2

Это мой аналогичный подход.

[TestMethod] 
    public void TestGetCurrentFloor() 
    { 
     var completedSync = new ManualResetEvent(false); 
     var elevator = new Elevator(Elevator.Environment.Offline); 

     elevator.ElevatorArrivedOnFloor += delegate(object sender, EventArgs e) 
     { 
      completedSync.Set(); 
     }; 

     elevator.GoToFloor(5); 

     completedSync.WaitOne(SOME_TIMEOUT_VALUE); 

     int floor = elevator.GetCurrentFloor(); 

     Assert.AreEqual(floor, 5); 
    } 

Вы также можете проверить возвращаемое значение WaitOne() для проверки, что ваш обработчик событий был вызван.

0

Мне действительно не нравится состояние гонки с помощью метода Monitor.Pulse/Wait выше.

А, не столь хорошо, но эффективный способ будет что-то вроде следующего:

[TestMethod] 
public void TestGetCurrentFloor() 
{ 
    // NUnit has something very similar to this, I'm going from memory 
    this.TestCounter.reset(); 

    var elevator = new Elevator(Elevator.Environment.Offline); 
    elevator.ElevatorArrivedOnFloor += (s,e) => { Assert.That(e.floor).Is(5) } 

    elevator.GoToFloor(5); 

    // It should complete within 5 seconds.. 
    Thread.Sleep(1000 * 5); 
    Assert.That(elevator.GetCurrentFloor()).Is(5); 

    Assert.That(this.TestCounter.Count).Is(2); 
} 

Мне не нравится это решение, потому что если лифт приходит в течение 500мс, вы остаетесь в ожидании еще 4500ms. Если у вас было много таких тестов, и вы хотели, чтобы ваши тесты были быстрыми, я бы полностью избегал этого сценария. Однако этот тест также удваивается как проверка работоспособности/проверки работоспособности.

Хотите, чтобы лифт поднялся в течение 2 секунд? Изменение таймаута.

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