W pracy mamy aplikacje, która jest pisana w Silverligtcie. Ostatnio trafiła do mnie potrzeba napisania unit testa do pokrycia command’a z ViewModela. Niestety, sam command wykorzystuje pewne dane słownikowe, które wcześniej aplikacja sobie zaczytuje asynchronicznie z WCF’a.
W projektach, w których dotychczas uczestniczyłem asynchroniczność była wykorzystywana na niskim poziomie i w zasadzie, nigdy nie miałem przyjemności i okazji do napisania testu pod coś co łączy się w takim trybie. Dodatkowo dane słownikowe winny się były załadować na początku unit testu w metodzie inicjalizującej, ponieważ chciałem je móc wykorzystać w kilku testach.
Okazuje się, że można coś takiego zrobić o czym dość fajnie napisał norweski programista Jonas Follesø.
Testowanie to, generalnie opiera się o metody “EnqueueXXX“, do których mamy dostęp jeśli nasza klasa podziedziczy sobie po SilverlighTest, a które sobie rejestrują co chcemy wykonać, a potem to wykonują.
Trochę to tak dziwnie wygląda, bo gdy mamy taką metodę:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
[TestClass] public class Tests : SilverlightTest { private Foo foo; // Atrybut "Asynchronous" jest potrzebny jeżeli mamy wykonania async. [ClassInitialize, Asynchronous] public void Setup() { foo = new Foo(); // flaga, która się zmieni, gdy nasza async metoda sie wykona. bool isDone = false; foo.LoadCompleted += (sender, args) => { isDone = true; }; // wykonujemy nasza metode EnqueueCallback(() => foo.DoMagicStuff(false)); // tutaj mowimy - ty sobie siedź i czekaj, aż isDone będzie true. EnqueueConditional(() => isDone); // jak się wykona to co wyżej, to wtedy sprawdź poniższą asercję. EnqueueCallback(() => Assert.IsTrue(foo.hokusPokus)); // To jest takie trochę "śliskie". // Teoretycznie, TestComplete powinno być wołane na końcu w stylu // "o patrz tutaj skończyłem". // Jeśli nie dodamy tej linijki, to wszystkie metody się nie wykonają // i taki test będzie sobie wisiał, wisiał i wisiał :< EnqueueTestComplete(); } [TestMethod] public void TestHokusPokus() { Assert.IsTrue(foo.hokusPokus); } } |
… to gdy się przez nią przedebugujemy, to zauważymy, że najpierw wykona sobie części kodu na które debugger napotka, a następnie zacznie wywoływać sobie metody z części Enqueue.
I wszystko niby fajnie, ale jest kilka pewnych ” ale “… :/
- czytelność tych testów zaczyna być zdrowo zaburzona. Jeżeli nie napiszemy sobie komentarzy, to za jakiś czas może się okazać problemem zrozumienie na szybko “ale co ja właściwie chciałem tu zrobić” -.-‘
- wsparcie frameworka do testowania jest delikatnie mówiąc – mizerne, w przypadku gdy coś nie zadziała, to w zasadzie nie dostarczy nam żadnej konkretnej informacji, co poszło nie tak. Np. gdy zapomnimy sobie dodać dyskusyjne “EnqueueTestComplete()” nasza aplikacja sobie zastygnie, a my będziemy się głowić… co tu Panie nie działa?
- EnqueueConditional mogłoby mieć opcję timeout’u. Na chwilę obecną niestety nie możemy powiedzieć naszemu testowi “ty tu sobie siedź i czekaj na isDone, ale jak nic się nie stanie przez 20 sek, to idź z koksem dalej“.
Przez co, gdy np, ktoś gdzieś sprawdzi paremetry wejściowe w taki sposób:
12345678public void DoMagicStuff(bool isRabbitAlive){if (isRabbitAlive == false) return;hokusPokus = true;if (LoadCompleted != null)LoadCompleted(this, new EventArgs());}
…, a nasz kod przechodzi przez właśnie taką metodę, to aplikacja sobie zawiśnie na metodzie EnqueueConditional, a naszym oczom ukaże się taki oto niewiele mówiący komunikat:
Michal
July 2, 2012ahhh… dumny jestem z Czesia piszacego Unit Testy 🙂 Jak za pare tygodni napiszesz posta w ktorym opisujesz jak zastosowales Test-Driven Development to masz u mnie piwo!