ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod Krzysztof Manuszewski.

1 ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod Kr...
Author: Adam Nowakowski
0 downloads 1 Views

1 ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod Krzysztof Manuszewski

2 Agenda Elementy testu jednostkowego Czym różnią się złe testy od dobrych Testowanie stanu / zachowania - obiekty Namiastek (Mock-i) Strategia tworzenia testów Jak pisac kod aby był łatwo testowalny Zasady SOLID

3 Nowa agenda Zasady SOLID czyli jak pisać dobry kod Elementy testu jednostkowego Czym różnią się złe testy od dobrych Testowanie stanu / zachowania - obiekty Namiastek (Mock-i) Strategia tworzenia testów Jak pisac kod aby był łatwo testowalny Zasady SOLID

4 Nowa agenda Zasady SOLID czyli jak pisać dobry kod Elementy testu jednostkowego Czym różnią się złe testy od dobrych Testowanie stanu / zachowania - obiekty Namiastek (Mock-i) Strategia tworzenia testów

5 Trudno jest testować zły kod...

6 ZŁY KOD WYGLĄDA TAK...

7 ... jest „sztywny” i „delikatny”  Nowe błedy pojawiają się w obszarach, które zdają sie być niezwiazane ze zmienianą funkcjonalnoscią  Pozornie drobne zmiany indukują poważne zmiany w wielu miejscach kodu i/lub skutkuja trudnymi do przewidzenia błędami  Trudno przewidzieć wpływ nawet drobnych zmian  Trudno przewidzieć czas oraz koszty rozwoju projektu/poprawek  Zespół prorgramistów traci wiarygodność  Menedżerowie niechętnie godza się na zmiany

8 ... i trudny do „reużycia” Potrzebne elementy zależą od niepotrzebnych Ryzyko ekstrakcji potrzebnego kodu jest duże a koszt wiekszy niż napisanie opotrzebnej funkcjonalności od podstaw

9 Bezpośrednie źródła problemów Praca z cudzym kodem Pośpiech Zmiany, ciagłe zmiany Niedostateczna/niejasna specyfikacja... a może przyczyną jest nienajlepsza architektura kodu

10 Prosty przykład public class Copier { public static void Copy() { int c; while((c=Keyboard.Read()) != -1) Printer.Write(c); } kopiowanie znaków z klawiatury na drukarkę

11 ... drobna zmiana... public class Copier { public static bool rdFlag = false; public static void Copy() { int c; while((c=(rdFlag ? PaperTape.Read() : Keyboard.Read()) != -1) Printer.Write(c); } Z klawiatury lub z czytnika taśmy

12 ..i następna... public class Copier { public static bool rdFlag = false; public static bool ptFlag = false; public static void Copy() { int c; while((c=(rdFlag ? PaperTape.Read() : Keyboard.Read()) != -1) if (ptFlag) Screen.Write(c); else Printer.Write(c); } Na drukarke lub ekran

13 ...i już nie jest prosty... Więcej źródeł i ujść danych Obsługa błędów I/O errors Przekodowywanie znaków Logowanie przekodowanych znaków do pliku Zmiana formatu tekstu w oparciu o kontekst (np justowanie tekstu

14 Wymagania się zmieniają... Zawsze lub przynajmniej czasami zwłaszcza w kontekście iteracyjnej realizacji projektu

15 public class KeyboardReader : { public int Read() { return Keyboard.Read(); } } public class PrinterWriter : { public Write(int c) { return Printer.Write(c); } } public class Copier { public static KeyboardReader reader = new KeyboardReader(); public static PrinterWriter writer = new PrinterWriter(); public static void Copy() { int c; while((c=(reader.Read())) != -1) writer.Write(c); }... być gotowym na zmiany

16 ... to podzielić odpowiedzialność Wyraźna separcja kodu: twórcy klas Printer i Writer nie muszą znać ani rozumieć logiki kopiowania ale: Zmiany w sposobie obsługi drukarki/klawiatury:  wymuszają zmiany w klasie kopier (typy atrybutów)  wymuszają rekompilację klasy „Copier” Implementujący klasę Copier musi znać i umieć tworzyć obiekty klas Printer, KeyboardReader Klasa Screen musi dziedziczyć po Printer (choć nie ma nic wspólnego z drukarką)

17 LEPSZYKOD WYGLĄDA TAK...

18 public interface IReader { public int Read() ; } public class KeyboardReader : IReader{ public int Read() { return Keyboard.Read(); } } public class Copier { public static IReader reader = new KeyboardReader(); public static IWriter writer = new PrinterWriter(); public static void Copy() { int c; while((c=(reader.Read())) != -1) writer.Write(c); }

19 public class Copier { IReader reader; IWriter writer; public Copier (IReader newReader, IWriter writer) { reader = newReader; writer = newWriter; } public static void Copy() {... }

20 Dobra separacja kodu Klasa Copier nie zależy od Printer ani od Reader Klasy Printer ani Reader nie zależą od Copier Wszystkie klasy (usługobiorcy i usługodawcy zależą od interfejsu) Zmiany interfejs-u sa jedynym powodem do zmian w większych obszarach kodu Interfejs stanowi specyfikację kontraktu pomiędzy 2 stronami – klientem i dostarczycielem pewnej funkcjonalności

21 S.O.L.I.D. - ny kod SRP: The Single Responsibility Principle OCP: The Open/Close Principle LSP: The Liskov Substitution Principle ISP: The Interface Segregation Principle DIP: The Dependency Inversion Principle

22 (SRP) Single-Responsibility Principle Klasa powinna mieć pojedynczy powód do zmian. Klasa Printer jest odpowiedzialna za pisanie na drukarkę Klasa Copier jest odpowiedzialna za proces kopiowania

23 SRP - przykład problemów 2 odpowiedzialności razem – zarządzanie połaczeniem i transmisja danych Jeżeli klienci korzystają z nich oddzielnie (prawdopodobne) zmiany w sygnaturze Dial wywołają konieczność rekompilacji klientów, którzy korzystają jedynie z Send/Recv public interface Modem { public void Dial(string pno); public void Hangup(); public void Send(char c); public char Recv(); }

24 SRP - przykład problemów Nie zmusza do realizacji obu funkcjonalności przez jedną klasę – chociaz dalej jest to mozliwe

25 SRP - przykład problemów Zmiany dowolnego z 3 aspektów oznaczają zmiany w klasie Employee Łatwo wprowadzić błąd do pobocznej funkcjonalności. Testować trzeba cała klasę. Pożądane jest ograniczenie funkcjonalności w obrębie zmienianej klasy

26 SRP - Możliwe rozwiązanie

27 Moment! Obiekty powinny hermetyzować swoją zawartość Czy obiekty powinny mieć wiedzę:  Jak zapisać samego siebie?  Jak raportować swój stan? To nie jest tak istotne!  Filozofia, która kryje się za OO nie jest w tym wypadku tak istotna  Podstawowym celem jest ograniczenie propagacji zmian w systemie  System ma być łatwy w utrzymaniu i modularny!

28 (OCP) Open/Close Principle Jednostki programowe (klasy, moduły, funkcje, itd.) powinny być otwarte na rozszerzenia a zamknięte na zmiany Rober C.C Martin Do kopiarki mogą być łatwo dodawane nowe typy czytników/pisarzy bez zmian (no prawie bez) w klasie Copier Ale „bez zmian” oznacza też, że klasa Copier nie powinna sama tworzyć obiektów, z których korzysta -> rozwiązywanie zależności

29 OCP - Otwartość na rozszerzanie Zapewniają np. wzorce projektowe: strategia metoda szablonowa Które rozwiązanie wybrać ?

30 Które rozwiazanie wybrać? Dziedziczenie wprowadza silniejsze zwiazki miedzy klasami:  Agregacja daje możliwość zmiany zachowania w czasie wykonania  Agregacje dają możliwość niezależnego określania zachowania w różnych obszaarch (niezaleznych strategii) Dziedziczenie interfejsu jest naturalne Dziedziczenie implementacji niekoniecznie Jeśli nie ma dodatkowych wskazówek agregacja może być lepszym rozwiazaniem!

31 OCP – co ze zmianami? Nie można zapobiec wszystkim zmianom Kluczowe jest rozpoznanie co może się zmieniać często lub co bedzie trudno zmienić Dodanie nowej funkcjonalności powinno być łatwe

32 (LCP) Liskov Substitution Principle Podklasy muszą być logicznie zgodne z typami bazowymi. TapeReader : KeyboardReader ??? Dziedziczenie oznacza „jest szczególnym przypadkiem”

33 public class Rectangle { private Point topLeft; private double width; private double height; public double Width { get { return width; } set { width = value; } } public double Height { get { return height; } set { height = value; } } LCP – Szkolny przykład (1)

34 Unit Testing public class Rectangle { private Point topLeft; private double width; private double height; public virtual double Width { get { return width; } set { width = value; } } public virtual double Height { get { return height; } set { height = value; } } public class Square : Rectangle { public override double Width { set { base.Width = value; base.Height = value; } public override double Height { set { base.Height = value; base.Width = value; } LCP – Szkolny przykład (2)

35 Unit Testing void foo (Rectangle r) { r.SetWidth(32); // calls Rectangle.SetWidth } Co bedzie gdy przekażemy obiekt typu Square? A poniżej ? void goo (Rectangle r) { r.Width = 5; r.Height = 4; if(r.Area() != 20) throw new Exception("Bad area!"); } Square nie zachowuje się jak szczególny przypadek prostokąta! LCP – Szkolny przykład (3)

36 (ISP) Interface Seggregation Principle Klasa nie powinna zależeć od tego, czego nie używa. Interfejs IReader powinien być oddzielny od IWriter. „Tłuste” klasy wprowadzają zwykle silne związki ze swoimi klientami Zmiana wymuszona przez jednego z klientów dotyka pozostałych

37 ISP - uwagi Należy unikać obszernych interfejsów  Interfejs (kontrakt) powinien być spójny  Interfejs (kontrakt) nie powinien być zbyt szeroki wszystkie implementujące go klasy będą musiały dostarczyć kompletu metod (np. WCF: 3-5-9)  Lepsze są wąskie, zorientowane na role interfejsy Jeżeli nie można uniknąć „tłustych” klas - te powinny być prezentowane klientom za pośrednictwem zbioru wąskich, zorientowanych na role interfejsów Oddzielni klienci mogą oznaczać oddzielne interfejsy Interfejsy moga dziedziczyc po sobie – można dostarczyć pojedynczą referencją do obiektu implementującego wiele interfejsów.

38 (DIP) Dependency-Inversion Principle 1. Wysokopoziomowe moduły nie powinny zależeć od nispopoziomowych (ani odwrotnie). Jedne i drugie powinny zależeć od abstrakcji (kontraktów). 2. Abstrakcje (kontrakty) nie powinny zależeć od szczegółów implementacyjnych. Implementacja powinna zależeć od abstrakcji (kontraktu). Hollywood principle: „Don‘t call us – we will call you” Copier i KeyboardReader zależą od interfejsu IReader, ale nie zależą od siebie Interfejs jest bardziej przejrzysty niż klasa

39 Unit Testing DIP - Przykład

40 Unit Testing DIP - podsumowanie Podstawa dobrego OOD Interfejsy należą do klientów Implementacja zmienia się często, interfejsy rzadko „Podejrzane” sytuacje:  Zmienna odwołuje się do nieabstrakcyjnej klasy  Klasa dziedziczy po nieabstrakcyjnej klasie  Metoda nadpisuje konkretną implementację z klasy bazowej

41 Zalecana lektura Robert C.C. Martin „Agile Principles, Patterns and Practices in C#” [przykłady są na tej podstawie] Robert C.C. Martin „Czysty kod" Warto zobaczyc screencast Martina z NDC 2009 „Robert C. Martin - Clean Design, SOLID Principles I and II.wmv”

42 Testy jednostkowe

43 W czym pomagają testy jednostkowe? Ułatwiają znajdowanie błedów Ułatwiają zrozumienie kodu Ułatwiają utrzymanie kodu Ułatwiają pisanie kodu

44 Testy jednostkowe Testy weryfikują na bieżąco konkretne aspekty zachowania klas. Złamanie założeń powoduje załamanie konkretnych testów. Przy dodawaniu/zmianach funkcjonalności testy chronią przed zepsuciem już zaimplemen- towanych funkcji. Stanowią dokumentację i zarazem przykłady użycia Kod powinień być pisany prosto. Działający kod można i należy udoskonalać. Aby to było bezpieczne potrzebne są testy.

45 Test jednostkowy Jest to automatyczny fragment kodu uruchamiający i weryfikujący poprawność wykonania pewnego aspektu kodu produkcyjnego Testy są pisane z wykorzystaniem framework-ów. Dzięki temu mogą być stworzone i uruchamiane szybko i łatwo: NUnit MSTest MBUnit Xunit Testy mogą być uruchamiane pojedynczo lub masowo przez każdego członka zespołu. Są częścią projektu ale nie są dostarczane do klientów.

46 Test jednostkowy – elementy (1) bool IsLoginOk(string user, string password); [TestFixture] Class TestClass { [Test] public void TestLogin() { LoginComponent sut = new LoginComponent (); bool result = sut.IsLoginOk("user","password"); Assert.IsFalse (result, "invalid user/password shouldn't be accepted"); }

47 NUnit Dedykowane GUI Wtyczki do VS:  R#  TestDriven.Net

48 Części testu czyli AAA Arrange – utwórz SUT, zainicjuj środowisko Act – wykonaj test Assert – sprawdź wyniki Przygotowanie może być realizowane Np. w SetUp

49 Test jednostkowy – elementy (2) [TestFixture] Class TestClass { LoginComponent sut; [SetUp] public void Init() { sut = new LoginComponent (); } [Test] public void TestLogin2() { var result = sut.IsLoginOk("Iksinski","realPassword"); Assert.IsTrue(result,"valid user/password should be acceprted"); }

50 Gdzie leży problem Kod produkcyjny powinien być dobrze przetestowany – to znaczy nie powinna udać się żadna zmiana kodu produkcyjnego bez wysadzenia jednego lub wielu testów Test powinien się jasno nazywać i powinien testować pojedynczy aspekt działania kodu to znaczy poszczególne zmiany w kodzie powinny "zapalać" jak najmniej testów Nie należy pisać niepotrzebnych testów Kod testów powinien zawierać jak najmniej powtórzeń – uwaga konstruktor (new osoba(...)) - to też powtórzenie Test powinien być zrozumiały

51 Dlaczego Kod produkcyjny powinien być dobrze przetestowany... Jeśli testy maja "dziury" – cały wysiłek nie ma sensu. Jeśli znajdujemy problem w kodzie produkcyjnym – dodajemy nowy test/testy a potem poprawiamy kod

52 Co testować Logikę. Instrukcje warunkowe, pętle itd. Testowanie prostych properties/funkcji mija się z celem. Publiczny interfejs. Jeżeli metody prywatne zawierają nietrywialna logikę może to znak, że klasa powinna zostać zrefaktoryzowana.  Np. samochód vs. silnik

53 Dlaczego Test powinien się jasno nazywać Testy powinny wskazywać na konkretne problemy - dobre nazwy zwalniają ze szczegółowych komunikatów przy asercja Niejasna nazwa przy tysiacach testów powoduje że musimy debugować/analizować kod Nazwa testu powinna dobrze lokalizować błąd. Najlepiej bez debugowania, bez analizy komunikatów. Jeśli mamy problem z nazwą testu bardzo prawdopodobne, że próbujemy przetestować zbyt wiele rzeczy na raz.

54 Jasno czyli jak ? Konwencje  LoginComponent_InvalidUser_ShuldThrowException  WhenUserIsInvalid. IsLoginOk_ShouldThrowException Trudno nazwać test, który dotyczy wiele aspektów zachowania klasy Przy dobrej nazwie komunikatów może nie być.

55 Dlaczego Test powinien testować pojedynczy aspekt działania kodu to znaczy poszczególne zmiany w kodzie powinny zapalać jak najmniej testów Test, który testuje wiele rzeczy bedzie częściej "padać" przy zmianach kodu. Test, który testuje wiele rzeczy jest skomplikowany Test, który testuje wiele rzeczy niewiele mówi w momencie upadku

56 Dlaczego Nie należy pisać niepotrzebnych testów Pisanie testów kosztuje. Utrzymanie testów kosztuje jeszcze więcej. Jeśli 2 testy padają zawsze razem – jeden jest niepotrzebny Jeśli test pada zawsze z jednym spośród kilku innych – jest niepotrzebny Pokusa by testować to co testują inne testy prowadzi do tego, że testy padają częściej niż by mogły

57 Dlaczego Kod testów powinien zawierać jak najmniej powtórzeń... Duplikacja w kodzie to ZŁO Jak jej unikać – wsólny kod inicjalizujący  Settup (ale nie tak ze jest jedne setup dla wszystkich testów – wolne i niejasne)  Hierarchia klas  Buildery danych testowych Pisać własne asserty

58 Dlaczego Test powinien być zrozumiały... Test stanowi dokumentacje Przed zmiana funkcjonalności należy zmienić test Nie lubie zmieniać tego czego nie rozumiem!!! Testy nie powinny zawierać logiki – jak testować testy? Jeśli test zawiera logikę należy ją wydzielić (np. do funkcji). Takie funkcje mozna przetestować. Testy można i należy refaktoryzować

59 Testy sterowane danymi Pojedynczy kod testu (parametryzowany) Test jest uruchamiany wielokrotnie dla różnych zestawów danych Dane dla testu mogą być umieszczone w kodzie lub brane z zewnętrznych źródeł (txt, xml, csv, xls, mdb itd.) UWAGA: to nie jest panaceum  Słaba diagnostyka  Tendencja do testowania kombinatorycznego

60 Testy sterowane danymi MSTest [TestClass] public class TestClass { [TestMethod] [DeploymentItem("FPNWIND.MDB")] [DataSource("System.Data.OleDb", "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=\"FPNWIND.MDB\"", "Employees", DataAccessMethod.Sequential)] public void TestMethod() { Console.WriteLine( "EmployeeID: {0}, LastName: {1}", TestContext.DataRow["EmployeeID"], TestContext.DataRow["LastName"] ); }

61 Testy sterowane danymi NUnit [TestCase(2.5d, 2d, Result=1.25d)] [TestCase(-2.5d, 1d, Result = -2.5d)] public double ValidateDivision(double numerator, double denominator) { var myClass = new MyClass(); return myClass.Divide(numerator,denominator); }

62 Dlaczego TDD/BDD Red/Green/Refactor Test = projekt/specyfikacja funkcjonalności Konkretny test odpowiada konkretnemu aspektowi działania klasy Test powinien weryfikować dany aspekt Test powinien dokumentować dany aspekt (być specyfikacją) Problem: należy sie skupić na danym aspekcie. Nie próbować implementować za dużo.

63 Jak pisać testy - podsumowanie Dobrej jakości testy nie wymagają intensywnej pielęgnacji. Projekty (agile) częściej padają nie z powodu braku ale z powodu złej jakości testów Kod nie może zawierać "hack-ów" (if test....) Test, który zawsze działa – nic nie testuje. Zawsze należy sprawdzić czy są przypadki gdy test zawodzi Typowy kod jest trudny do testowania. Testy dla istniejącego (i stabilnego kodu) mają umiarkowany sens (chyba że chcemy kod zmieniać)

64 Jak używać testów? Są często (stale?) uruchamiane podczas kodowania Są cyklicznie uruchamiane na serwerze buildów. Testy odzwierciedlają kontrakt pomiędzy użytkownikiem i dostarczycielem funkcjonalności Testy stanowią wyznacznik jakości architektury kodu -> testy mogą służyć tworzeniu dobrej architektury (TDD/BDD)

65 Testowanie zachowania Izolacja klas

66 Życie prywatne klasy Jeżeli testy wymagają dostępu do niepublicznych składników np. dla weryfikacji stanu (niepokojące...) :  Nie należy rozhermetyzować klasy  Można dodać klasę potomną dla potrzeb testu (składniki protected)  Można użyć refleksji

67 Jakośc kodu Testy to też kod – równiez powinien być (bardzo) dobrej jakości  Krótki, zrozumiały kod  Dobre nazewnictwo  Brak powtórzeń Testy można i należy refaktoryzować Testy nie powinny zawierać logiki – jak testować testy? Jeśli test zawiera logikę należy ją wydzielić (np. do funkcji). Takie funkcje mozna przetestować. Dobrej jakości testy nie wymagają intensywnej pielęgnacji. Projekty padają nie z powodu braku ale z powodu złej jakości testów

68 Duplikacja Duplikacja to ZŁO :  Duży koszt pielęgnacji  Utrudniona poprawa testów/rozwój kodu (Rak testów) W celu uniknięcia duplikacji:  Buildery obiektów testowych  Własne asercje  Metody weryfikujące  Testy sterowane danymi

69 Struktura Testy można grupować w klasy (np. dla wspólnej inicjalizacjj SUT) Jedna klasa testowa nie musi (i zwykle nie odpowiada) jednej klasie testowanej raczej konkretnym danym testowym Czesto (zwykle?) dla pojedynczej funkcji piszemy kilka testów: jeden test - jeden aspekt działania funkcji (jeden asert logiczny)

70 Inicjalizacja Sut SUT = system under test SUT nie powinien być wspołdzielony pomiędzy wieloma testami (tj inicjalizacja test1, test2 itd).  Wrażliwość na kolejnośc wykonania  Trudna diagnostyka Sut może być kazdorazowo inicjowany w teście lub inicjowany w SetUp. To drugie poejście ułatwia redukcje redundancji

71 Filozofia: definiowania testów Jeden po drugim: przyrostowy development Wszystkie na raz: np definiujemy pojedyncze user story jako sekwencje testów

72 Filozofia: budowa test fixture Up front  Łatwo o błedny projekt  Niepotrzebny kod – YAGNI (You aren't gonna need it) Test po teście: Nie należy pisać kodu na wyrost Przyrostowy development Fresh Fixture

73 Filozofia: co testować Stan obiektów Zachowanie obiektów: Testujemy wołania innych funkcji/obiektów Zasada proś [o przysługę] nie pytaj [o stan] Jak trzeba mieszamy podejścia Przede wszystkim należy testować to co ma wartość z punktu widzenia klienta

74 Zachowanie... public class InvoiceProcessor { private ISender sender; private ILogger logger; public InvoiceProcessor(ISender nSender, ILogger nLogger) { sender = newSender; logger = nLogger; } public bool Process(...) { logger.Log("start"); if (...) {... bool ret = sender.Send(invoice);... } } } var procesor = new InvoiceProcesor(new InvoiceSender(...), new Logger()); TEST

75 ...to nie stan Problem 1: ignorujemy zachowanie kodu logger.Log() Problem 2: nie mamy skonfigurowanego sendera –czy sender.Send() zwrócil true czy false Problem 3: czy sender zostal wywolany i z jakimi paramerami

76 Wymagane zastępstwo Problem 1: public class FakeLogger : Ilogger { public void Log(string msg) {} } Problem 2: public class FakeSender : ISender { public bool Ret = true; public bool Send (obiect toSend) { return Ret; } }

77 Wymagane zastępstwo Problem 3: public class FakeSenderValidator : ISender { public bool Ret = true; public bool SendWasCalled = false; public object SendArgument; public bool Send (object toSend) { SendWasCalled = true; SendArgument = toSend; return Ret; } }

78 Bez nowych klas... Stub: – obiekt kreowany dynamicznie – akceptujący wołania i ew. zwracający konkretne wartości Mock: – obiekt kreowany dynamicznie – z mozliwością weryfikacji konkretnych zachowań Mocking frameworks: Nmock, Moq – stosunkowo proste Rhino mock – bardzo zaawansowany TypeMock – jeszcze bardziej zaawansowany ale... komercyjny

79 Przykład 1, 2 [Test] public void Process_whenSendingSuccesful_...() { //Problem1: var logger = MockRepository.GenerateStub (); //Problem2: var sender = MockRepository.GenerateStub (); sender. Stub(s => s.Send(null)). IgnoreArguments(). Return(true); InvoiceProcessor sut = new InvoiceProcessor(sender, logger); var result = Sut.Process(....);... }

80 Przykład 3 [Test] public void Process_whenSendingSuccesful_...() { var logger = MockRepository.GenerateStub (); var sender = MockRepository.GenerateStub (); sender. Stub(s => s.Send(null)). IgnoreArguments(). Return(true); Invoice invoice =...; InvoiceProcessor sut = new InvoiceProcessor(sender, logger); var result = Sut.Process(invoice);... //Problem 3: sender.AssertWasCalled( s => s.Send(invoice) ); }

81 Problemy przy testach Niejawne wejście - środo- wisko zewnetrzne np.: Pojawienie się pliku Brak pamieci Pojawienie się procesu Otrzymanie maila Przyciśnięcie przycisku w GUI Zmiana danych w bazie Niejawne wyjście – efekt działania kodu np.: Skasowanie pliku Zabicie procesu Wysłanie maila Wyświetlenie czegoś na ekranie, zmiana stanu elementow GUI Zapis danych do bazy

82 Trudny test public void StartMonitoring(...) {... if (System.IO.File.Exists("myFile")) //send email } Niejawne wejście Niejawne wyjście

83 Dedykowana Podklasa class SystemMonitor{ public void StartMonitoring(...) {... if (FileExists("myFile")) SendEmail(...) } protected virtual bool FileExists(string fileName) { return System.IO.File.Exists(fileName); } protected virtual bool SendEmail (...) { //send email }

84 Dedykowana Podklasa class SystemMonitorTestSubclas : SystemMonitor { public bool fileExists = true; public bool emailSent = false; public virtual void SendEmail(...) { emailSent = true; } public virtual bool FileExists (...) { return fileExists; } } var sut = new SystemMonitorTestSubclas (); A z mockiem: var sut = MockRepository.GeneratePartialMock (); sut.Stub(s => s.FileExist (null)).IgnoreArguments().Return(true); sut.Stub(s => s.SendEmail(null)).IgnoreArguments();.... sut.StartMonitoring(); sut.AssertWasCalled( s => s.SendEmail(null), options => IgnoreArguments());

85 Obiekty izolujący class SystemMonitor { private FileSystemProvider fileSystem; private MailSenser sender; public void StartMonitoring(...) {... while(...) {... if (fileSystem.FileExists("myFile")) sender.SendEmail(...) }

86 Narzędzia Frameworki UT  xUnit  MSpec Mocking tools:  TypeMock Isolator  RhinoMock  Mocq Kontenery IOC Automocking Specyfikacja wymagań  SpecFlow

87 Warto poczytać, popatrzeć... Andy Hunt, Dave Thomas "Pragmatic Unit Testing in C# with Nunit" Roy Osherove "The Art of Unit Testing with Examples in.NET" Gerard Meszaros "xUnit Test Patterns" http://www.ayende.com/wiki/Rhino+Mocks.ashx Prezenacje wideo: "Roy Osherove - Understanding Test Driven Development.wmv" "Roy Osherove - Unit Testing Best Practices.wmv" "Roy Osherove - Test Driven Development, Using Mock Objects.wmv"

88 Kontenery DI

89 Kod prehistoryczny Ograniczenia: Żeby zmienić klasy usług lub zmodyfikować zależności należy zmienić kod klasy zależnej Konkretna implementacja klas usługowych musi być dostępna w czasie kompilacji Testowanie (jednostkowe?) klas jest trudne z powodu konieczności uwazględniania pot. Skomplikowanego zachowania klas usługowych Klasy zawierają zduplikowany kod do tworzenia i zarzadzania serwisami.

90 DI: Wstrzykiwanie zależności - budowniczy

91 DI przypadek użycia public interface IService { } public class Application { private readonly IService service; public Application(IService service) { this.service = service; } } public class ServiceImpl : IService { } Application a = new Application(new ServiceImpl()); //Co z ew. parametrami ServiceImpl ?

92 IoC – z kontenerem DI IUnityContainer container = new UnityContainer(); //configuration UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); section.Containers.Default.Configure(container); … Application application = container.Resolve ();

93 IoC - Przegląd rozwiązań  Castle  Unity  Ninject  Autofac  StructureMap  Spring.Net

94 DIC - Możliwości i różnice: Różne modele życia obiektów Automatyczna rejestracja klas/obiektów (np przez refleksję) Konfiguracja xml/programowa/skryptowa Introspekcja Kaskadowe rozwiązywanie zależności Wstrzykiwanie zależności przez konstruktor/właściwości Komunikowanie błedów (np zależności rekursywne, brak rozwiązania dla typu) Obsługa niezwiązanych generyków, list obiektów

95 Service Locator

96 UnityContainer container= new UnityContainer() ServiceLocator.SetLocatorProvider( () => new UnityServiceLocator(container) ); // container configuration container.configure….. ServiceLocator.Current.GetInstance () ServiceLocator.Current.GetInstance ("simple")

97 Życie jest skomplikowane... Moduł B Moduł A Moduł C ServiceLocator UNITY Adapter GetInstance Konfiguracja

98 Unity Wstrzykiwanie zależności:  konstruktor  właściwości  metody Konfiguracja  Atrybuty  XML config  Programowa Auto Registration Cykliczne referencje są raportowane jako Stack overflow...

99 Prosty kod public interface IService { … }; public interface ILogger { … }; public class ConsoleLogger{ public ConsoleLogger() { … } } public class SimpleService { public SimpleService(ILogger logger) { … } } public class Application { public Application(ILogger logger, IService service) { … } }

100 Unity – konfiguracja programowa using (IUnityContainer container = new UnityContainer()) { container.RegisterType ().RegisterInstance (new ConsoleLogger ()); Application application = container.Resolve (); ILogger loggerInstance = container.Resolve (); }

101 Unity – konfiguracja xml using (IUnityContainer container = new UnityContainer()) { UnityConfigurationSection config = (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); if (config != null) config.Containers.Default.Configure(container); Application application = container.Resolve (); }

102 Unity – XML elementy konfiguracji …. Mappings Aliases are not obligatory

103 Unity – wielokrotne konstruktory public class SimpleService : IService { public SimpleService(ILogger logger) { … } public SimpleService(String logFile) { … } } Unity domyślnie wybiera konstruktor z największa liczbą parametrów Jeżeli jest takich kilka rzucany jest wyjatek Jak określić który konstruktor ma być wybierany?

104 Unity – wybór konstruktora public class SimpleService : IService { [InjectionConstructor] public SimpleService(ILogger logger) { … } public SimpleService(String logFile) { … } } container. RegisterType ( new InjectionConstructor(typeof(ILogger) ) ) 1 2 3

105 Unity – wstrzykiwanie zależności przez właściwości public class SimpleService : IService { [Dependency] public ILogger Logger { get; set; } } container. RegisterType ( new InjectionProperty("Logger") ) 1 2 3

106 Unity – wstrzykiwanie zależności przez metody public class SimpleService : IService { [Dependency] public Init(Ilogger logger){ } } container. RegisterType ( new InjectionMethod("Init") ) 1 2 3

107 Unity – specjalizacja public class SimpleService : IService { [Dependency("UI")] public ILogger Logger { get; set; } } container. RegisterType ("UI", new InjectionConstructortypeof(ILogger) ) 1 1 2 2 3 3

108 Zasoby CommonServiceLocator implementation: http://www.codeplex.com/CommonServiceLocator http://www.codeplex.com/CommonServiceLocator Auto rejestracja dla unity http://autoregistration.codeplex.com/ http://autoregistration.codeplex.com/ Automocking