Testowanie klas abstrakcyjnych

Pewnie każdy na swojej ścieżce programistycznej spotkał się z klasą abstrakcyjną. Wrzucamy tam kod, który zdaje się być domyślną implementacją pewnej grupy klas i szkoda nam kopiować tego zachowania do każdej z nich osobna. Skoro wszystkie zachowują się podobny sposób, czasem tylko dodając coś od siebie, to warto wykorzystać dziedziczenie i napisać mniej (DRY). Czyli prawdopodobny wydaje się taki scenariusz:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
max-height: 500px;
overflow: auto;
/*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 AbstractTesting
   2:  {
   3:      public abstract class AbstractClass
   4:      {
   5:          public virtual int Method(int input)
   6:          {
   7:              switch (input)
   8:              {
   9:                  case 1:
  10:                      return 1;
  11:                  case 2:
  12:                      return 4;
  13:                  case 3:
  14:                      return 8;
  15:                  default:
  16:                      return 0;
  17:              }
  18:          }
  19:      }
  20:   
  21:      public class ConcreteClass1 : AbstractClass
  22:      {
  23:          public override int Method(int input)
  24:          {
  25:              switch (input)
  26:              {
  27:                  case 1:
  28:                      return -1;
  29:                  default:
  30:                      return base.Method(input);
  31:              }
  32:   
  33:          }
  34:      }
  35:   
  36:      public class ConcreteClass2 : AbstractClass
  37:      {
  38:          public override int Method(int i)
  39:          {
  40:              switch (i)
  41:              {
  42:                  case 4:
  43:                      return 16;
  44:                  default:
  45:                      return base.Method(i);
  46:   
  47:              }
  48:          }
  49:      }
  50:   
  51:      public class ConcreteClass3 : AbstractClass
  52:      {
  53:      }
  54:   
  55:      public class ConcreteClass4 : AbstractClass
  56:      {
  57:          public override int Method(int i)
  58:          {
  59:              switch (i)
  60:              {
  61:                  case 1:
  62:                      return -1;
  63:                  case 2:
  64:                      return -2;
  65:                  case 3:
  66:                      return -3;
  67:                  default:
  68:                      return base.Method(i);
  69:              }
  70:          }
  71:      }
  72:   
  73:      public class ConcreteClass5 : AbstractClass
  74:      {
  75:          public override int Method(int i)
  76:          {
  77:              switch (i)
  78:              {
  79:                  case 1:
  80:                      return -1;
  81:                  case 2:
  82:                      return -4;
  83:                  case 3:
  84:                      return -8;
  85:                  default:
  86:                      return 1;
  87:              }
  88:          }
  89:      }
  90:  }

Celowo pomijam dodatkowe metody w klasie bazowej i klasach pochodnych, nie na tym chce się tutaj skupić.
Teraz można zadać sobie pytanie, jak mam to przetestować? Przynajmniej ja je sobie zadawałem. Czy testować w każdej klasie tylko to co wnosi ona nowego, a dla klasy klasy bazowej napisać specjalnego mocka, który umożliwi stworzenie jej instancji, a następnie przetestowanie wspólnego kodu. Czy też pominąć w testach klasę bazową, ale dla każdej z klas dziedziczących pisać powtarzający się test, sprawdzający wszystkie warunki switcha? Jest też trzecie rozwiązanie tego problemu, sugerując się podpowiedziami kolegów z pracy, trzeba być wystarczająco odważnym, wrzucić na produkcję i zobaczyć czy zabangla. Ale tego przykładu nie będę tutaj analizować.

Zamiast rozwodzić się nad tym, co jest lepsze, a co gorsze, sprawdzimy jak to wychodzi w praniu. Pierwsze podejście, napisać mniej testów:

   1:  [TestFixture]
   2:  public class AbstractTestClass
   3:  {
   4:      [Test]
   5:      public void t0_1()
   6:      {
   7:          var sut = new MockAbstractClass();
   8:          Assert.AreEqual(1, sut.Method(1));
   9:      }
  10:   
  11:      [Test]
  12:      public void t1_4()
  13:      {
  14:          var sut = new MockAbstractClass();
  15:          Assert.AreEqual(4, sut.Method(2));
  16:      }
  17:   
  18:      [Test]
  19:      public void t3_8()
  20:      {
  21:          var sut = new MockAbstractClass();
  22:          Assert.AreEqual(8, sut.Method(3));
  23:      }
  24:   
  25:      [Test]
  26:      public void t4_0()
  27:      {
  28:          var sut = new MockAbstractClass();
  29:          Assert.AreEqual(0, sut.Method(4));
  30:      }
  31:  }
  32:   
  33:  public class MockAbstractClass : AbstractClass
  34:  {
  35:  }
  36:   
  37:  [TestFixture]
  38:  public class TestC1
  39:  {
  40:      [Test]
  41:      public void t0_1()
  42:      {
  43:          var sut = new ConcreteClass1();
  44:          Assert.AreEqual(-1, sut.Method(1));
  45:      }
  46:  }
  47:   
  48:  [TestFixture]
  49:  public class TestC2
  50:  {
  51:      [Test]
  52:      public void t0_16()
  53:      {
  54:          var sut = new ConcreteClass2();
  55:          Assert.AreEqual(16, sut.Method(4));
  56:      }
  57:  }
  58:   
  59:  [TestFixture]
  60:  public class TestC4
  61:  {
  62:      [Test]
  63:      public void t1_m1()
  64:      {
  65:          var sut = new ConcreteClass4();
  66:          Assert.AreEqual(-1, sut.Method(1));
  67:      }
  68:   
  69:      [Test]
  70:      public void t2_m2()
  71:      {
  72:          var sut = new ConcreteClass4();
  73:          Assert.AreEqual(-2, sut.Method(2));
  74:      }
  75:   
  76:      [Test]
  77:      public void t3_m3()
  78:      {
  79:          var sut = new ConcreteClass4();
  80:          Assert.AreEqual(-3, sut.Method(3));
  81:      }
  82:   
  83:      [Test]
  84:      public void t4_0()
  85:      {
  86:          var sut = new ConcreteClass4();
  87:          Assert.AreEqual(0, sut.Method(4));
  88:      }
  89:  }
  90:   
  91:  [TestFixture]
  92:  public class TestC5
  93:  {
  94:      [Test]
  95:      public void t1_m1()
  96:      {
  97:          var sut = new ConcreteClass5();
  98:          Assert.AreEqual(-1, sut.Method(1));
  99:      }
 100:   
 101:      [Test]
 102:      public void t2_m2()
 103:      {
 104:          var sut = new ConcreteClass5();
 105:          Assert.AreEqual(-4, sut.Method(2));
 106:      }
 107:   
 108:      [Test]
 109:      public void t3_m3()
 110:      {
 111:          var sut = new ConcreteClass5();
 112:          Assert.AreEqual(-8, sut.Method(3));
 113:      }
 114:   
 115:      [Test]
 116:      public void t4_0()
 117:      {
 118:          var sut = new ConcreteClass5();
 119:          Assert.AreEqual(1, sut.Method(16));
 120:      }
 121:  }

Pokrycie kodu powinno być 100%. Wszystkie test powinny przejść na zielono. Skoro wszystko działa dobrze, to po co się zastanawiać na innym rozwiązaniem? A to dlatego, drogie dzieciaczki, że jeśli wprowadzicie teraz jakąś zmianę w klasie Abstract, to wszystkie testy klas pochodnych nadal będą się świecić na zielono. Pomimo tego, że zmieni się ich zachowanie. W ten sposób nie dowiecie się (z testów), że np. klasa Concret3 od teraz zachowuje się zupełnie inaczej, inne korzystające z domyślnego zachowania też oszalały. Ktoś może powiedzieć, że to poprawne zachowanie, skoro dziedziczysz po czymś/kimś i zdajesz się na jego domyślną implementacje to zmieniasz się razem z nią jak chorągiewka na wietrze. Tylko czy o to chodzi? Czy klasy nie powinny mieć własnego rozumku? Klient korzysta z implementacji klasy 1,2,3,4 czy 5 a nie klasy podstawowej. W takiej sytuacji każda klasa, a w zasadzie test dla każdej z klas powinien wykryć zmiany w jej zachowaniu, poinformować o tym, że zostało ono zmienione i od teraz nie spełnia wcześniej przyjętego rozumowania. Moim zdaniem tak powinny wyglądać testy:

   1:  namespace AbstractTesting
   2:  {
   3:      [TestFixture]
   4:      public class ConcreteClass1Test
   5:      {
   6:          [Test]
   7:          public void T001_M1()
   8:          {
   9:              var sut = new ConcreteClass1();
  10:   
  11:              var result = sut.Method(1);
  12:   
  13:              Assert.AreEqual(-1, result);
  14:          }
  15:   
  16:          [Test]
  17:          public void T002_M2()
  18:          {
  19:              var sut = new ConcreteClass1();
  20:   
  21:              var result = sut.Method(2);
  22:   
  23:              Assert.AreEqual(4, result);
  24:          }
  25:   
  26:          [Test]
  27:          public void T003_M3()
  28:          {
  29:              var sut = new ConcreteClass1();
  30:   
  31:              var result = sut.Method(3);
  32:   
  33:              Assert.AreEqual(8, result);
  34:          }
  35:   
  36:          [Test]
  37:          public void T004_M155()
  38:          {
  39:              var sut = new ConcreteClass1();
  40:   
  41:              var result = sut.Method(155);
  42:   
  43:              Assert.AreEqual(0, result);
  44:          }
  45:      }
  46:   
  47:      [TestFixture]
  48:      public class ConcreteClass2Test
  49:      {
  50:          [Test]
  51:          public void T001_M1()
  52:          {
  53:              var sut = new ConcreteClass2();
  54:   
  55:              var result = sut.Method(1);
  56:   
  57:              Assert.AreEqual(1, result);
  58:          }
  59:   
  60:          [Test]
  61:          public void T002_M2()
  62:          {
  63:              var sut = new ConcreteClass2();
  64:   
  65:              var result = sut.Method(2);
  66:   
  67:              Assert.AreEqual(4, result);
  68:          }
  69:   
  70:          [Test]
  71:          public void T003_M3()
  72:          {
  73:              var sut = new ConcreteClass2();
  74:   
  75:              var result = sut.Method(3);
  76:   
  77:              Assert.AreEqual(8, result);
  78:          }
  79:   
  80:          [Test]
  81:          public void T004_M4()
  82:          {
  83:              var sut = new ConcreteClass2();
  84:   
  85:              var result = sut.Method(4);
  86:   
  87:              Assert.AreEqual(16, result);
  88:          }
  89:   
  90:          [Test]
  91:          public void T005_M55()
  92:          {
  93:              var sut = new ConcreteClass2();
  94:   
  95:              var result = sut.Method(55);
  96:   
  97:              Assert.AreEqual(0, result);
  98:          }
  99:      }
 100:   
 101:      [TestFixture]
 102:      public class ConcreteClass3Test
 103:      {
 104:          [Test]
 105:          public void T001_M1()
 106:          {
 107:              var sut = new ConcreteClass3();
 108:   
 109:              var result = sut.Method(1);
 110:   
 111:              Assert.AreEqual(1, result);
 112:          }
 113:   
 114:          [Test]
 115:          public void T002_M2()
 116:          {
 117:              var sut = new ConcreteClass3();
 118:   
 119:              var result = sut.Method(2);
 120:   
 121:              Assert.AreEqual(4, result);
 122:          }
 123:   
 124:          [Test]
 125:          public void T003_M3()
 126:          {
 127:              var sut = new ConcreteClass3();
 128:   
 129:              var result = sut.Method(3);
 130:   
 131:              Assert.AreEqual(8, result);
 132:          }
 133:   
 134:          [Test]
 135:          public void T004_M55()
 136:          {
 137:              var sut = new ConcreteClass3();
 138:   
 139:              var result = sut.Method(55);
 140:   
 141:              Assert.AreEqual(0, result);
 142:          }
 143:      }
 144:   
 145:      [TestFixture]
 146:      public class ConcreteClass4Test
 147:      {
 148:          [Test]
 149:          public void T001_M1()
 150:          {
 151:              var sut = new ConcreteClass4();
 152:   
 153:              var result = sut.Method(1);
 154:   
 155:              Assert.AreEqual(-1, result);
 156:          }
 157:   
 158:          [Test]
 159:          public void T002_M2()
 160:          {
 161:              var sut = new ConcreteClass4();
 162:   
 163:              var result = sut.Method(2);
 164:   
 165:              Assert.AreEqual(-2, result);
 166:          }
 167:   
 168:          [Test]
 169:          public void T003_M3()
 170:          {
 171:              var sut = new ConcreteClass4();
 172:   
 173:              var result = sut.Method(3);
 174:   
 175:              Assert.AreEqual(-3, result);
 176:          }
 177:   
 178:          [Test]
 179:          public void T004_M55()
 180:          {
 181:              var sut = new ConcreteClass4();
 182:   
 183:              var result = sut.Method(55);
 184:   
 185:              Assert.AreEqual(0, result);
 186:          }
 187:      }
 188:   
 189:      [TestFixture]
 190:      public class ConcreteClass5Test
 191:      {
 192:          [Test]
 193:          public void T001_M1()
 194:          {
 195:              var sut = new ConcreteClass5();
 196:   
 197:              var result = sut.Method(1);
 198:   
 199:              Assert.AreEqual(-1, result);
 200:          }
 201:   
 202:          [Test]
 203:          public void T002_M2()
 204:          {
 205:              var sut = new ConcreteClass5();
 206:   
 207:              var result = sut.Method(2);
 208:   
 209:              Assert.AreEqual(-4, result);
 210:          }
 211:   
 212:          [Test]
 213:          public void T003_M3()
 214:          {
 215:              var sut = new ConcreteClass5();
 216:   
 217:              var result = sut.Method(3);
 218:   
 219:              Assert.AreEqual(-8, result);
 220:          }
 221:   
 222:          [Test]
 223:          public void T004_M55()
 224:          {
 225:              var sut = new ConcreteClass5();
 226:   
 227:              var result = sut.Method(55);
 228:   
 229:              Assert.AreEqual(1, result);
 230:          }
 231:      }
 232:  }

W tym przypadku gdy zmieni się zachowanie klasy podstawowej, wszystkie testy klas korzystających z domyślnej implementacji, automatycznie zgłoszą niezadowolenie ze zmian.
Nic za darmo, w drugim przypadku trzeba napisać więcej testów, a czasem nawet go powtórzyć. Oczywiście można też pozbyć się tego switcha z kodu, albo wyjechać do Niemiec i zbierać ogórki – byle by tylko nie pisać testów. Tutaj miałem na celu pokazanie tego, jak zachowują się oba rozwiązania i które z nich, moim zdaniem jest tym słuszniejszym.

Jak zwykle pozostaje otwarty na krytykę i zastrzegam sobie prawo do popełniania błędów.

Czym się różni właściwość klasy od pola klasy?

Wracając jeszcze do filozofowania o kodzie, dziś trochę o cechach klas, a dokładniej: pola i właściwości. Zanim wymyślono idee właściwości dostęp do pól odbywał się na dwa sposoby. Pole w klasie (np. name) mogło być publiczne i każdy miotał nim jak szatan, druga opcja to dostęp kontrolowany przez parę metod typu GetName i SetName. Umożliwiały one kontrolowanie tego kto i na jakich zasadach może korzystać z cech wewnętrznych klasy.
Później, aby pominąć pisanie GetName/SetName, wymyślono właściwości, które w sprytny sposób, miały pozbyć się z kodu klienta getów i setów, czyniąc kod bardziej czytelnym. Jednocześnie pozostawić możliwość kontrolowanie kto i jak ma dostęp do pola, poprzez “ukrytą” implementację tychże get i set. Wygląda więc to tak, że klient korzysta z naszego pola w klasie, które naprawdę jest jest właściwością opakowującą to pole. Zresztą każdy wie jak wygląda i działa właściwość w C#.
Teraz czas na właściwą filozofię, czym się różni właściwość klasy od pola w klasie?

  1. Właściwość może zostać tak zaimplementowana, aby była tylko do zapisu lub tylko do odczytu. Pole w klasie jest najczęściej implementowane czytalne/pisalne (uwielbiam nowo mowę). Czasem tworzy się stałe. (ot czasami tak trzeba).
  2. Właściwość podczas zapisu lub odczytu może nas poczęstować wybornym wyjątkiem, ta sama operacja wykonana na polu nigdy.
  3. Właściwość nie może zostać przekazana jako ref lub out argument metody, pole klasy tak.
  4. Czas wykonania właściwości może być czasem bardzo długi, np. podczas wykonywania synchronizacji wątków, leniwej inicjalizacji właściwości, etc. Dostęp do pola zawsze jest operacją natychmiastową.
  5. Dostęp do właściwości może powodować efekty uboczne, np. w cache, każdy dostęp może zmniejszać czas życia keszu. Dostęp do pola nigdy nie ma takiej możliwości.
  6. Odczytanie właściwości klasy czasem może się wymagać zwiększania zapotrzebowania na pamięć RAM, potrzebę na przeliczenie pewnych zależności, czy wykonanie dodatkowych operacji, może także zwrócić nie pełny wynik. Dostęp do pola, nie wymaga dodatkowych środków, oraz zwraca oryginalną zawartość tego pola.

Nasuwa się pytanie, czy wy będąc klientem klasy X, która oferuje dostęp do właściwości, oczekujecie, że dostęp do właściwości Name będzie:

  • trwał strasznie długo
  • każdy operacja odczytu zmienia, zwraca inną wartość
  • dostaniecie tylko spółgłoski
  • zanim dostaniecie wynik, zużycie pamięci wzrośnie o kilkanaście mega

Takich rzeczy można oczekiwać od metod, to one powinny robić coś skomplikowanego, dostęp do pola powinien być możliwie szybki i prosty. Warto o tym pamiętać, pisząc swój kawałek kodu. Może trzeba wrócić do poczciwego GetName(), GetNameConsonantsOnly(),
GetNameVowelsOnly(), GetNameUsing5MbRamMore() ?

Ot takie tam żywcem zerżnięte informacje z książki którą czytam i pomyślałem, że warto o tym napisać.

ps.
DateTime.Now 😉

Przyjemne funkcje

Jak przyjmować i jak zwracać kulturalnie – zastanawialiście się kiedyś na tym? Taki programistyczny savoir-vivre. Jak to zrobić, żeby mi (programiście) było wygodnie, a jednocześnie uszcześliwić przyszłego użytkownika API które tworzymy? Przecież to może być właśnie ja (ja piszący tego bloga)!

Sprzedam wam dwie proste reguły (na bank są inne o których nie wiem), które warto zapamiętać lub przynajmniej sie nad nimi zastanowić.

Przyjmowany parametr powinien być możliwie wysoko w drzewie dziedziczenia, możliwie najogólnieszy, z którego możemy skorzystać. Jak zawsze będę się wspierać na przejaskrawionych przykładach, zupełnie oderwanych od rzeczywistości:

.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:  int NotSoNiceSumFunction(List<int> arg)
   2:  {
   3:      int result = 0;
   4:      foreach (var i in arg)
   5:      {
   6:          result += i;
   7:      }
   8:      return result;
   9:  }

Oraz drugi przyklad:

   1:  int NiceSumFunction(IEnumerable<int> arg)
   2:  {
   3:      int result = 0;
   4:      foreach (var i in arg)
   5:      {
   6:          result += i;
   7:      }
   8:      return result;
   9:  }

Zasadniczo nie różnią się one od siebie poza typem arugumentu. Za pierwszym razem określamy go bardzo dokładnie: pracujemy tylko z listą intów. Podczas gdy w seksownym ciele funkcji, nie ma nigdzie zachowania, które mogłoby uzasadnić potrzebę używania listy. Za drugim razem, wymagamy tylko aby argument implementował interfejs IEnumerable.
Jaki jest zysk z takiego pozytywnego nastawienia?

   1:  void Test(string[] args)
   2:  {
   3:      List<int> input1 = new List<int>();
   4:      IList<int> input2 = new List<int>();
   5:      ICollection<int> input3 = new List<int>();
   6:      IEnumerable<int> input4 = new List<int>();
   7:      int[] input5 = new int[100];
   8:   
   9:      NotSoNiceSumFunction(input1); // :)
  10:      NotSoNiceSumFunction(input2); // :(
  11:      NotSoNiceSumFunction(input3); // :(
  12:      NotSoNiceSumFunction(input4); // :(
  13:      NotSoNiceSumFunction(input5); // :(
  14:   
  15:      NiceSumFunction(input1); // :)
  16:      NiceSumFunction(input2); // :)
  17:      NiceSumFunction(input3); // :)
  18:      NiceSumFunction(input4); // :)
  19:      NiceSumFunction(input5); // :)
  20:  }

Powyżej przykładowy kod z różnymi kontenerami dla intów. Które linijki kodu nie będą chciały banglać? Mój kompilator nie poradził sobie z linijkami 10,11,12,13. Czyli tak jak powiedzieliśmy podczas definiowania funkcji NotSoNiceSumFunction – działamy tylko i wyłącznie z listą intów. Podczas gdy NiceSumFunction poradził sobie z każdym rodzajem parametru. Chyba nie muszę pisać z kim będzie się lepiej współpracowało?

Teraz w drugą stronę, jest juz po wszystkim, wiatraczek wyje, procek gorący, mamy wynik. Jak go przekazać stronie zainteresowanej? Zwrócić jako ogólny typ, czy dokładnie określić co nam wyszło? Myśl, myśl, myśl.

Znowu kod dla przykładu:

   1:  List<int>  GetAllItemsAsList()
   2:  {
   3:      List<int> items = new List<int>();
   4:      // magic goes here
   5:      return items;
   6:  }

Wersja mniej szczegółowa:

   1:  static IEnumerable<int>  GetAllItemsAsEnum()
   2:  {
   3:      IEnumerable<int> items = new List<int>();
   4:      // magic goes here
   5:      return items;
   6:  }

Na którą wersję się decydujecie? Ja przyznam się szczerze, że do tej pory wynik zwracałem zawsze przez IEnumerable. Ale już tak nie robię.
Jak może wyglądać wykorzystanie metod ze strony klienta na przykładach:

   1:  void Test(string[] args)
   2:  {
   3:      List<int> result1 = null;
   4:      IList<int> result2 = null;
   5:      ICollection<int> result3 = null;
   6:      IEnumerable<int> result4 = null;
   7:      int[] result5 = null;
   8:   
   9:   
  10:      result1 = GetAllItemsAsList(); // :)
  11:      result2 = GetAllItemsAsList(); // :)
  12:      result3 = GetAllItemsAsList(); // :)
  13:      result4 = GetAllItemsAsList(); // :)
  14:      result5 = GetAllItemsAsList(); // :(
  15:                                    
  16:      result1 = GetAllItemsAsEnum(); // :(
  17:      result2 = GetAllItemsAsEnum(); // :(
  18:      result3 = GetAllItemsAsEnum(); // :(
  19:      result4 = GetAllItemsAsEnum(); // :)
  20:      result5 = GetAllItemsAsEnum(); // :(
  21:  }

W tym przypadku widać, że klient będzie bardziej szczęśliwy jeśli dokładnie określimy wynik naszej pracy, a decyzję o tym co z nim zrobić zostawimy końcowemu odbiorcy. W przypadku zwracania jako enum, klient będzie musiał przyjąć IEnumerable, a następnie samodzielnie przekształcić to, w razie potrzeby, w klasą bardziej wyspecjalizowaną.

Czyli co, bądźmy mili i proaktywni, przyjmujmy wszystko z czym możemy sobie poradzić. A zwracając wynik, zadbajmy o szczegółową informację o tym, czym jest wynik i jak z niego korzystać, klient sam podejmie najlepszą decyzję czy wykorzysta pełny potencjał naszej ciężkiej pracy, czy potrakutje nas po między mordziu.

Pozdrawiam z IE8 i czekam na riposty.

PRISM – materiały do nauki.

Jestem zafascynowany framworkiem PRISM, czytam, oglądam i piszę jakieś przykładowe aplikacje z wykorzystaniem tej biblioteki. Prism ułatwia tworzenia modularnych i dynamicznych systemów, dostarcza mechanizm do rozwiązywania zależności (Unity Container), umożliwia dynamiczną zmianę zachowania aplikacji poprzez wczytywanie dodatkowych modułów z katalogu lub aktualizację pliku konfiguracyjnego aplikacji – to tylko część z błyszczących bajerów.
Prism wspiera Silverlight, WPF czy Windows Phone 7 oraz mocno promuje wzorzec MVVM. Aby skorzystać z Prism wystarczy zaciągnąć paczkę z codeplex.com, rozpakować oraz dodać referencje do projektu.

Polecam dwa źródła do nauki: dla posiadających dostęp do płatnych szkoleń na Pluralsight taki oto kurs – Introduction to PRISM . Jeśli nie masz dostępu, nie masz się czym martwić na Channel9 jest inny równie dobry, oraz dwu i pół godzinny film akcji, gdzie autor tworzy prostą aplikację do odbierania i wysyłania poczty elektronicznej – Prism & Silverlight. Pierwszy z materiałów jest oparty na WPF, drugi to aplikacja webowa pisana w Silverlight.

Moje proste działania można obserwować na bitbucket.org pod adresem:
https://bitbucket.org/jstadnicki/prismcapture oraz link do repozytorium git https://jstadnicki@bitbucket.org/jstadnicki/prismcapture.git

Konfiguracja nhibernate

Nie taki straszny ten nhibernate jak się początkowo wydaje.
Wiem bo sam sprawdziłem, na początku myślałem, znowu wszystko w xml, nigdy nie wiadomo co i gdzie wpisać, … Nie tym razem, wystarczy obejrzeć sobie ten prosty wstęp i okazuje się, że podłączenie nh do lokalnego pliku z sqlce jest proste.
Chcesz korzystać z postgresql jako bazy danych, nic skomplikowanego. Zacznij od ściągnięcia paczki nhibernate. Następnie rozpakuj na dysk, a w środku znajdź katalog o nazwie “Configuration_Templates”, w nim są przykładowe pliku konfiguracyjne dla różnych baz, które są wspierane przez NH, w tym także postgresql.
Dla pełnego działania nh oraz psql wymagane jest także posiadanie npgsql – data provider.
Można zaciągnąć go z oficjalnej strony lub wykorzystując Application Stack Builder z paczki instalacyjnej do PostgreSQL. (Swoją drogą pewnie za pomocą nugeta też się da)
Należy wybrać wersje (jeśli mamy zainstalowanych kilka) psql a następnie driver, który nas interesuje:

Jak widać ja już mam zainstalowane to czego potrzebowałem. Teraz wystarczy już tylko dodać referencję do .dll i można działać.

Jeszcze tylko żeby zapamiętać, pliku konfiguracyjne dla nhibernate oznaczyłem jako content oraz copy always. Natomiast pliki odpowiadające za mapowanie obiektów jako embedded resource.
W moim przypadku konfiguracja zawiera dane użytkownika oraz hasło i nie może być w taki sposób wykorzystane w profesjonalnym rozwiązaniu, ale na potrzeby nauki oraz czytelności kodu jest jak jest.

Ostatnie żeby pamiętać, nh dostarcza schemat dla xml, aby mieć podpowiadanie składni w VS należy dodać pliki .xsd do projektu, albo wrzucić je do “X:PROGRAM_FILESMicrosoft Visual Studio VERSIONXmlSchemas”. Odpowiednio podmieniając X, PROGRAM_FILES oraz VERSION 😉

Jeśli ktoś chce może skorzystać z moich małych projektów, stworzonych na podstawie tutoriala z początku wpisu. Zawiera on konfiguracja dla mssql oraz pgsql i znajduje się tutaj lub można sklonować to https://bitbucket.org/jstadnicki/nhibernate-examples.git repozytorium.

Więcej nie wiem. Ale się nauczę.

ps.
W zasadzie skoro w tytule jest konfiguracja nhibernate to czemu nie wrzucić jest od razu tutaj:
MSSQL:

.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:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <hibernate-configuration 
   3:      xmlns="urn:nhibernate-configuration-2.2">
   4:      <session-factory>
   5:          <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
   6:          <property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
   7:          <property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
   8:          <property name="connection.connection_string">Data Source=FirstSample.sdf</property>
   9:          <property name="show_sql">true</property>
  10:      </session-factory>    
  11:  </hibernate-configuration>

Linia 9 jest opcjonalna, powoduje że zapytania generowane przez NH są wypluwane na konsole.

PostgreSQL:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <hibernate-configuration
   3:      xmlns="urn:nhibernate-configuration-2.2">
   4:      <session-factory name="NHibernate.Test">
   5:          <property name="connection.driver_class">NHibernate.Driver.NpgsqlDriver</property>
   6:          <property name="connection.connection_string">
   7:              Server=localhost;Port=12345;Database=Products;User ID=postgres;Password=1234567890;
   8:          </property>
   9:          <property name="dialect">NHibernate.Dialect.PostgreSQL82Dialect</property>
  10:          <property name="show_sql">True</property>
  11:      </session-factory>    
  12:  </hibernate-configuration>

Jak widać, w tym przypadku czarno na białawym podane jest hasło oraz nazwa użytkownika do bazy. TAK NIE MOŻE SIĘ DZIAĆ.