Pobierz prezentację
Pobieranie prezentacji. Proszę czekać
OpublikowałAdam Nowakowski Został zmieniony 8 lat temu
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 <typeAlias alias="string" type="System.String, mscorlib" /> <typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> …. 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
Podobne prezentacje
© 2024 SlidePlayer.pl Inc.
All rights reserved.