2013-06-13 10 views
5

Próbuję pisać testy jednostkowe wokół niestandardowej implementacji SynchronizationContext.Jak mogę przetestować, czy implementacje są synchroniczne lub asynchroniczne, gdy jest to wymagane?

Dwie ważne operacje na tej klasy są Send i Post, gdzie Send wywołuje delegata synchronicznie i Post wywołuje delegata asynchronicznie.

Chciałbym napisać testy jednostkowe, aby zweryfikować to zachowanie, że delegaci zostali wykonani synchronicznie lub asynchronicznie. Nie chcę, aby testy polegały na opóźnieniach w przypadku powodzenia, ponieważ sztucznie przedłuża on testy (ale rozsądne jest, aby awarie powodowały opóźnienie) w wykonaniu.

Początkowo rozważaliśmy użyciu Zadania sygnalizować wykonanie delegat:

var tcs = new TaskCompletionSource<object>(); 

var context = new CustomSynchronizationContext(); 

context.Send((state) => tcs.SetResult(null), null); 

// Task should already be completed! 
Assert.IsTrue(this.tcs.Task.IsCompleted); 

Jednak to nie zapewnia pełnomocnik został nie wykonywane asynchronicznie bardzo szybko, zanim zawodnik testowy mógł kontynuować.

Jak mogę zorganizować test całym kontekście, aby Send bloki zakończeniu delegata i Post nie robi, ale że delegaci zarówno powoływać?

+1

+1 na pytanie, a nazwa "tragediopisarz" osoby, która rozwiąże problemy ... :) –

+0

Isn czy możliwe jest odświeżenie wątku 'Thread.ManagedThreadId', na którym delegat został wykonany? Jeśli był taki sam jak główny wątek, został wykonany synchronicznie, w przeciwnym razie asynchronicznie. – Davio

+0

Kontekst synchronizacji nie uruchomi żadnych delegatów w wątku wywołującym, więc obawiam się, że nie. Wątki to tylko szczegół implementacji. –

Odpowiedz

-1

Zawierające mój pomysł:

var mainThreadId = Thread.ManagedThreadId; 
var sendThreadId; 
context.Send((state) => sendThreadId = Thread.ManagedThreadId); 
Assert.AreEqual(mainThreadId, sendThreadId); 

nie wiem, czy to faktycznie działa, trzeba sprawdzić.

+0

Kontekst synchronizacji nie uruchomi żadnych delegatów w wątku wywołującym, więc obawiam się, że nie. Wątki to tylko szczegół implementacji w programowaniu asynchronicznym. –

+0

Tak, "synchroniczny" i "asynchroniczny" nie jest taki sam jak "w tym samym wątku" i "w innym wątku". Metoda synchroniczna może wykonać delegata w innym wątku i synchronicznie czekać na zakończenie. – svick

3

Uważam, że można to osiągnąć za pomocą pary ManualResetEvents. Używając poniższego kodu, spowolnienie występuje tylko wtedy, gdy testy się nie powiodły (liczby są dość wysokie i prawdopodobnie można je bezpiecznie zmniejszyć). Pomysł polega na tym, że potwierdzamy kolejność, w której muszą zajść rzeczy, które mogą się zdarzyć tylko wtedy, gdy zablokujemy lub nie zablokujemy.

Dla synchronicznego testu:

var incall = new ManualResetEvent(false); 
var unblock = new ManualResetEvent(false); 
var context = new CustomSynchronizationContext(); 
var t = Task.Run(() => context.Send(state => 
{ 
    incall.Set(); 
    unblock.WaitOne(5000); 
}, null)); 
Assert.IsTrue(incall.WaitOne(1000)); 
Assert.IsFalse(t.Wait(10)); 
unblock.Set(); 
Assert.IsTrue(t.Wait(1000)); 

do testu Async:

var incall = new ManualResetEvent(false); 
var unblock = new ManualResetEvent(false); 
var context = new CustomSynchronizationContext(); 
var t = Task.Run(() =>context.Post(state => 
{ 
    incall.Set(); 
    unblock.WaitOne(5000); 
}, null)); 
Assert.IsTrue(incall.WaitOne(1000)); 
Assert.IsTrue(t.Wait(1000)); //This will timeout if unblock is blocking completion of the task 
unblock.Set(); 
+0

Ale metoda asynchorniczna może zdać test synchroniczny, jeśli czas był odpowiedni. – svick

+0

Czy odnosisz się do faktu, że odblokowanie może przekroczyć limit czasu, a zadanie zakończy się przed ostatnim sprawdzeniem lub innym problemem? – jageall

+0

Nie, że podczas twojego drugiego 'Assert()', metoda asynchroniczna mogła już rozpocząć wykonywanie (więc 'incall.WaitOne()' kończy się natychmiast), ale także jeszcze nie powróciło, więc 'Status' nadal będzie'. Uruchamianie ". – svick

Powiązane problemy