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

Slides:



Advertisements
Podobne prezentacje
C++ wykład 2 ( ) Klasy i obiekty.
Advertisements

C++ wykład 4 ( ) Przeciążanie operatorów.
Programowanie obiektowe PO PO - LAB 2 Wojciech Pieprzyca.
Programowanie obiektowe
Wzorce.
Zaawansowane metody programowania – Wykład V
Dziedziczenie. Po co nam dziedziczenie? class osoba { char * imie, char * imie, * nazwisko; * nazwisko;public: void wypisz_imie(); void wypisz_imie();
Generics w .NET 2.0 Łukasz Rzeszot.
Bezpieczeństwo wyjątków w C++: OpenGL
PROGRAMOWANIE STRUKTURALNE
Serwery Aplikacji ASP .NET Web Objects Arkadiusz Popa.
Refaktoryzacja czyli odświeżanie kodu
Obiektowe metody projektowania systemów
Struktury.
Zaawansowane techniki obiektowe
C++ wykład 2 ( ) Klasy i obiekty.
Zasady zaliczenia Warunki uzyskania zaliczenia:
Pakiety i ATD 1 Definicja. Pakietem albo jednostką programową nazywamy grupę logicznie powiązanych elementów, które mogą być typami, podtypami, obiektami.
Wzorce projektowe w J2EE
Wstęp do kontenerów IoC
Test Doubles Adam Gabryś , v1.1,
Pakiety w Javie Łukasz Smyczyński (132834). Czym są pakiety? Klasy w Javie są grupowane w pewne zbiory zwane pakietami. Pakiety są więc pewnym podzbiorem.
Zaawansowane Techniki Obiektowe
Komponentowe i rozproszone Jak pisać dobry kod. ZŁY KOD WYGLĄDA TAK...
Tworzenie nowych kont lokalnych i domenowych, oraz zarządzanie nimi
Podstawy programowania II
JAVA c.d.. Instrukcji wyboru SWITCH używamy, jeśli chcemy w zależności od wartości pewnego wyrażenia wykonać jeden z kilku fragmentów kodu. Jest to w.
Dziedziczenie Maciek Mięczakowski
Komponentowe systemy rozproszone Wprowadzenie. Komponent... jest to podstawowa jednostka oprogramowania z kontraktowo (deklaratywnie) opisanymi interfejsami,
Programowanie obiektowe Wykład 3 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/21 Dariusz Wardowski.
Programowanie obiektowe Wykład 6 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/14 Dariusz Wardowski.
Projektowanie obiektowe
Programowanie obiektowe 2013/2014
ZWIĄZKI MIĘDZY KLASAMI KLASY ABSTRAKCYJNE OGRANICZENIA INTERFEJSY SZABLONY safa Michał Telus.
Zarządzanie Projektami
Kurs języka C++ – wykład 9 ( )
Programowanie w języku C++
Dobry kod OO Jeżeli zapytamy statystycznego programistę z czym kojarzy mu się dobry kod OO to najprawdopodobniej będzie mówił o wzorcach projektowych.
UML W V ISUAL S TUDIO Mateusz Lamparski. UML D EFINICJA Unified Modeling Language (UML) to graficzny język do obrazowania, specyfikowania, tworzenia i.
Treści multimedialne - kodowanie, przetwarzanie, prezentacja Odtwarzanie treści multimedialnych Andrzej Majkowski informatyka +
Bazy i Systemy Bankowe Sp. z o.o. ul. Kasprzaka 3, 85 – 321 Bydgoszcz
Programowanie strukturalne i obiektowe C++
Diagram klas Kluczowymi elementami są: klasy (class)
Kurs języka C++ – wykład 4 ( )
Technologie internetowe Wykład 5 Wprowadzenie do skrytpów serwerowych.
Copyright © Jerzy R. Nawrocki Team Software Process Inżynieria oprogramowania II Wykład.
Komponentowe systemy rozproszone Wprowadzenie. Komponent... jest to podstawowa jednostka oprogramowania z kontraktowo (deklaratywnie) opisanymi interfejsami,
Projektowanie obiektowe. Przykład: Punktem wyjścia w obiektowym tworzeniu systemu informacyjnego jest zawsze pewien model biznesowy. Przykład: Diagram.
Paweł Starzyk Obiektowe metody projektowania systemów
Wzorce Projektowe w JAVA
Programowanie Zaawansowane
Partnerstwo dla Przyszłości 1 Lekcja 28 Dziedziczenie i rodzaje dziedziczenia.
T ESTY JEDNOSTKOWE W C# Alicja Majka, A GENDA Wprowadzenie do środowiska Czym są testy jednostkowe i po co je stosować? XUnit, NUnit Pokrycie.
Interfejs użytkownika „No matter how cool your interface is, less of it would be better”
Testy jednostkowe. „Test jednostkowy (unit test) to fragment kodu, który sprawdza inny fragment kodu”
Inżynieria oprogramowania Wzorce konstrukcyjne WWW: Jacek Matulewski Instytut Fizyki, UMK.
Programowanie Obiektowe – Wykład 6
ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod
Programowanie Obiektowe – Wykład 9
Strumienie, Wczytywanie, Zapisywanie, Operacje na plikach
Krzysztof Manuszewski
Klasy, pola, obiekty, metody. Modyfikatory dostępu, hermetyzacja
(według:
Delegaty Delegat to obiekt „wiedzący”, jak wywołać metodę.
Programowanie Obiektowe – Wykład 2
Zaawansowane techniki obiektowe
PGO - Projektowanie i implementacja pierwszych klas
Refaktoryzacja czyli odświeżanie kodu
Zapis prezentacji:

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

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

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

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

Trudno jest testować zły kod...

ZŁY KOD WYGLĄDA TAK...

... 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

... 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

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

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ę

... 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

..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

...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

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

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

... 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ą)

LEPSZYKOD WYGLĄDA TAK...

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); }

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

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

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

(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

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(); }

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

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

SRP - Możliwe rozwiązanie

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!

(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

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

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!

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

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

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)

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)

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)

(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

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.

(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

Unit Testing DIP - Przykład

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

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”

Testy jednostkowe

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

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.

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.

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"); }

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

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

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"); }

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

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

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

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.

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ć.

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

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

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

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ć

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

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"] ); }

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); }

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.

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ć)

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)

Testowanie zachowania Izolacja klas

Ż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

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

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

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)

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

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

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

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

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

...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

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; } }

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; } }

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

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(....);... }

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) ); }

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

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

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

Dedykowana Podklasa class SystemMonitorTestSubclas : SystemMonitor { public bool fileExists = true; public bool Sent = false; public virtual void Send (...) { Sent = 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.Send (null)).IgnoreArguments();.... sut.StartMonitoring(); sut.AssertWasCalled( s => s.Send (null), options => IgnoreArguments());

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

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

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" 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"

Kontenery DI

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.

DI: Wstrzykiwanie zależności - budowniczy

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 ?

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

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

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

Service Locator

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

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

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...

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) { … } }

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

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 (); }

Unity – XML elementy konfiguracji <typeAlias alias="string" type="System.String, mscorlib" /> <typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity, Version= , Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> …. Mappings Aliases are not obligatory

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?

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

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

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

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

Zasoby CommonServiceLocator implementation: Auto rejestracja dla unity Automocking