У меня есть поблочное тестирование задач класс, который запускает события, когда поток запускается и заканчивается. Сокращение вниз версия незаконного источника следующие:
public class ThreadRunner
{
private bool keepRunning;
public event EventHandler Started;
public event EventHandler Finished;
public void StartThreadTest()
{
this.keepRunning = true;
var thread = new Thread(new ThreadStart(this.LongRunningMethod));
thread.Start();
}
public void FinishThreadTest()
{
this.keepRunning = false;
}
protected void OnStarted()
{
if (this.Started != null)
this.Started(this, new EventArgs());
}
protected void OnFinished()
{
if (this.Finished != null)
this.Finished(this, new EventArgs());
}
private void LongRunningMethod()
{
this.OnStarted();
while (this.keepRunning)
Thread.Sleep(100);
this.OnFinished();
}
}
У меня затем есть тест, чтобы проверить что Finished
огни события после LongRunningMethod
закончился следующим образом:
[TestClass]
public class ThreadRunnerTests
{
[TestMethod]
public void CheckFinishedEventFiresTest()
{
var threadTest = new ThreadRunner();
bool finished = false;
object locker = new object();
threadTest.Finished += delegate(object sender, EventArgs e)
{
lock (locker)
{
finished = true;
Monitor.Pulse(locker);
}
};
threadTest.StartThreadTest();
threadTest.FinishThreadTest();
lock (locker)
{
Monitor.Wait(locker, 1000);
Assert.IsTrue(finished);
}
}
}
Так идея, здесь являющаяся, который тест заблокирует максимум для одной секунды - или до Finish
событие запущено - прежде, чем проверить ли finished
флаг установлен.
Очевидно я делал что-то не так как иногда, тест передаст, иногда он не будет. Отладка кажется очень трудной, а также точки останова, я ожидал бы быть пораженным ( OnFinished
метод, например), не всегда кажется.
Я предполагаю, что это - просто мое неверное толкование способа, которым работает поточная обработка, так надо надеяться, кто-то может просветить меня.
Блокировка здесь просто неуместна, вы захотите сигнализировать о событии. Например:
public void CheckFinishedEventFiresTest() {
var threadTest = new ThreadRunner();
var finished = new ManualResetEvent(false);
threadTest.Finished += delegate(object sender, EventArgs e) {
finished.Set();
};
threadTest.StartThreadTest();
threadTest.FinishThreadTest();
Assert.IsTrue(finished.WaitOne(1000));
}
Кажется, ваш тест неверен. Предположим, что после threadTest.FinishThreadTest ();
блокировка получена кодом в CheckFinishedEventFiresTest ()
. Тогда тест провалится. У вас есть четкое условие гонки.
Обратите внимание, что возврат от FinishThreadTest ()
не гарантирует , что поток завершен. Он просто устанавливает флаг для потока, который может быть принят во внимание в любой момент (ничто в принципе не гарантирует, что поток запускается планировщиком немедленно).
В вашем случае поток, скорее всего, будет занят Sleep ()
ing. После вызова threadTest.FinishThreadTest ();
блокировка, скорее всего, будет получена потоком, в котором выполняется CheckFinishedEventFiresTest ()
. Монитор подождет 1 секунду, а затем откажется. После этого блокировка будет снята, поэтому делегат сможет заблокировать только в этот момент.
Влад абсолютно прав, но я попробую еще раз прояснить проблему:
// This runs on the other thread
threadTest.Finished += delegate(object sender, EventArgs e) {
// I can't get this lock if the test thread gets here first!
lock (locker) {
finished = true;
Monitor.Pulse(locker);
}
};
Это можно сделать с помощью какого-нибудь хэндла ожидания. Я бы использовал ManualResetEvent
:
ManualResetEvent waitHandle = new ManualResetEvent(false);
threadTest.Finished += delegate(object sender, EventArgs e) {
finished = true;
waitHandle.Set();
};
threadTest.StartThreadTest();
threadTest.FinishThreadTest();
// Specify a timeout so your test isn't hostage forever
if (waitHandle.WaitOne(timeout, true)) {
Assert.IsTrue(finished);
}
Недавно я написал серию сообщений в блоге о последовательностях событий модульного тестирования для объектов, которые публикуют как синхронные, так и асинхронные события. В сообщениях описывается подход и структура модульного тестирования, а также предоставляется полный исходный код с тестами.
Использование платформы тесты могут быть записаны следующим образом:
AsyncEventPublisher publisher = new AsyncEventPublisher();
Action test = () =>
{
publisher.RaiseA();
publisher.RaiseB();
publisher.RaiseC();
};
var expectedSequence = new[] { "EventA", "EventB", "EventC" };
EventMonitor.Assert(test, publisher, expectedSequence, TimeoutMS);
EventMonitor выполняет всю тяжелую работу и запускает тест (действие) и утверждает, что события возникают в ожидаемой последовательности (ожидаемая последовательность). Он обрабатывает асинхронные события и распечатывает приятные диагностические сообщения при сбое теста.
В сообщениях много подробностей, описывающих проблемы и подходы, а также исходный код:
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part -1 /