NDepend4 – co potrafi statyczna analiza kodu.

Dostałem NDepend4 w zamian za jego opis 🙂 Nie muszę o nim mówić w samych słodkich komentarzach, także można wpis czytać do końca.
NDepend służy to statycznej analizy kodu napisanego w .NET. Potrafi ocenić kod po wieloma względami, np: ze względu na skomplikowanie, ilość linii kod czy instrukcji IL, ilość zmiennych, łatwość modyfikacji, może zasugerować zmianę typów z referencyjnych na wartościowe (ref type –> value type).
Sama instalacja jest banalnie prosta, ściągamy paczkę ze strony, rozpakowujemy oraz wrzucamy plik z licencją :> Chyyyba że jej nie macie, wtedy można skorzystać z darmowej wersji 14 dniowej. To wszystko, potem wystarczy odpalić i załadować plik z solucją, którą chcielibyśmy się bliżej przyjrzeć.

Do opisu wykorzystam swoją pierwszą aplikację na Windows Phone. Trochę wstyd, ale dla dobra ludzkości trzeba się czasem trochę poświęcić. Przy okazji, jest ona dostępna na Baazar (nieoficjalny marketplace dla Windows Phone 7) – nazywa się CompactCal

Po uruchominiu NDepend można załadować plik solucji, którą chcemy poddać analizie. Następnie zostanie wyświetlona lista assemblies, które zostaną przeanalizowane:

Jak widać może się okazać, że czegoś będzie brakować, wtedy można dodać ręcznie brakujące pliki (np. używając drag’n’drop) walnąć OK. Warto zwrócić uwagę z której konfiguracji będą brane: debug czy release.

Czary mary, czekamy chwilę i wybieram opcje Close dialog – chce wszystko sam zobaczyć.
Pierwsze ciary na plecach, bo widzę bałagan w kodzie. Wszytko przez diagram zależności klas, u mnie wygląda on tak:

(Trochę się obrazek rozjechał)
Ci z nas, którzy posiadają Visual Studio wyższą niż expres (i chyba pro też), mogą znać już taki obrazek. Podobny widok można utworzyć w MVSC z menu Architecture -> Generate Dependency Graph -> By Assembly.
Ogólnie rzecz ujmując widać na nim, co od czego zależy i jak bardzo, im grubsza kreska tym więcej zależności w kodzie. Może być taka sytuacja że już tutaj zauważymy zależności których
nie powinno być. Może z jakiegoś powodu nasz View sięga bezpośrednio
do serwisów z danymi, a miał to robić tylko ViewModel. Po rozwiązaniu takich problemów, ja najczęściej usuwam  zależności od systemowy plików, tak jest dla mniej przejrzyściej.
Niestety ND (żeby nie pisać ciągle całej nazwy) nie potrafi sobie poradzić z ułożeniem kwadracików tak, aby linie się nigdzie nie przecinały. Nad czytelnością trzeba diagramu trzeba popracować samodzielnie. Nie wiem czemu, ale lubię właśnie w ten sposób mieć ułożone wszelkie diagramy.
Kolejnym innym widokiem zależności jest Matrix view, jest czytelniejszy niż diagram powyżej, w przypadkach gdy jest bardzo dużo obiektów do analizy.

U mnie tego nie widać, ale gdy pojawią się cyferki na czarnym tle, oznaczać to będzie zależności cykliczne. Choć muszę się przyznać, że gdy po raz pierwszy uruchomiłem ND na tym projekcie pewnie znalazło by się kilka czarnych miejsc. Na szczęście od tamtego czasu zdążyłem poprawić kod, a ten wpis powstaje dużo później. Widać na nim dokładnie zależności modułów, możemy badać zależność na podstawie ilości wykorzystywanych metod, pól, namespaces, typów zmiennych, i jeszcze kilku innych. ND potrafi także wykryć zależności nie bezpośrednie.
Po kliknięciu na jedno ze skrzyżowań, zobaczymy jeszcze dokładniejszy diagram tego, gdzie i jak są wykorzystywane te zależności, np. ViewModel vs. Helpers:

Do tego można wygenerować ciekawszy graph (Export Matrix Code Elements to Graph):

Co w wyniku da coś takiego:

W tym przypadku diagram zależności jest czytelny (linie się nigdzie nie przecinają).

To dopiero początek, jeśli już pogodziliśmy się ze skalą zależności w naszym kodzie warto przejść dalej i zobaczyć jakie reguły złamaliśmy oraz gdzie. ND dostarcza ponad 80 zdefiniowanych reguł. Możemy samodzielnie je modyfikować, dodawać nowe oraz usuwać te z nich , które naszym lub architekta odpowiedzialnego za projekt są z jakiegoś powodu nie potrzebne.

Tak bardzo źle nie jest, brak czerwonych kółeczek.

Jak widać powyżej istnieje kilka różnych grup zasad, z których część została w brzydki sposób naruszona, a część jest akceptowalna. Zobaczmy co zrobiłem źle, sprawdźmy Code Quality (dwuklik i widać szczegóły):

Każda grupa składa się znowu z kilku mniejszych reguł, tutaj jak widać mam najwyraźniej problem ze zbyt dużymi klasami i metodami. Brakiem komentarzy się nie przejmuje. Mój kod komentuje się sam – tak go wytresowałem.

Ważne jest to że parametry definiujące te reguły możemy samodzielnie zmieniać. Dla przykładu domyślne aby metoda została oznaczona za posiadającą zbyt duża ilość parametrów,  musi posiadać ich więcej niż pięć:

Metody zbyt duże muszą mieć więcej niż 30 linii kodu lub więcej niż 200 instrukcji IL.

Gdy już zdecydujemy się obejrzeć listę przewinień służy do tego osobny panel, ja sprawdzę którą z metod tak strasznie spasłem kodem:

Ja mam dwa takie przypadki, gdzie ten drugi jest bliski wartości granicznej. Aby obejrzeć kod wystarczy dwuklik na wybranym przypadku. W wersji darmowej trzeba niestety ręcznie znaleźć kod w MSVC.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, „Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  public static WriteableBitmap GenerateBitmapImage(string aHeading, IEnumerable<string> aItems)
   2:  {
   3:      WriteableBitmap wbm = null;
   4:      StackPanel sp = new StackPanel();
   5:      sp.Margin = new Thickness { Left = 6, Bottom = 0, Right = 6, Top = 0 };
   6:      sp.Height = 173;
   7:      sp.Width = 173;
   8:      sp.Background = new SolidColorBrush(Colors.Transparent);
   9:   
  10:      if (!string.IsNullOrEmpty(aHeading))
  11:      {
  12:          TextBlock heading = new TextBlock();
  13:          heading.Style = (Style)Application.Current.Resources["PhoneTextTitle3Style"];
  14:   
  15:          heading.Text = aHeading;
  16:          heading.Height = 35;
  17:          heading.Width = 173;
  18:   
  19:          sp.Children.Add(heading);
  20:      }
  21:   
  22:      int maxitems = (string.IsNullOrEmpty(aHeading)) ? 5 : 6;
  23:      int c = 0;
  24:   
  25:      foreach (var item in aItems)
  26:      {
  27:          TextBlock tbAction = new TextBlock();
  28:          tbAction.Foreground = (SolidColorBrush)Application.Current.Resources["PhoneAccentBrush"];
  29:          tbAction.Text = item;
  30:          tbAction.Style = (Style)Application.Current.Resources["PhoneTextNormalStyle"];
  31:          tbAction.Margin = new Thickness(4, 0, 0, 0);
  32:          tbAction.Height = 27;
  33:          tbAction.Width = 173;
  34:          sp.Children.Add(tbAction);
  35:          if (c++ > maxitems)
  36:          {
  37:              break;
  38:          }
  39:      }
  40:   
  41:      //call measure, arrange and updatelayout to prepare for rendering
  42:      sp.Measure(new Size(173, 173));
  43:      sp.Arrange(new Rect(0, 0, 173, 173));
  44:      sp.UpdateLayout();
  45:   
  46:      wbm = new WriteableBitmap(173, 173);
  47:      wbm.Render(sp, null);
  48:      wbm.Invalidate();
  49:      return wbm;
  50:  }

Rzeczywiście całkiem sporo, kod z bloków IF oraz FOREACH można wyrzucić do osobnych metod, tak samo ostatnie linijki z kodem który wymusza render na bitmapie. Poprawię to później 😉
To tylko jeden z wielu przypadków, który jest możliwy do przeanalizowania w kodzie. Jak już napisałem ND domyślnie ma ich ponad 80. Na stronie domowej są przykłady jak pisać własne CQL (code query language) i analizować kod na inny sposób, np:

  • Ostrzegaj gdy code coverage poniżej n%
  • Nazwy interfejsów nie zaczynające się od litery I
  • Wykorzystanie wątków inaczej niż przez ThreadPool
  • Zmiany wartości zmiennych, bez wykorzystania mechanizmów synchronizacji
  • Zbyt długa lista typów dziedziczących po X

Oprócz tego co wymieniłem powyżej, generowany jest także dodatkowy raport podsumowujący cały projekt, wykresy, liczby, inne pierdółki, coś co menadżerowie i klienci lubią najbardziej.
Skupmy się na najważniejszej grupie, na programistach.Są tam także informacje, które nam także się przydadzą.
Dodatkowy wykres wyświetlający stopień abstrakcji oraz stabilność waszego kodu. Gdzie stabilność nie oznacza dobrego kodu, a raczej współpracę z innymi typami (wsparcie dla polimorfizmu). W zasadzie ciężko wytłumaczyć ten parametr. Scott Hanselman ma podobny wpis o ND, tam tłumaczy trochę lepiej ten wykres: post Scotta.
Teraz będzie siara, bo o ile jestem zwolennikiem abstrakcji, to w tym projekcie pojechałem bez niej:

Warto trzymać się zielonej strefy.
I tak Service oraz GeocodeService był już tworzony z myślą o testach czy zastosowaniem innego źródła danych, dlatego znajduje się wyżej jeśli chodzi o oś Y (abstrakcję), natomiast Model, Extension (tam są metody rozszerzające), Helpery, etc jest płaskie jak deska. Położenie na osi X działa w taki sposób: jeśli assembly jest nie rozszerzalne, brakuje mu/jej jakiejkolwiek wirtualności, a dodatkowo wiele innych typów od niej zależy tym bliżej strefy bólu (na lewo) będzie się znajdować. U mnie wygląda to tak, że kod nie jest abstrakcyjny (są momenty), jednak nie posiadam klas które są wykorzystywane przez wszystkie inne. Brak tutaj typowych „utilsów” czy klas statycznych, do których wszyscy sięgają. Może dzięki temu jestem z pięknej zielonej strefie szczęścia. Model mógłby dodać od siebie trochę abstrakcji (może ISP), mógłbym sprawdzić czy wszędzie gdzie jest on wykorzystywany jest to potrzebne, może rozbić część klas na trochę mniejsze. Natomiast patrząc na Service widać że zbliża się on do powoli do zbyt wielkiej abstrakcji, lub braku wykorzystania części jego funkcjonalności. Warto sprawdzić czy wszystkie jego metody są potrzebne, może da się część z nich tak zmienić, aby wykorzystywały jakiś wspólny kod.

Z dodatkowych w raporcie ND można zobaczyć ile jest w sumie klas, metod w klasach, właściwości w klasach, linii kodu w projekcie, klasach, metodach, wszystko to co wymieniłem można także badać pod względem ilości poleceń w IL.

Po co utrzymywać kod z którego nikt nie korzysta? W moim przypadku zostało kilka metod, które zostały domyślnie stworzone przez wizarda podczas tworzenia projektu na Windows Phone, np. cała seria ApplicationLaunching, ApplicationActivated, ApplicationDeactivated czy ApplicationClosing. z której nigdzie nie korzystam. Takie proste rzeczy, a czynią kod z którym się pracuje przyjemniejszym w utrzymaniu.

ND wspiera MSVC 2008, 2010 oraz 2012. Umożliwia badanie kodu prosto z menu kontekstowego w edytorze:
Raporty można zapisywać, a następnie porównywać czy zmiany wprowadzane idą ku lepszemu. Dzięki integracji z VS można także prosto edytować i uruchamiać zapytania CQL – prosto ze środowiska pracy.
Na krótkich filmikach widziałem także, że ND potrafi porównywać ze sobą poszczególne wersje aplikacji. Niestety nie wiem jak do tego dojść, podejrzewam, że trzeba pilnować numerów wersji w assembly.
Odpalany z linii poleceń powinien być prosty do integracji z system CI (w domu takiego czegoś nie posiadam) i generować odpowiednio konfigurowalne raporty – menadżerowie się ucieszą. Możliwość zdefiniowania własnych reguł przy pomocy CQL umożliwi generowanie ostrzeżeń podczas budowania kodu, który np. nie posiada testów.

Pisałem wcześniej o tym, że warto rozmawiać. Zadawać pytania i czytać kod nie tylko swój. Poprosić znajomego o sprawdzenie tego co stworzyliśmy. ND wydaje się być czymś co może ten proces częściowo zastąpić. Nie warto jednak od razu usuwać ludzi z listy kontaktów. Jasna sprawa, że dla mnie czy innego Kowalskiego (pozdrawiam Tomka jeśli to czyta) jest on raczej drogim narzędziem 299 euro, szczególnie gdy w domu piszemy kod do szuflady. Ale gdy zarabia się na oprogramowaniu to świetne rozwiązanie, senior czy inny architekt może na początku projektu ustalić pewne zasady, który kod musi spełniać i dopiero gdy czekin w repozytorium spełni je wszystkie, zostanie przesłany do osobistego review. Pozwali to zaoszczędzić wszystkim dużo czasu (czas to pieniądz) oraz pominąć głupie i często powtarzające się błędy, np. brak słowa sealed przy klasie nie przeznaczonej do dziedziczenia.

TL;DR; (podsumowanie)
NDepend to dobre narzędzie do statycznego sprawdzenia kodu. Szczególnie przydatne w komercyjnych projektach, pozwoli automatycznie pilnować porządku w kodzie, zapewni przestrzeganie przyjętych zasad w projekcie. Zadowoli menadżerów wymagających nudnej papierologii. Nawet jeśli piszesz do szuflady, to na koniec lub po zakończeniu projektu, wykorzystaj wersję 14 dniową i sprawdź swój kod. Może się okazać, że da się lepiej.
Jeśli miałbym narzekać, to mogliby dać wersję darmową do domowego użytku.

Jest także NDepened dla Javy oraz dla C++. Niestety nie wiem jak dobre/złe są.

Linki:
NDepend strona domowa: http://www.ndepend.com/Default.aspx
Cennik: http://www.ndepend.com/Purchase.aspx
Lista ficzerów, część z nich posiada filmiki. Zabawne jest to, że korzystają z syntezatora mowy: http://www.ndepend.com/Features.aspx

Jeśli macie pytania to proszę o kontakt, jeśli chcecie żebym opisał jakąś część funkcjonalności ND to piszcie. Jeśli chcecie abym sprawdził jakiś kod to piszcie.

Metody rozszerzające – testowanie i porządek w api.

Jak każdemu porządnemu developerowi zdarza mi się czasem napisać testy. Jak każdemu porządnemu developerowi, czasem zdarza mi się wykorzystać mechanizm metod rozszerzających (jeśli nie wiesz o czym mówię sprawdź na msdn). Jak każdy prawdziwy developer, chciałem przetestować logikę, która była wykorzystywana w jednej z takich metod. W zasadzie to nie w samej metodzie, chciałem sprawdzić czy zostanie wywołana z wartościami, które są dla mnie ważne.
Zacznę od metod rozszerzających, a testowanie przyjdzie samo.

Tak wygląda interfejs udostępniony przez samą (bez metod rozszerzających) klasę List:

Aby przewinąć listę na sam dół wystarczą trzy wciśnięcia Page Down. Jak widać jedyny import, z którego korzystam to System.Collections.Generic, wymagany aby móc skorzystać z List

To się stanie gdy dodam System.Linq: (obrazek się troszkę nie mieści)

Pojawi się nowa funkcjonalność, zdefiniowana w System.Linq, która pozwala robić cuda z listą i innymi IEnumerable (jak również innymi obiektami). Po imporcie aby przewinąć listę metod, trzeba osiem razy walnąć w Page Down.

Co zrobić, aby lista nowych funkcjonalności nie zaśmiecała dostępnego api? Można to wszystko ukryć przy pomocy jednej klasy, która przekierowuje żądania dalej, do właściwej części, gdzie zdefiniowane będą rozszerzenia
Normalnie implementacja rozszerzeń może wyglądać jakoś tak:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, „Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  namespace ConsoleApplication
   2:  {
   3:   
   4:      class Program
   5:      {
   6:          static void Main(string[] args)
   7:          {
   8:              MyClass mc = new MyClass();
   9:          }
  10:      }
  11:   
  12:      public static class MyClassExtension
  13:      {
  14:          public static void Foo0(this MyClass target) { }
  15:          public static void Foo1(this MyClass target) { }
  16:          public static void Foo2(this MyClass target) { }
  17:          public static void Foo3(this MyClass target) { }
  18:          public static void Foo4(this MyClass target) { }
  19:          public static void Foo5(this MyClass target) { }
  20:          public static void Foo6(this MyClass target) { }
  21:          public static void Foo7(this MyClass target) { }
  22:          public static void Foo8(this MyClass target) { }
  23:          public static void Foo9(this MyClass target) { }
  24:      }
  25:   
  26:      public class MyClass
  27:      {
  28:      }
  29:  }

Po takiej implementacji otrzymujemy cztery metody podstawowe, oraz dodatkowy dziesięć nowych z rozszerzenia w MyClassExtension.

Można trochę to wszystko uprościć dodając odpowiednią warstwę abstrakcji (abstrakcja was uwolni, zapamiętajcie to). Opis poniżej:

   1:  namespace ConsoleApplication
   2:  {
   3:      using System;
   4:   
   5:      class Program
   6:      {
   7:          static void Main(string[] args)
   8:          {
   9:              MyClass mc = new MyClass();
  10:              mc.ExtensionService().Foo0();
  11:          }
  12:      }
  13:   
  14:      public static class MyClassExtension
  15:      {
  16:          public static Func<MyClass, IMyClassExtensionService> ExtensionFactory { get; set; }
  17:   
  18:          static MyClassExtension()
  19:          {
  20:              ExtensionFactory = target=> new MyClassExtensionReleaseVersion(target);
  21:          }
  22:   
  23:          public static IMyClassExtensionService ExtensionService(this MyClass target)
  24:          {
  25:              return ExtensionFactory(target);
  26:          }
  27:      }
  28:   
  29:      public interface IMyClassExtensionService
  30:      {
  31:          void Foo0();
  32:          void Foo1();
  33:          void Foo2();
  34:          void Foo3();
  35:          void Foo4();
  36:          void Foo5();
  37:          void Foo6();
  38:          void Foo7();
  39:          void Foo8();
  40:          void Foo9();
  41:      }
  42:   
  43:      public class MyClassExtensionReleaseVersion : IMyClassExtensionService
  44:      {
  45:          private MyClass myclass;
  46:   
  47:          public MyClassExtensionReleaseVersion(MyClass target)
  48:          {
  49:              this.myclass = target;
  50:          }
  51:   
  52:          public void Foo0() { }
  53:          public void Foo1() { }
  54:          public void Foo2() { }
  55:          public void Foo3() { }
  56:          public void Foo4() { }
  57:          public void Foo5() { }
  58:          public void Foo6() { }
  59:          public void Foo7() { }
  60:          public void Foo8() { }
  61:          public void Foo9() { }
  62:      }
  63:   
  64:      public class MyClass
  65:      {
  66:      }
  67:  }

Tak się to prezentuje teraz:

Zacznę od linijki 29 gdzie zdefiniowany został interfejs ze wszystkimi metodami, które mają działać jako rozszerzenie dla klasy MyClass. Linia 43 to implementacja tego interfejsu, warto zauważyć że klasa ta nie jest statyczna, oraz nie posiada statycznych pól, a metody nie przyjmują parametrów. W konstruktorze otrzymuje ona obiekt, na rzecz którego ma działać. Najlepsze zostawiam na koniec, linijka 14, klasa udostępniająca rozszerzenie dla MyClass. W ExtensionService (23) wykorzystywana jest właściwość ExtensionFactory, który jest zwyczajną metodą wytwórczą, zwracającą nowy obiekt MyClassExtensionReleaseVersion, do którego przesyła obiekt, na rzecz którego ma zostać wywołana metoda rozszerzająca. Mam nadzieję, że kod tłumaczy to prościej niż ja. Linia 10 to nowy sposób na wywołanie metod rozszerzających. 
Właściwość ExtensionFactory została zdefiniowana jako publiczna, co umożliwia jego zmianę w razie potrzeby testowania. Tak samo obiekt rozszerzający jest deklarowany poprzez interfejs, w związku z czym w testach można wykorzystać np. MyClassExtensionMockVersion i sprawdzać czy klasa zachowuje się tak jak tego oczekujemy.

Ja skorzystałem z tego rozwiązania podczas testowania nawigacji w projekcie, który korzysta z PRISMa, tam do zmiany widoków wykorzystywany jest interfejs IRegionManager i kilka metod rozszerzających ten interfejs. Chciałem sprawdzić, czy po wykonaniu operacji X jedna z moich klas zarząda zmiany widoku. Jedynym sposobem, na sprawdzenie parametrów przesłanych w query było stworzenie i podstawienie własnej implementacji metod rozszerzających. Na początku testów mam taki zapis:

   1:  Capture.Common.Prism.PrismExtensions.ServiceFactory = p => new NavigationServiceMock(p);

Tak wygląda mój lipny serwis, który działa zamiast tego z Prism:

   1:  public class NavigationServiceMock : INavigation
   2:  {
   3:      private IRegionManager p;
   4:   
   5:      public NavigationServiceMock(IRegionManager p)
   6:      {
   7:          this.p = p;
   8:      }
   9:   
  10:      public IRegionManager AddToRegion(string regionName, object view)
  11:      {
  12:          throw new NotImplementedException();
  13:      }
  14:   
  15:      public IRegionManager RegisterViewWithRegion(string regionName, Func<object> getContentDelegate)
  16:      {
  17:          throw new NotImplementedException();
  18:      }
  19:   
  20:      public IRegionManager RegisterViewWithRegion(string regionName, Type viewType)
  21:      {
  22:          throw new NotImplementedException();
  23:      }
  24:   
  25:      public void RequestNavigate(string regionName, string source)
  26:      {
  27:          throw new NotImplementedException();
  28:      }
  29:   
  30:      public void RequestNavigate(string regionName, Uri source)
  31:      {
  32:          LoadTradeViewModelTest.NavigationServiceMock_RequestedNavigatedTargetRegionName = regionName;
  33:          LoadTradeViewModelTest.NavigationServiceMock_ReqestedNavigatedTargetUri = source.OriginalString;
  34:      }
  35:   
  36:      public void RequestNavigate(string regionName, string source, Action<NavigationResult> navigationCallback)
  37:      {
  38:          throw new NotImplementedException();
  39:      }
  40:   
  41:      public void RequestNavigate(string regionName, Uri source, Action<NavigationResult> navigationCallback)
  42:      {
  43:          throw new NotImplementedException();
  44:      }
  45:  }

Najważniejsze w tym wszystkim to RequestNavigate (30) gdzie zapisuje żądany region i uri. Później w teście porównuje czy są takie jak oczekiwałem. Wszystkie NotImplementedException są nie używane w testach, dlatego też mogły pozostać w takiej domyślnie oferowanej przez VS formie.
Normalna implementacja przekazuje wszystkie parametry do oryginalnej implementacji.

Pojawia się pewien problem, przy próbie stworzenie podobnego rozwiązania dla klas generycznych. Ten problem to kompilator, który cały czas twierdzi że metody rozszerzające muszą być w klasach statycznych, nie generycznych. Same metody mogą być generyczne. Aby to obejść na szybko wymyśliłem coś takiego

   1:  namespace ConsoleApplication
   2:  {
   3:      using System;
   4:      using System.Collections.Generic;
   5:   
   6:      class Program
   7:      {
   8:          static void Main(string[] args)
   9:          {
  10:              List<int> l = new List<int>();
  11:              l.GetExtension().Foo1();
  12:          }
  13:      }
  14:   
  15:      public static class ExtensionsFactory<T>
  16:      {
  17:          public static Func<List<T>, IListExtensionService> Factory { get; set; }
  18:      }
  19:   
  20:      public static class ListExtension
  21:      {
  22:          public static IListExtensionService GetExtension<T>(this List<T> target)
  23:          {
  24:              if (ExtensionsFactory<T>.Factory == null)
  25:              {
  26:                  ExtensionsFactory<T>.Factory = o => new ListExtensionSerivce<T>(o);
  27:              }
  28:   
  29:              return ExtensionsFactory<T>.Factory(target);
  30:          }
  31:      }
  32:   
  33:      public interface IListExtensionService
  34:      {
  35:          void Foo1();
  36:      }
  37:   
  38:      public class ListExtensionSerivce<T> : IListExtensionService
  39:      {
  40:          private List<T> list;
  41:   
  42:          public ListExtensionSerivce(List<T> target)
  43:          {
  44:              this.list = target;
  45:          }
  46:   
  47:          public void Foo1()
  48:          {
  49:              Console.WriteLine("foo and the list");
  50:          }
  51:      }
  52:  }

Cały trik w tym przypadku, to przeniesienie odpowiedzialności na wytwarzanie obiektów z ListExtension do ExtensionsFactory. A podczas wywołania metody, należny się upewnić że fabryka już istnieje.

Myślę że wystarczy mojego wymądrzania się na dziś. Podoba się? Nie podoba się? Macie inne sposoby? Pytania? Uwagi? Cokolwiek?

ps
Mam nadzieję, że więcej wyjaśniłem niż zakręciłem.
ps2
Jak już wszystko napiszę, to czytam czy jest ok, a potem i tak mam lekkiego stresa, że coś głupiego napisałem.

xUnit vs Event – jak go przetestować?

Rozpoczynam kolejny projekt, który ma przynieść mi chwałę, sławę i pieniądze. Piszę go przy wykorzystaniu TDD (jak zwykle angielska wiki ma więcej do powiedzenia) Wszystko szło ładnie do momentu gdy nie natrafiłem na test w którym chciałem sprawdzić czy klasa którą testuję wywoła event. No bo jak sprawdzić teraz coś co wykona się później?
Na szczęście wujek google zna programistów, którzy znają odpowiedź na takie i inne pytania, w związku z czym szybko znalazłem odpowiedź na swój problem.
Zamieszczę ja tutaj ze stosownymi komentarzami od siebie, plus kilka własnych uwag, które może ktoś uzna za wartościowe. Jest taki oto sobie kod:

  1. // arrange
  2. var cut = new AsyncImageServiceDownloader();
  3. var isp = new ImagesServiceProvider();
  4. const string ServiceAddress = „http://www.digart.pl/”;
  5. const string PageSuffix = „/przegladaj/nowe.html?p=”;
  6. isp.ServiceAddress = ServiceAddress;
  7. isp.PageAddressSuffix = PageSuffix;
  8. cut.AddProvider(isp);
  9. int downloaded = 0;
  10. int canceled = 0;
  11. int suspended = 0;
  12. int resumed = 0;
  13. const uint Requested = 30;
  14. ManualResetEvent synvEvent = new ManualResetEvent(false);
  15.  
  16. // act
  17. cut.AddProvider(isp);
  18. cut.PageDownloaded += (s, e) => { downloaded++; };
  19. cut.PageDownloadCanceled += (s, e) => { canceled++; };
  20. cut.PageDownloadSuspended += (s, e) => { suspended++; };
  21. cut.PageDownloadResumed += (s, e) => { resumed++; };
  22.  
  23. cut.DownloadPageAsync(0, 0, Requested);
  24. for (uint i = 0; i < Requested; ++i)
  25. {
  26.     cut.SuspendDownload(0, i);
  27. }
  28.  
  29. synvEvent.WaitOne(1000);
  30. for (uint i = 0; i < suspended; ++i)
  31. {
  32.     cut.ResumeDownload(0, i);
  33. }
  34.             
  35. synvEvent.WaitOne(15 * 1000);
  36. // assert
  37. Assert.That(downloaded, Is.EqualTo(Requested));
  38.  

Widzimy podział na trzy AAA (nie znalazłem polskiego).
Na początku przygotowuje środowisko, które będę chciał sprawdzić; cut (class under test) to właściwy obiekt, który chce sprawdzić, isp służy do poprawnego zainicjalizowania obiektu cut. Ważny obiekt ManualResetEvent jest deklarowany i tworzony w linii 14 posłuży mi do synchronizacji i czekania na eventy. Linie od 18 do 21 to podpięcie się pod eventy, którymi klasa może rzucić. Choć w tym przypadku interesują mnie tylko dwa: suspended oraz downloaded. Przy użyciu wyrażeń lambda podpinam się pod eventy, a jedyną rzecz jaką robię to zliczam ich wywołania. W tym teście nie interesuje mnie zawartość argumentów.
Skoro wszystko mam już gotowe do działania to ogień! Linia 23 wywołuje asynchroniczne zaciąganie 30 elementów (requested=30). Chwilę potem do akcja wkracza pętla z niecierpiącym zwłoki poleceniem SuspendDownload następnie daję chwilę testowanemu obiektowi na wykonanie kodu odpowiedzialnego za wywołanie odpowiednich eventów. Wszystko dzięki wywołaniu ManualResetEvent.WaitOne(1000) – specjalnie podany jest timeout, ponieważ nigdzie nie będzie zmieniony jego stan. Czas się kończy, testy wykonują się dalej. Kolejne rozkazy; ResumeDownload tyle razy ile razy zdążyłem go zawiesić. I kolejny odpoczynek z timeout 15 sekund, co pozwala spokojnie zakończyć się wszystkim operacjom ściągnięcia.
Na sam koniec sprawdzam czy rzeczywiście udało się ściągnąć tyle ile początkowo żądałem. Voila! To tyle, proste prawda?

Moje dwie uwagi:
– Pamiętaj że w obsłudze eventa możesz samodzielnie zmienić stan ManualEventReset, jeśli z tego skorzystasz nie zapomnij jednak dodać timeout do WaitOne w przeciwnym wypadku, w razie niepowodzenia testów ManualResetEvent zawiesi działanie testów.
– Zastanów się czego dokładnie oczekujesz od twojego cut. Jeżeli cut wywoła PageDownloaded trzy razy to od razu będziesz szczęśliwy? A może chcesz sprawdzić czy cut wywoła PageDownloaded tylko trzy razy, a żadnych innych eventów ani razu.

Jakieś uwagi? Inne sposoby? Literówka?
Wiesz jak mnie znaleźć.
JS

Jak następnym razem zacznę projekt

Ostatnimi czasy więcej czytam niż piszę i tak mi się kilka przemyśleń zebrało na temat: „co ja bym zrobił lepiej w tym projekcie”. Żeby było jasne, to są moje przemyślenia, do których bd starać się zastosować. Czysta teoria, nie potwierdzoną praktycznie żadną praktyką. Może poza kilkoma linijkami kodu. Czytam teraz takie różne kawałki kodu, pisze różne testy i wynika z tego prosta sprawa. Taniej, szybciej i skuteczniej jest napisać kawałek prostego testu, a potem poprawić drobne błędy. Niż napisać oddać testerom i „czekać” na listę błędów. Można inaczej: jedna osoba pisze kod, druga testy. Według mnie to jest strasznie uciążliwe, o każdej zmianie trzeba informować piszącego test, a nie zawsze się o tym pamięta. Jak testy nie przechodzą to nie do końca wiadomo kto zjebał, takie to nie poręczne. W dodatku w tej sytuacji, osoba pisząca testu musi wiedzieć co się dzieje w kodzie, znać specyfikacje i ogólnie ogarniać komponent – dla mnie to nie potrzebne powtarzanie. Osobiście uważam, że developer który pisze kod, jest za niego odpowiedzialny jak i za jego jakość => sam powinien zadbać o jego poprawne działanie.

Oczywistą oczywistością jest to, że nie będzie po kolei. Tzn od ważnej do nie ważnej, od małej do dużej, czy od żółtej do czerwonej.

Do rzeczy:
Weryfikuj zawsze dane wejściowe, jeżeli używasz wskaźników to przede wszystkim sprawdź czy wskazują tam gdzie powinny. Czyli nie są null, jeżeli będziesz z nich odczytywać lub do nich zapisywać. Lub czy są null jeżeli będziesz do nich przypisywać. To w ogóle temat rzeka z tymi wskaźnikami. Jeżeli oczekujesz wartości sprawdź, czy te które dostałeś są poprawne, np. mieszczą się w obsługiwanych zakresach. Ten mały i prosty kawałek kodu, ułatwi późniejsze debugowanie. Po wszystkim można taki kod wrzucić w #ifdef _DEBUG_ i po sprawie.
Odruchowo do każdego kodu, który wypocisz dodaj kawałek testu, który sprawdzi czy rzeczywiście, to co zrobiłeś działa tak byś sobie życzył. Postaraj się wywołać metodę  / funkcję z każdym możliwym parametrem, nawet z takim, który uznasz za błędny. Przecież od tego masz weryfikacje danych wejściowych. Jeżeli kod ma coś zwrócić sprawdź czy wynik jest tym czego oczekujesz lub czy rzucony wyjątek jest tym o który chodzi. Do każdego z rodzaju testów stwórz sobie osobne środowisko, a ingerencje w kod testowany naprawdę ogranicz do niezbędnego minimum. Najgorszą sprawą jest, gdy okazuję się że test jednostkowe i systemowe nie mogą zostać przeprowadzone na tym samym kodzie, lub że wyniki dla tych samych komponentów dadzą inny wynik.
Po tym jak obmyślisz już co piszesz, jak, w czym, etc. I narysujesz sobie kilka kwadracików ze strzałeczkami i takich tam. Kiedy myślisz, że to już ten moment że możesz już pisać. Odczekaj jeszcze z 5 minut. Popatrz na te ładne obrazki i pomyśl czy to na pewno tak ma być. Nie zapomniałeś o czymś? Co się stanie i jak bardzo będziesz się musiał namęczyć żeby dodać jakiegoś nowego ficzera? Albo czy możliwe będzie ponowne wykorzystanie komponentów, które właśnie zamierzasz zakodzić. Czy napewno dziedziczenie a nie agregacja 😉 Jeżeli po 5 minutach nadal jesteś pewien swego, to śmiało zaczynaj pisać.
Tutaj się trochę powtórzę, ale to mój blog więc mogę. Chodzi mi o obrazki, jak sobie narysujesz już wszystkie klocki układanki, wszystkie przepływy, kto, z kim i dlaczego. Zrób przysłowiowy krok w tył i spójrz czy nie będzie Ci wstyd takiego gniota później pisać i poprawiać. Prawie zawsze można lepiej.
Życie: nigdy nie zaczynaj dnia pracy od suszonych owoców.

Jak już ktoś przebrniesz przez moje propozycje, dorzuć coś od siebie.
Wstyd mi za tego posta, ale musze.

test pass

Tutaj na zesłaniu poznałem dziwną i niezwykle innowacyjną sztukę testowania, właściwie to chodzi o podejście jakiego się używa podczas fazy testowania. Framework z którego korzystam, które jest autorstwa firmy z którą aktualnie współpracuje, nie zawsze nadąża za aplikacją która jest testowana, szczególnie na maszynach z więcej niż jednym rdzeniem. W związku z tym, stosuje się sztuczki, np. zwiększenie priorytetu dla aplikacji testującej, czy też odpowiednie ustawienie affinity [wikipedia] dla aplikacji testowanej i testującej plus dodatkowe ostrzeżenie od testera, który miał już z tym do czynienia „don’t touch anything now”. Szczerze mówiąc do tego momentu jestem jeszcze w stanie wszystko zrozumieć. Ale! Dodał o jedno stwierdzenie za dużo: „Jeżeli się nie uda, to odpal jeszcze raz i jeszcze raz, aż przejdzie”. WTF? Jakiej jakości będzie/jest ten kod? Czy testy się pisze z nadzieją, że może sie uda? Czy może po to żeby znaleźć i wyeliminować błędy? Czy jedna na dziesięć prób , która się powiedzie mówi że wszystko jest ok, czy jedna nieudana na dziesięć udanych mówi, że powinieneś się zastanowić, bo chyba czegoś zapomniałeś w kodzie zrobić jak należy.
A ty po ilu testach się uśmiechniesz?