Pobierz prezentację
Pobieranie prezentacji. Proszę czekać
1
Zaawansowane Techniki Obiektowe
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
public class Authentication { private string _key; public string Key { get {return _key;} set {_key = value;} } public string EncodePassword(string password) { if (password==null || password.Length==0) { throw new ValidationException ("Password is empty"); } // do the encoding ... return encoded_password; }
11
Test jednostkowy – przykład 1 cd.
[TestFixture] public class TestFixture1 { Authentication authenticator; [SetUp] public void Init() { // set up our authenticator and key authenticator = new Authentication(); authenticator.Key = "TESTKEY"; } [Test] public void Encoding_ForArgument_ShouldReturnProperValue() { String result = authenticator.EncodePassword("user"); // Validate that for "user" and "TESTKEY" //key we should get proper result Assert.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( , 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) { var myClass = 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 (zaraz po) 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 : W celu uniknięcia duplikacji:
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 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"
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: Problem 2:
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 } Niejawne wejście Niejawne wyjście
49
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
50
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< SystemMonitor >(); 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());
51
Obiekty izolujący class SystemMonitor { private FileSystemProvider fileSystem; private MailSenser sender; public void StartMonitoring(...) { ... while(...) { ... if (fileSystem.FileExists("myFile")) sender.Send (...) }
52
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" Prezenacje wideo: "Roy Osherove - Test Driven Development, Using Mock Objects.wmv" "Ayende Rahien - Interaction Based Testing with Rhino Mocks.wmv"
Podobne prezentacje
© 2024 SlidePlayer.pl Inc.
All rights reserved.