1 / 52

Zaawansowane Techniki Obiektowe

Zaawansowane Techniki Obiektowe. Wprowadzenie Jak pisać UT ?. Testy jednostkowe - wprowadzenie. W czym pomagają testy jednostkowe?. Ułatwiają znajdowanie błedów Ułatwiają zrozumienie kodu Ułatwiają utrzymanie kodu Ułatwiają pisanie kodu. Test jednostkowy.

rangle
Download Presentation

Zaawansowane Techniki Obiektowe

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. ZaawansowaneTechnikiObiektowe Wprowadzenie Jak pisać UT ?

  2. Testy jednostkowe - wprowadzenie

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

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

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

  6. 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.AreEqual (false,result, "invalid user/password shouldn't be accepted"); } }

  7. NUnit – troche infrastruktury • [SetUp] - metoda wywoływana przed każdym testem. • Konstruktor nie jest wywoływany w takim momencie bo obiekt nie jest tworzony za każdym razem • [TearDown] – metoda wywoływana po każdym teście • [FixtureSetup]/[FixtureTeardown] – analogicznie

  8. 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.AreEqual(true,result, " valid user/password should be acceprted") ); } }

  9. Test jednostkowy – elementy (3) [Test] [ExpectedException(typeofInvalidArgumentException))] public void TestLogin3() { var result = sut.IsLoginOk(null,null); }

  10. Test jednostkowy – przykład 1 publicclass Authentication {privatestring _key;publicstring Key { get  {return _key;} set {_key = value;} }publicstringEncodePassword(stringpassword)  {if (password==null || password.Length==0) {thrownewValidationException("Password is empty");}// do the encoding ...returnencoded_password; }

  11. Test jednostkowy – przykład 1 cd. [TestFixture] publicclass TestFixture1 {  Authentication authenticator; [SetUp] publicvoid Init()  {     // set up our authenticator and key     authenticator = new Authentication();authenticator.Key = "TESTKEY"; } [Test] publicvoid Encoding_ForArgument_ShouldReturnProperValue() { String result = authenticator.EncodePassword("user"); // Validate that for "user" and "TESTKEY" //key we should get proper resultAssert.AreEqual("fwe94t-gft5",result); }

  12. NUnit podstawowy model asercji • Are(Not)Equals, AreSame • Contains • Greater, GreaterOrEqual, Less, LessOrEqual • IsEmpty, IsNaN, IsFalse, IsTrue, , Is(Not)Null, • Is(Not)InstanceOfType, Is(Not)AssignableFrom

  13. NUnit – Assert + fluent interface • Assert.That(1 + 1, Is.EqualTo(2)); • Assert.That(2.5000 + 2.5001, Is.EqualTo(5).Within(.0001)); • Assert.That( "Hello", Is.EqualTo( "hello" ).IgnoreCase ); • Assert.That(o1, Is.SameAs(o2)); • Assert.That(new ArrayList(), Is.Empty); • Assert.That(ht, Is.InstanceOfType(typeof(IDictionary))); • Assert.That( phrase, Text.Contains( "tests fail" ) ); • Assert.That( phrase, Text.EndsWith( "PASSING!" ).IgnoreCase ); • Assert.That( phrase, Text.Matches( "Make.*tests.*pass" ) ); • Assert.That(iarray, Has.Some.GreaterThan(2)); ...i inne

  14. MSTest vs NUnit • Analogiczne atrybuty np.: • TestFixture -> TestClass • Test -> Test Method • SetUp – TestSetUp • Nieco słabszy model asertów • [Timeout], [DataSource] • Nieintuicyjna organizacja testów: listy testów, wykonanie w oddzielnych katalogach • Automatycznie generowane testy (niekoniecznie sensowna struktura, nazewnictwo itd?), • Generowane akcesory do prywatnych składowych (czy prywatne elementy powinny byc testowane?) • Wparcie ze strony IDE

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

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

  17. 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) { varmyClass = new MyClass(); return myClass.Divide(numerator,denominator); }

  18. Testy jednostkowe- Jak Pisać Dobre Testy

  19. Dobre testy jednostkowe • Zrozumiałe • Powtarzalne • Niezależne • Szybkie • Łatwe do uruchomienia • Łatwe w utrzymaniu

  20. Po co pisać 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.

  21. (TJ) Jak pisać testy? • Testy powinny testować jedną klasę/funkcję a nie cały system... • 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ć) Dwa podejścia: • Testy piszemy po (zarazpo) napisaniu kodu – w ten sposób możemy kod stosunkowo łatwo zmienić, zawsze należy sprawdzić czy test upada • Testy piszemy przed kodem (TDD/BDD)

  22. (TJ) Jak nazywać testy? • Nazwa testu powinna dobrze lokalizować błąd. Najlepiej bez debugowania, analizy komunikatów. • Czy nazwy w prezentowanych przykładach były dobre? • Dobre nazwy zwalniają ze szczegółowych komunikatów przy asercjach • Konwencje • LoginComponent_InvalidUser_ShuldThrowException • WhenUserIsInvalid. IsLoginOk_shouldthrowException • Trudno nazwać test, który dotyczy wiele aspektów zachowania klasy

  23. (TJ) Jak używać testów? • Są często (stale?) uruchamiane podczas kodowania • Są cyklicznie uruchamiane na serwerze buildów. • Testy odzwierciedlają kontakt 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)

  24. (TJ) 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

  25. (TJ) Ż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

  26. (TJ) 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

  27. (TJ) 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

  28. (TJ) 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

  29. (TJ) 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)

  30. (TJ) Filozofia: definiowania testów • Jeden po drugim: przyrostowy development • Wszystkie na raz: np definiujemy pojedyncze user story jako sekwencje testów

  31. (TJ) 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

  32. (TJ) Filozofia: co testować • Stan obiektów • Zachowanie obiektów: • Testujemy wołania innych funkcji/obiektów • Intensywne użycie "test doubles" – delikatne testy • Zasada proś [o przysługę] nie pytaj [o stan] • Jak trzeba mieszamy podejścia

  33. (TJ) Test doubles (zastępcy?)

  34. (TJ) Warto poczytać, popatrzeć ... • Andy Hunt, Dave Thomas "Pragmatic Unit Testingin C# with Nunit" • Roy Osherove "The Art of Unit Testingwith Examples in .NET" • Gerard Meszaros "xUnit Test Patterns" Prezenacje wideo: • "Roy Osherove - Understanding Test Driven Development.wmv" • "Roy Osherove - Unit Testing Best Practices.wmv"

  35. Testy jednostkowe- Testowanie zachowania

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

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

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

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

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

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

  42. Przykład 3 [Test] public void Process_whenSendingSuccesful_...() { var logger = MockRepository.GenerateStub<ILogger>(); var sender = MockRepository.GenerateStub<ISender>(); 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)); }

  43. Więcej o opcjach sender.AssertWasCalled( s => s.Send(null), options => options.IgnoreArguments() .Repeat.Twice()); sender.AssertWasNotCalled( s => s.Send(null) ); sender.AssertWasCalled( s=>s. Error(0,null), options => options. IgnoreArguments() .Constraints(Is.LessThan(10),Text.StartsWith("Error")) .Repeat.Twice());

  44. Jakie parametry miało wołanie Error int cnt = 0; sender.Stub(s => s.Error(0, 0)) .IgnoreArguments() .Do(new CallbackDelegate(MyFunction)); .Do((Delegates.Action<int, string>) delegate(int x, string msg) { Console.WriteLine(msg); cnt = cnt + x; } ) .WhenCalled( tmp => { Console.WriteLine(tmp.Arguments[1]); cnt = cnt + (int) tmp.Arguments[0]; } )

  45. Po kolei ... Stara składnia (nowa niestety nie wspiera takich konstrukcji) var mockery = new MockRepository(); var sender = mockery.DynamicMock<ISender>(); using (mockery.Ordered()) { sender .Expect(s => s.Send(null)) .IgnoreArguments() .Return(false) .Repeat.Times (3); sender.Expect(s => s.Error(0,null)) .IgnoreArguments() .Repeat.AtLeastOnce(); } mockery.ReplayAll(); • Uwaga! Ostrożnie! Delikatne testy! - Nie należy przesadzać z określaniem kolejności!

  46. Co Nosorożec może a czego nie... 2 rodzaje składni (nowa: silne typowanie, jednorodna składnia dla funkcji z typem i void, wsparcie dla składni AAA – tj. AssertThatWasCalled) Można mockować (Rhino Mock): • Elementy interfejsu (funkcje, properties) • Funkcje, properties wirtualne • Wybrane składowe klas (-> PartialMock) Nie można mockować (Rhino Mock): • Klasy sealed • Statyczne składniki • Funkcje niewirtualnne

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

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

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

  50. 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: varsut = MockRepository.GeneratePartialMock< SystemMonitor >(); 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());

More Related