Testy jednostkowe w Visual Studio  

Udostępnij na: Facebook

Autor: Piotr Zieliński

Opublikowano: 2011-06-21

Wprowadzenie

W czasach skomplikowanych i wielowarstwowych systemów informatycznych prawidłowe testowanie oprogramowania jest ogromnym wyzwaniem. Testy manualne wykonywane przez użytkowników są dobrym sposobem na walidację aplikacji, jednak dla rozbudowanych programów są zbyt czasochłonne. Dlatego dobrym uzupełnieniem są testy zautomatyzowane – są to programy do automatycznego (jak sugeruje nazwa) testowania aplikacji.

Kod źródłowy zamieszczonych w artykule przykładów.

Dlaczego warto używać testów zautomatyzowanych?

Przede wszystkim raz napisane testy mogą być wykorzystywane wielokrotnie. Przetestowanie aplikacji jest zatem znacznie szybsze i łatwiejsze. Ma to również jeszcze jedną ogromną zaletę – odporność na błędy regresyjne. Błędy regresyjne są błędami powracającymi po jakiejś modyfikacji kodu. Często naprawiając jedną rzecz w kodzie, psujemy drugą, wcześniej już uznaną za działającą. Wykorzystując testy manualne, zawsze musimy testować całą aplikację, aby mieć pewność, że błędy regresyjne się nie pojawiły. Posiadając testy zautomatyzowane, za pomocą kilku kliknięć możemy przekonać się o poprawności bądź niepoprawności badanego kodu.

Ponadto testy zautomatyzowane można uruchamiać regularnie o określonych porach lub na pewnych etapach produkcji. Na przykład można ustawić testy tak, żeby były wykonywane codziennie w nocy, aby rano był dostępny pełny raport.

Czym są testy jednostkowe?

Testy jednostkowe (ang. unit test) porównują oczekiwany wynik funkcji z rzeczywistym rezultatem. Na przykład sprawdźmy funkcję sumującą dwie liczby całkowite. W łatwy sposób można ustalić dane wejściowe oraz oczekiwany wynik:

Tabela 1. Oczekiwane wyniki dla funkcji sumującej  dwie liczby całkowite.

Liczba A Liczba B Oczekiwany wynik
1 1 2
2 3 5
-5 5 0

Test jednostkowy wywołuje zatem badaną funkcję i przekazuje jej parametry, a następnie porównuje otrzymany wynik z oczekiwanym. Jeśli badana funkcja dla parametrów 2 oraz 3 zwróci wartość inną niż 5, wiadomo, że kod został niepoprawnie napisany.

Testy jednostkowe często są nazywane testami czarnej skrzynki dlatego, że tester nie musi znać kodu zawartego w badanej funkcji. Dla testera ważny jest wyłącznie wynik wywołania funkcji – nie musi analizować kodu w niej zawartego.

**Rysunek 1. Testy czarnej skrzynki.

Właściwości dobrze napisanych testów jednostkowych

Przed implementacją pierwszych testów należy zrozumieć istotę ich działania. Oto kilka najważniejszych cech dobrze napisanych testów jednostkowych.

  • Niezależność oraz izolacja – testy jednostkowe, jak sugeruje nazwa, dotyczą małych, dokładnie sprecyzowanych obszarów kodu. Nie można pisać testów badających kilka funkcji naraz. Jeżeli wywołanie jednej metody pociąga za sobą wykonanie innych funkcji (np. metoda A wywołuje metodę B itp.), należy wykorzystać tzw. makiety obiektów. Makiety obiektu naśladują zachowanie metod, zwracając z góry zdefiniowane wyniki. Za pomocą frameworku NMock łatwo zdefiniować, co poszczególne metody powinny zwracać. Dzięki takiej izolacji w przypadku wystąpienia błędu wiemy dokładnie, w którym miejscu szukać przyczyny błędu – obszar poszukiwań znacząco się pomniejszył. Ponadto testy jednostkowe powinny być niezależne od zewnętrznych zasobów (usługa sieciowa czy zdalna baza danych). Pozwoli to na łatwe uruchamianie testów na różnych komputerach – bez konieczności skomplikowanej konfiguracji.
  • Przejrzystość – testy jednostkowe powinny zostać napisane z wielką starannością, ponieważ mogą posłużyć jako dokumentacja. Ze względu na ich specyfikę programista, czytając kod testu jednostkowego, może łatwo zrozumieć, jak korzystać z  danej klasy oraz jak dobierać parametry wejściowe.
  • Szybkość – wykonanie poszczególnych testów nie powinno przekraczać 5–10 sekund (jeśli nie złamano zasady atomowości). Często wykonuje się automatycznie szereg testów jednostkowym i naturalne jest, aby zostały one zakończone w jak najkrótszym czasie.

Co należy testować w pierwszej kolejności?

Oczywiście najlepiej byłoby przetestować dogłębnie cały system – wszystkie metody wraz ze wszystkimi przypadkami użycia. W rzeczywistości nie zawsze jest to możliwe. Należy zatem określić, które metody najbardziej są podatne na błędy.

  • Publiczne metody - stanowią interfejs dostępowy do całego systemu. Podczas powstawania nowych wersji bibliotek rzadko zmieniają sygnaturę (zachowanie kompatybilności wstecz). Pisanie testów metod publicznych jest bardzo proste i sprowadza się do standardowego wywoływania badanych funkcji. Wadą podejścia jest niezgodność z podstawą zasadą atomowości – metody publiczne często pełnią rolę funkcji opakowujących (ang. wrappers).
  • Metody chronione - wykonują bardziej atomowe operacje. Niestety, ich przetestowanie jest trudniejsze, ponieważ nie można w bezpośredni sposób wywołać metody chronionej ze sterownika testu. Rozwiązaniem jest  stworzenie klasy opakowującej dziedziczącej po badanej klasie. Wtedy wystarczy przeładować chronione metody i wywoływać je w publicznych metodach opakowujących. Wtedy wystarczy przeładować chronione metody i wywoływać je w publicznych metodach opakowujących.

Rysunek 2. Testowanie metod chronionych.

  • Metody prywatne są jeszcze trudniejsze do przetestowania.  – Nie nie można ich wywołać bezpośrednio w kodzie. Należy zdać sobie jednak sprawę, że modyfikatory public, protected, private mają znaczenie wyłącznie dla kompilatora. Za pomocą mechanizmu refleksji można ominąć ograniczenia i wywołać prywatną metodę:
System.Type type = typeof(Window);
System.Reflection.MethodInfo methodInfo = type.GetMethod(“InitializeComponent”, System.Reflection.BindingFlags.Instance | 
   System.Reflection.BindingFlags.NonPublic);
methodInfo.Invoke(this, null);

Najważniejszym argumentem przeciwko wykonywaniu testów metod prywatnych jest fakt, że tester ma pewność testując metody publicznie i chronione tester ma pewność, że przetestuje również wszystkie metody prywatne. Z drugiej jednak strony metody prywatne zawierają wyłącznie operacje atomowe, co jest bardzo korzystne z przyczyn opisanych już wyżej.

Pierwszy test jednostkowy

Po długiej teorii można przejść już do praktyki – napisania pierwszego testu jednostkowego. Załóżmy, że do przetestowania mamy klasę wykonującą podstawowe operacje matematyczne:

public class BasicOperations
    {
        public int Add(int numberA, int numberB)
        {
            return numberA + numberB;
        }
        public int Subtract(int numberA, int numberB)
        {
            return numberA - numberB;
        }
        public int Multiply(int numberA, int numberB)
        {
            return numberA * numberB;
        }
        public int Divide(int numberA, int numberB)
        {
            return numberA / numberB;
        }
    }

Następnym krokiem jest stworzenie konkretnego testu jednostkowego. Można napisać całą klasę testującą od postaw lub skorzystać z wbudowanych kreatorów. Drugie rozwiązanie jest dużo prostsze, więc klikamy prawym przyciskiem myszy na nazwie klasy i wybieramy Create Unit Test.

**Rysunek 3. Generowanie testów jednostkowych dla zaimplementowanej klasy.

Po kliknięciu pojawi się okno, w którym można wybrać metody, dla których chcemy wygenerować testy. Upewniamy się, że wszystkie pola zostały zaznaczone i klikamy OK.

**Rysunek 4. Wybieranie metod, dla których zostaną wygenerowane testy jednostkowe.

Po chwili zostanie wygenerowany nowy projekt zawierający testy jednostkowe. Warto przyjrzeć się przyjętej notacji. Każda klasa testująca zakończona jest tekstem „Test” (BasicOperations – BasicOperationsTest). Podobnie zostały nazwane metody testujące.

Rysunek 5. Wygenerowany projekt testów jednostkowych.

Automatycznie wygenerowana metoda testująca wygląda następująco:

[TestMethod()]
        public void AddTest()
        {
            BasicOperations target = new BasicOperations(); // TODO: Initialize to an appropriate value
            int numberA = 0; // TODO: Initialize to an appropriate value
            int numberB = 0; // TODO: Initialize to an appropriate value
            int expected = 0; // TODO: Initialize to an appropriate value
            int actual;
            actual = target.Add(numberA, numberB);
            Assert.AreEqual(expected, actual);
            Assert.Inconclusive("Verify the correctness of this test method.");
        }

W ciele metod najpierw jest tworzona instancja klasy BasicOperations. Następnie do zmiennych lokalnych przypisane są wartości parametrów wejściowych (numberA oraz numberB), oraz spodziewany wynik (expected). Zmienna acutal przechowuje rzeczywistą wartość wykonania metody Add. W celu walidacji poprawności metody Add należy porównać wartość zmiennej actual (rzeczywisty wynik) z expected (wzorcowy wynik) za pomocą asercji AreEqual:

Assert.AreEqual(expected, actual);

Asercja sprawdza, czy parametry wejściowe (expected, actual) są równe. Jeśli wartości będą różne, test zakończy się niepowodzeniem i programista będzie musiał poprawić błąd w kodzie. Wywołanie Inconclusive oznacza, że test nie może zostać rozstrzygnięty. Domyślnie jest to generowane po to, aby zmusić programistę do przejrzenia automatycznie utworzonych testów jednostkowych.

Ustawmy więc parametry wejściowe, oczekiwany wynik oraz usuńmy Inconclusive:

[TestMethod()]
        public void AddTest()
        {
            BasicOperations target = new BasicOperations();
            int numberA = 3; 
            int numberB = 5; 
            int expected = 8;
            int actual;
            actual = target.Add(numberA, numberB);
            Assert.AreEqual(expected, actual);            
        }

Aby uruchomić test, należy kliknąć na nazwie testu oraz z menu kontekstowego wybrać Run Tests:

**Rysunek 6. Uruchamianie testów.

Po chwili pojawi się komunikat odnoszący się do wyniku testu:

**Rysunek 7. Raport z wykonania testów.

Spróbujmy przetestować teraz metodę Divide:

[TestMethod()]
        public void DivideTest()
        {
            BasicOperations target = new BasicOperations();
            int numberA = 4; 
            int numberB = 2; 
            int expected = 2;
            int actual;
            actual = target.Divide(numberA, numberB);
            Assert.AreEqual(expected, actual);            
        }

Po uruchomieniu przekonamy się, że funkcja dla takich danych wejściowych działa prawidłowo. Jak już zostało wcześniej wspomniane, testy jednostkowe powinny być atomowe – dotyczyć wyłącznie jednego przypadku użycia. Jeśli chcemy przetestować inne przypadki, należy dopisać dodatkowe metody. Jeśli weźmiemy pod uwagę funkcję dzielenia, z pewnością wypadałoby sprawdzić co się stanie, jeśli spróbujemy dzielić przez zero. Załóżmy, że oczekiwanym wynikiem jest wyrzucenie przez metodę wyjątku ArgumentOutOfRangeException. Należy więc napisać dodatkową metodę testującą:

[TestMethod()]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void DivideByZeroTest()
        {
            BasicOperations target = new BasicOperations();
            int numberA = 4;
            int numberB = 0;            
            int actual;
            actual = target.Divide(numberA, numberB);  
  }

Warto zwrócić uwagę na atrybut ExpectedException, który określa, jaki wyjątek powinien zostać wyrzucony. Test zatem zostanie uznany jako zaliczony tylko wtedy, gdy metoda Divide wyrzuci wyjątek typu ArgumentOutOfRangeException. Po uruchomieniu testu szybko przekonamy się, że metoda nie przejdzie go pozytywnie:

**Rysunek 8. W tej chwili Divide wyrzuca wyjątek DivideByZeroException zamiast ArgumentOutOfRangeException.

Metoda Divide nie przeszła testu i należy  ją poprawić poprzez obsługę przypadku, w którym drugi parametr wejściowy równy jest zero:

public int Divide(int numberA, int numberB)
        {
            if (numberB == 0)
                throw new ArgumentOutOfRangeException();
            return numberA / numberB;
        }

Po ponownym uruchomieniu testu, uzyskujemy wynik pozytywny:

Rysunek 9. Test jednostkowy dzielenia przez zero.

Warto zaznaczyć, że można uruchomić jednocześnie kilka testów jednocześnie naraz. Przykładowo, chcąc uruchomić wszystkie testy jednostkowe dla danej klasy testującej, wystarczy kliknąć jej nazwę i z menu kontekstowego wybrać Run Tests.

Rysunek 10. Wykorzystując IDE, można również odpalić uruchomić kilka testów jednostkowych jednocześnie.

 

Typy asercji

Oprócz najczęściej wykorzystywanej metody AreEqual istnieje wiele innych typów asercji:

Metoda Opis
AreEqual Sprawdza, czy wartości są równe.
AreNotEqual Sprawdza, czy wartości są różne.
AreNotSame Sprawdza, czy przekazane wartości nie odnoszą się do tego samego obiektu. W przeciwieństwie do AreEqual, metoda bada referencje, a nie konkretne wartości.
AreSame Sprawdza czy przekazane wartości odnoszą się do tego samego obiektu. W przeciwieństwie do AreEqual, metoda bada referencje, a nie konkretne wartości.
Fail Wywołanie powoduje, że test jednostkowy jest niezaliczony.
Inconclusive Powoduje, że wykonanie testu jest nierozstrzygnięte.
IsFalse Sprawdza, czy dostarczona wartość lub wyrażenie są fałszywe
IsInstanceOfType Sprawdza, czy dostarczona wartość jest podanego typu.
IsNotInstanceOfType Sprawdza, czy dostarczona wartość nie jest podanego typu.
IsNotNull Sprawdza, czy dostarczona wartość nie jest NULL.
IsNull Sprawdza, czy dostarczona wartość jest NULL.
IsTrue Sprawdza, czy dostarczona wartość lub wyrażenie jest prawdziwe.

Testy zorientowane na dane (ang. data-driven testing)

Podstawowym problemem w testach jednostkowych jest dostarczenie prawidłowego zbioru testującego. Można tworzyć kilka metod testujących, które wykorzystują różne dane.  Istnieje jednak dużo bardziej eleganckie rozwiązanie – zamiast pisać kilka metod wykonujących te same operacje, lepiej dostarczyć zbiór parametrów wejściowych. Dane można dostarczać w różny sposób – poprzez bazy danych, pliki tekstowe czy arkusz kalkulacyjny. Ze względu na łatwość edycji danych, w artykule wykorzystano arkusz kalkulacyjny. Załóżmy, że chcemy przetestować metodę Add za pomocą rozmaitych danych wejściowych:

Tabela 2. Dane testujące dla metody Add.

Parametr A Parametr B Oczekiwany wynik
0 5 5
-5 5 0
2 2 4
-1 -1 -2

W celu podłączenia źródła danych (pliku excel) do testu jednostkowego, wykonujemy następujące czynności:

1. Z menu głównego wybieramy Test->Window->TestView.

Rysunek 11. Okno TestView.

2. Klikamy na teście test AddTest oraz z menu kontekstowego wybieramy właściwości (properties).

**Rysunek 12. Właściwości testu.

3. Znajdujemy właściwość Data Connection String i klikamy w trzykropek.

4. W kreatorze zaznaczamy pierwszą opcję (database) i klikamy Next.

Rysunek 13. Możliwe źródła danych.

5. Na tym etapie należy określić dokładnie źródło danych. W tym celu klikamy w New Connection, a następnie w przycisk Change w celu ustawienia sterownika na ODBC. Po ustawieniu prawidłowego pliku okno powinno wyglądać następująco:

**Rysunek 14. Prawidłowa konfiguracja połączenia do arkusza kalkulacyjnego.

6. Następnie można przejść już do końca kreatora i kliknąć Finish, pamiętając jednak o wybraniu stosownego arkusza.

**Rysunek 15. Należy pamiętać o wybraniu prawidłowego arkusza.

Na końcu VS spyta, się czy dodać plik do projektu – należy oczywiście potwierdzić.

**Rysunek 16, Za każdym uruchomieniem testu, VS przekopiuje plik z danymi do aktualnego kontekstu –- jest to tzw. deployment.

Warto zwrócić uwagę, że kreator dodał tak naprawdę nowy atrybut do testu – DataSource:

[DataSource("System.Data.Odbc", "Dsn=Excel Files;dbq=|DataDirectory|\\TestData.xlsx;defaultdir=.;driverid=1046;maxbuffersize=2048;pagetimeout=5", "Sheet1$", DataAccessMethod.Sequential), DeploymentItem("TestProject1\\TestData.xlsx"), TestMethod]
        public void AddTest()
        {
            BasicOperations target = new BasicOperations();
            int numberA = Int32.Parse(TestContext.DataRow[0].ToString());
            int numberB = Int32.Parse(TestContext.DataRow[1].ToString());
            int expected = Int32.Parse(TestContext.DataRow[2].ToString());
            int actual;
            actual = target.Add(numberA, numberB);
            Assert.AreEqual(expected, actual);            
        }

W ciele metody, zamiast „na sztywno” przypisywać wartości, wykorzystujemy TestContext:

TestContext.DataRow[0]

Można również uzyskać stosowne dane, podając nazwę kolumny zamiast indeksu:

TestContext.DataRow["FirstName"]

Test oczywiście zakończy się oczywiście sukcesem:

**Rysunek 17. Warto zwrócić uwagę na liczbę porównań Results 4/4.

Instalacja potrzebnych danych

Podczas implementacji testów należy szczególnie zadbać o ich niezależność od zewnętrznych zasobów.Z tego względu, gdy test jednostkowy potrzebuje jakichś danych (np. pliku tekstowego), należy zadbać, o to aby plik był instalowany za każdym uruchomieniem testu. Nie można pozwolić, aby testy odnosiły się do bezwzględnej ścieżki na dysku. Jeśli zajdzie taka potrzebna, można skorzystać z tzw. wdrażania danych (ang. deployment). Wystarczy z menu głównego wybrać Test->Edit Test Settings->Local, a następnie kliknąć na pozycji Deployment:

**Rysunek 18.****Wdrażanie danych: wystarczy kliknąć na AddFile, aby wskazać wymagane pliki.

Po uruchomieniu testu, wszystkie wymagane pliki będą przekopiowywane do odpowiedniego folderu.

Testy z kolejnością (ang. ordered testing)

Wszystkie testy jednostkowe powinny być niezależne – wykonanie jednego testu nie może wpływać bezpośrednio lub pośrednio na drugi test. W praktyce jednak często spotykamy się z sytuacją, gdy  niepowodzenie jednego testu gwarantuje, że drugi test również zostanie zakończony negatywnie. W takiej sytuacji warto postarać się o pewien porządek wykonywania testów – np. od tych podstawowych do bardziej złożonych.

W VS2010 wystarczy kliknąć na nazwie projektu testowego i z menu kontekstowego wybrać Add->Ordered Test. Pojawi się okno, w którym można zdefiniować kolejność wykonywania testów:

Rysunek 19. Testy jednostkowe można również wykonywać w określonej kolejności.

 

Przypadki użycia, dobieranie danych wejściowych

Jednym z większych wyzwań podczas pisania testów jednostkowych jest prawidłowe rozpoznanie przypadków użycia. Należy pamiętać, że pojedynczy test powinien testować wyłącznie jeden warunek (zasada atomowości). Poniżej kilka wskazówek odnoszących się do identyfikacji przypadków użycia:

  • Najpierw należy napisać test najczęściej występującego przypadku użycia czyli prawidłowego wykonania metody. Testujemy metodę, dobierając prawidłowe, najczęściej występujące dane wejściowe.
  • Mieszanie danych – dla pewności warto przetestować metodę różnymi danymi, np. za pomocą wcześniej już opisanych testów zorientowanych na dane.
  • Wartości graniczne – trzeba pamiętać o napisaniu testów z wartościami skrajnymi, tzn. maksymalnie małymi lub dużymi. Dla liczb całkowitych będzie to int.MaxValue lub int.MinValue.
  • Wartości NULL – często badana funkcja nie działa, gdy dostarczymy jej wartości NULL. Wynika to z faktu, że programiści często zapominają o wstępnej walidacji parametrów wejściowych.
  • Wyjątki – należy również celowo dostarczać błędne wartości. Zadaniem testu jest wtedy sprawdzenie, czy funkcja wyrzuci prawidłowy wyjątek w sytuacji, gdy dostarczymy błędne dane.
  • Pokrycie kodu – w idealnej sytuacji każda linia kodu powinna zostać wywołana przez któryś z testów jednostkowym. VS pozwala na sprawdzenie, w jakiś procencie jest to zrealizowane – o tym w następnej sekcji.

Pokrycie kodu

Pokrycie kodu mierzy, ile procent kodu zostało sprawdzone przez testy jednostkowego. Przyjmuje się, że dobrze napisane testy powinny mieć pokrycie rzędu przynajmniej 70% . Wyróżniamy  dwa sposoby wyliczania pokrycia:

  • Pokrycie wyrażeń (ang. statement coverage) – metryka stanowi iloraz liczby linii kodu wywołanych przez testy jednostkowe oraz całkowitej liczby linii kodu. Jeśli jakaś linia nie została pokryta, istnieje zatem ryzyko wystąpienia w niej błędu.
  • Pokrycie rozgałęzień (ang. branch coverage) – pokrycie nie bada  linii kodu, a rozgałęzienia zrealizowane za pomocą np. instrukcji if.

Aby uzyskać wartość metryki, należy najpierw z menu kontekstowego wybrać Test->Edit Test Settings->Local, a następnie przejść do zakładki Data And Diagnostics. W oknie należy zaznaczyć opcję Code Coverage.

Rysunek 20. Uaktywnianie metryki pokrycia kodu.

Następnie klikamy na przycisk Configure i zaznaczamy biblioteki, z których powinny zostać zebrane dane.

Rysunek 21.* Należy wybrać biblioteki, z których będą zbierane dane***.

W tej chwili po uruchomieniu testów można przejrzeć wyniki w oknie Code Coverage Results ( z menu głównego należy wybrać Test->Windows->Code Coverage Results).

**Rysunek 22. Metryka pokrycia kodu.

Na koniec pozostaje pytanie, czy warto dążyć do uzyskania pokrycia równego 100%? Zwykle nie jest to warte wysiłku, ponieważ nawet 100% pokrycie pokrycia kodu nie gwarantuje, że metoda działa poprawnie. Rozważmy jeszcze raz implementację metody Divide:

public int Divide(int numberA, int numberB)
        {
            return numberA / numberB;
        }

Napisanie pojedynczego testu jednostkowego doprowadzi do uzyskania pokrycia równego 100%:

[TestMethod()]
        public void DivideTest()
        {
            BasicOperations target = new BasicOperations();
            int numberA = 4; 
            int numberB = 2; 
            int expected = 2;
            int actual;
            actual = target.Divide(numberA, numberB);
            Assert.AreEqual(expected, actual);            
        }

Jak już wiemy, metoda nie działa poprawnie – nie obsługuje przypadku, gdy drugi parametr równy jest zero. Pomimo dokładnego pokrycia, testy nie zagwarantowały poprawności. W praktyce więc wystarczy, gdy uzyskamy pokrycie rzędu 80–%-90%.

Zakończenie

Na zakończenie warto jeszcze wspomnieć o metodyce TDD (ang. test-driven development). TDD polega na tym, że najpierw tworzone są testy, a dopiero potem właściwy kod. Takie podejście zapewnia maksymalne pokrycie, ponieważ nie można napisać właściwego kodu dopóki nie zostanie napisany test.

Warto również wspomnieć, że istnieje jeszcze jeden popularny framework do testowania –- NUnit. Zainteresowanych zachęcam do zapoznania się we własnym zakresie z dokumentacją zamieszczoną na oficjalnej stronie projektu.

 


          

Piotr Zieliński

Absolwent informatyki o specjalizacji inżynieria oprogramowania Uniwersytetu Zielonogórskiego. Posiada szereg certyfikatów z technologii Microsoft (MCP, MCTS, MCPD). W 2011 roku wyróżniony nagrodą MVP w kategorii Visual C#. Aktualnie pracuje w General Electric pisząc oprogramowanie wykorzystywane w monitorowaniu transformatorów . Platformę .NET zna od wersji 1.1 – wcześniej wykorzystywał głównie MFC oraz C++ Builder. Interesuje się wieloma technologiami m.in. ASP.NET MVC, WPF, PRISM, WCF, WCF Data Services, WWF, Azure, Silverlight, WCF RIA Services, XNA, Entity Framework, nHibernate. Oprócz czystych technologii zajmuje się również wzorcami projektowymi, bezpieczeństwem aplikacji webowych i testowaniem oprogramowania od strony programisty. W wolnych chwilach prowadzi blog o .NET i tzw. patterns & practices.