Testowanie kodu wielowątkowego jest nie lada wyzwaniem. Teoretycznie powinno się tego unikać, ale czasami nie ma innego wyjścia. Co robić w sytuacji, gdy w komponencie tworzonym podczas naszego testu uruchamiany jest nowy wątek, a w nim wyskakuje wyjątek? Test oczywiście nie przechodzi, ale wcale niekoniecznie musimy wiedzieć dlaczego tak a nie inaczej się stało. Kiedyś pół godziny straciłem na wgapianie się w monitor zanim wpadłem na to, że w wątku pobocznym wyskakuje wyjątek. Bo jak wiadomo, wyjątek rzucony w nowym wątku nie jest przekazywany nigdzie z powrotem - po prostu gubimy o nim wszelkie informacje.
Dość rozsądnym wyjściem wydało mi się po prostu zalogowanie informacji o tym fakcie i pokazanie jej w wynikach testu. Test nie przejdzie i sam framework do testowania nie będzie dokładnie wiedział co się stało, ale z wyświetloną informacją przed oczami łatwiej będzie nam naprawić błąd.
Testy jednostkowe nie powinny jednak zakładać, że będą wykonane w środowisku z poprawnie skonfigurowanym loggerem. Więcej - podczas testów "prawdziwe" logowanie w ogóle nie powinno się odbywać, w końcu wrzucanie do event logu czy bazy danych (zależność nieakceptowalna jeżeli chodzi o testy jednostkowe!) tony informacji wypluwanych przez system podczas testu jest całkowicie zbędne.
Dlatego też stworzyłem loggera specjalnie na tą okazję. Założenia:
- nie wymagamy żadnego pliku konfiguracyjnego
- logujemy tylko błędy
- bez ingerencji w kod systemu dostajemy się do logowanych przezeń informacji
- logowanie konfigurujemy w jednym miejscu, bez konieczności pamiętania o tym w każdym teście
Z wykorzystaniem frameworka log4net rozwiązanie tego problemu okazało się... banalne. Oto kod logujący to co nas interesuje do strumienia Debug:
1: public class TestAppender : AppenderSkeleton
2: {
3: private static readonly PatternLayout _layout = new PatternLayout("%level %logger %ndc - %message%newline");
4:
5: public TestAppender()
6: {
7: this.Layout = _layout;
8: }
9:
10: protected override void Append(LoggingEvent loggingEvent)
11: {
12: if (loggingEvent.Level < Level.Error)
13: return;
14:
15: Debug.WriteLine(base.RenderLoggingEvent(loggingEvent));
16: }
17: }
A oto metoda konfigurująca całą infrastrukturę log4net, zawarta w klasie bazowej dla wszystkich testów jednostkowych:
1: [SetUp]
2: public void SetupLogging()
3: {
4: BasicConfigurator.Configure(new TestAppender());
5: }
Testowanie wielowątkowości nadal nie jest przyjemne, ale przynajmniej teraz wiem gdzie szukać przyczyny, jeśli coś poszło nie tak.