Języki i Techniki Programowania II Wykład 15 Testowanie
Testy "Beware of bugs in the above code; I have only proved it correct, not tried it." Knuth
Typy Testów Smoke Tests - odpalenie systemu do wykonania prostego zadania, po dokonaniu jakiejkolwiek zmiany Regression Tests test czy funkcjonalność została zachowana. Problem: BARDZO DUŻO PRACY Rozwiązanie: Zautomatyzowanie procesu Continuous Build proces automatycznego budowania i uruchamiania Load Tests sprawdzanie granic maksymalnego obciążenia Stress Tests testy stabilności pod obciążeniem Performance Testing pomiary wydajności
Zakresy Testów Poziomy testów: Acceptance Tests Sprawdzenie zgodności ze specyfikacją (Test Scripts) System Tests Sprawdzenie poprawności współdziałania modułów Sprawdzenie różnych konfiguracji Unit Tests Najprościej: main w każdej klasie Służą jako dokumentacja Warto użyć narzędzi
Kategorie Testów Kategorie testów z punktu widzenia testerów: White Box testing testy developerskie Black Box Testing testy dokonane przez QA Gray Box Testing gdy testerzy mają dostęp do konfiguracji. Kategorie testów z punktu widzenia cyklu życia: Alpha Tests Głównie White Box Testing Beta Tests Gray, Black Box Testing u wybranej grupy użytkowników Gamma Tests nieformalne (świadome wady)
Test Driven Development Pisząc program i tak na bieżąco testujemy Wiara w to że raz poprawiony błąd się nie powtórzy nie jest niczym uzasadniona Powtarzalne czynności można i trzeba automatyzować Koszt automatyzacji zwraca się bardzo szybko
Test Driven Development Testy zwiększają sprawność programisty (ang. mobility) Testerzy na pewno nie stracą pracy Testy są dla ludzi, nie dla programów
Test Driven Development Testy zwiększają sprawność programisty (ang. mobility)... a nie dowodzą poprawności programu Testerzy na pewno nie stracą pracy Testy są dla ludzi, nie dla programów... bo przecież testy nie stanowią części produktu końcowego
Test Driven Development Zautomatyzowane testy: Pomagają lepiej zarządzać koncentracją (ang. flow) Stanowią naturalne rozszerzenie nawyku kompilujuruchom-pisz-kompiluj-uruchom... Pozwalają wcześnie wyeliminować trywialne błędy te najbardziej zniechęcające Powodują że przyjemne programowanie znowu staje się przyjemne Pozwalają odejść od mentalności Jak działa, nie dotykaj - nie musimy już godzić się na kompromisy - Refactor Mercilessly
Reguły Test Driven Development 1. You are not allowed to write any production code unless it is to make a failing unit test pass. 2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures. 3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test. 1. Zaczynamy pisanie od Unit Testu 2. Piszemy tylko tyle ile trzeba, by test mógł przejść (czyli niewiele), przechodzimy do pisania kodu produkcyjnego (brak kompilacji jest traktowany jako niepowodzenie testu) 3. Jeśli tylko test przejdzie, lub się kompiluje przechodzimy do punktu 2
Proces Test Driven Development 1. Stwórz test który sprawdza drobną część funcjonalności 2. Doprowadź do stanu by test się nie powiódł 3. Napisz kod który spowoduje że test przejdzie 4. Zrefaktoruj kod, doprowadzając do najprostszej możliwej struktury (Refactor Mercilessly) 5. Idź do punktu 1.
Proces Test Driven Development Założenia: Interfejsy oraz przekazywanie wiadomości (message passing) czyli wywoływanie metod - są ważniejsze niż kod proceduralny Testuj co się da W Praktyce: test typowej sytuacji, testy warunków brzegowych, testy sprawdzające poprawność usunięcia każdego buga Wszystkie testy muszą zawsze przechodzić
Ograniczenia automatyzacji testów Problemy: Interfejs użytkownika, jego funkcjonalność Interfejs WWW (istnieją do tego narzędzia, ale...) Niedeterministyczne algorytmy heurystyczne algorytmy genetyczne, wyżarzanie (annealing) etc... Systemy rozproszone Testowanie struktury danych (np. zasadniczy element inżynierii Baz Danych)
Problemy automatyzacji testów Dla skutecznego testowania, należy: Mieć architekturę systemu która dobrze wspiera rozdzielenie funkcjonalności oraz korzystanie z MockObject-ów (sugerowane: Inversion of Control) Nie mieć logiki w warstwie prezentacji (niekoniecznie może się udać) Zaplanować bardzo intensywne dziedziczenie testów Przemyśleć strategię testowania co chcemy testować, jakie narzędzie chcemy stosować, jaki mamy styl programowania, jaki będzie cykl życiaprojektu nie ma uniwersalnych rozwiązań
Ograniczenia automatyzacji testów Problemy: Interfejs użytkownika, jego funkcjonalność Interfejs WWW (istnieją do tego narzędzia, ale...) Niedeterministyczne algorytmy heurystyczne algorytmy genetyczne, wyżarzanie (annealing) etc... Systemy rozproszone Testowanie struktury danych (np. zasadniczy element inżynierii Baz Danych)
Automatyzacja testów Testy winny być: Atomiczne kod jest mniej kruchy przy refactoringu Izolowanie i niezależne od porządku uruchomienia Czytelne Proste w konfiguracji Szybkie
Automatyzacja testów Luźne uwagi: Książkowe podejście: Unit=jednostka, czyli dokładnie 1 klasa, metoda itp.... ale w praktyce jest to nie Unit Test - ale Programmer Test - test funkcjonalności, nie jednostki Prędkość wykonania testów jest kluczowa nikt nie będzie czekał 30 minut na jedną kompilację + testy Testy działają w trakcie tworzenia software'u. Nie należy jednak zapominać o wersji produkcyjnej: logi i dobrze zaprojektowane wyjątki są nadal konieczne
Automatyzacja testów Luźne uwagi cd.: Czy 100% pokrycie testami jest konieczne? NIE Jaka jest prędkość kodowania przy zastosowaniu testów? Pod koniec projektu może nawet być ujemna (refactoring)! Wniosek: Bądź elastyczny, nie daj się zastraszyć metodologiom i metrykom jedynym kryterium oceny jest skuteczność
JUnit Twórcy JUnit Kent Beck (XP) i Erich Gamma (GoF Book) Jak stworzyć własny Unit Test: Dziedziczymy po TestCase Przeciążamy runtest Tworzymy własny konstruktor, przekazując String do super(...); Wołamy asserttrue() w metodach testowych metoda testowa musi być: zaczynać się od test (Junit <4.0) publiczna bezparametrowa
} JUnit Tworzymy Fixture (Ustawienie) zestaw testów z okre ślonymi danymi np. Mock Objectem/Stubem Dziedziczymy po TestCase Przeci ążamy runtest Zestaw testów: Tworzymy wasny ł konstruktor, przekazuj ąc String do super(...); setup() tworzy nam dane, teardown() niszczy public static Test suite() { TestSuite suite= new TestSuite(); suite.addtest(new MoneyTest("testMoneyEquals")); suite.addtest(anothermoneytest.class); return suite;
Junit 4 przed każ dym testem wykonywane s ą wszystkie metody oznaczone jako @Before, a po teś cie @After przed wszystkim testami wykonywane s ą wszystkie metody oznaczone jako @BeforeClass, a po teś cie @AfterClass Klasy testów ju ż nie musz ą dziedziczy ć z TestCase Testy stanowi ą metody publiczne bezparametrowe oznaczone poprzez @Test Mo żna kontrolowa ć czas testu @Test (timeout=10) Mo żna kontrolowa ć czy test rzuci ł okre ślony wyj ątek @Test(expected=IndexOutOfBoundsException.class)
Junit 4 import static org.junit.assert.assertequals; //Java 5 magic @Test public void simplequery() throws SQLException { Statement stmt; try { stmt = conn.createstatement();resultset rs = null; rs = stmt.executequery("select 1 from data"); if(rs.next()) assertequals("1",rs.getstring(1)); } catch (SQLException e) { e.printstacktrace(); fail("problems with the database"); } }
JUnit public static void main(string args[]) { } Tak że: junit.textui.testrunner.run(suite()); java junit.awtui.testrunner java junit.swingui.testrunner Podajemy nazw ę klasy z testem
JUnit a Eclipse Eclipse 3.1+ ma wbudowanego JUnit Mo żna dodawa ć do projektu testy dla konkretnych klas Uruchamiamy je poprzez Run As... -> JUnit test
Mock vs Stub Mock Object Obiekt testowy pozwalaj ący sprawdzi ć interakcj ę Stub Object Obiekt testowy pozwalaj ący sprawdzi ć poprawno śc danych Fake Object prawie dziaaj ł ący obiekt Dummy Object zapchajdziura - obiekt nigdy nie wykorzystany
Stub Object public class OrderStateTester extends TestCase { private static String TALISKER = "Talisker"; private static String HIGHLAND_PARK = "Highland Park"; private Warehouse warehouse = new WarehouseImpl(); protected void setup() throws Exception { warehouse.add(talisker, 50); warehouse.add(highland_park, 25); } public void testorderisfilledifenoughinwarehouse() { Order order = new Order(TALISKER, 50); order.fill(warehouse); asserttrue(order.isfilled()); assertequals(0, warehouse.getinventory(talisker)); }
Stub Object public void testorderdoesnotremoveifnotenough() { Order order = new Order(TALISKER, 51); order.fill(warehouse); assertfalse(order.isfilled()); assertequals(50, warehouse.getinventory(talisker)); }
The Golden File Jak testowa ć aplikacje dziaaj ł ące z linii komend? Jeś li aplikacja ma dobrze okre ślenie wej ście i wyj ście (np. plik wej ściowy do parsera i wynik parsowania) stosujemy metod ę The Golden File - nagrany plik z wcze śniejszym wynikiem przetwarzania który test tylkoporównuje ze swoim wynikiem Za: Prawdopodobnie stworzenia najszybszy typ testów do stworzenia Przeciw: Trudne do uogólnienia na inne przypadki.pury ści mog ą powiedzie ć że Golden File to jest smell
Bazy Danych Jak testowa ć aplikacje korzystaj ąc ą z bazy danych? Istnieje kilka rozwi ąza ń: Korzystamy z bazy danych testowej (tak robi RoR) nale ży tylko piel ęgnowa ć jej struktur ę Korzystamy ze Stubów przechowuj ących dane w pliku konfiguracyjnym (korzystaj ąc np. z DBUnit) Tworzymy dla każ dego testu struktur ę bazy w pami ęci (korzystaj ąc np. z Hibernate oraz JavaDB/Derby) prawdopodobnie dziaaj ł ące najszybciej rozwi ązanie
Bazy Danych DBUnit dziedziczymy po DBTestCase public void testme() throws Exception {... IDataSet databasedataset=getconnection().createdataset(); ITable actualtable=databasedataset.gettable("table_name"); IDataSet expecteddataset = new FlatXmlDataSet(new File("expectedDataSet.xml")); ITable expectedtable = expecteddataset.gettable("table_name"); Assertion.assertEquals(expectedTable, actualtable); }
Pattern Mock Object Unit Test z zao ł żenia ma sprawdza ć wy łącznie drobn ą cz ęść systemu. W tym celu wszelka interakcja ze światem zewn ętrznym (pozosta łą częś ci ą systemu) musi zosta ć zast ąpiona przez MockObject MockObject powinien: by ć łatwy do stworzenia, łatwy do skonfigurowania, szybki, deterministyczny, bez interfejsu użytkownika, wy łącznie z metodami dost ępowymi ( get )
Pattern Mock Object NIE TESTUJ MOCKA Wniosek wszystkie elementy systemu musz ą pochodzi ć z fabryk. Żaden obiekt nie mo że tworzy ć elementów z którymi wspópracuje ł ( żeby mo żna byo ł podmieni ć go pod Mock) To si ę nazywa Inversion Of Control (IoC) istniej ą dedykowane kontenery temat wykracza znacznie poza tematyk ę wykadu ł
Mock Object public class OrderInteractionTester extends MockObjectTestCase { private static String TALISKER = "Talisker"; public void testfillingremovesinventoryifinstock() { //setup data Order order = new Order(TALISKER, 50); Mock warehousemock = new Mock(Warehouse.class); //setup expectations warehousemock.expects(once()).method("hasinventory").with(eq(talisker),eq(50)).will(returnvalue(true)); warehousemock.expects(once()).method("remove").with(eq(talisker), eq(50)).after("hasinventory");
Mock Object //exercise order.fill((warehouse) warehousemock.proxy()); warehousemock.verify(); asserttrue(order.isfilled()); } public void testfillingdoesnotremoveifnotenoughinstock() { Order order = new Order(TALISKER, 51); Mock warehouse = mock(warehouse.class); warehouse.expects(once()).method("hasinventory").withanyarguments().will(returnvalue(false)); order.fill((warehouse) warehouse.proxy()); assertfalse(order.isfilled());}
JMock Istniej ą narz ędzia do tworzenia Mock Object jako proxy mniej kruche ni ż rę cznie tworzone (uwaga: nie testuj Mocka) Sprawdzamy funkcjonalno ść Publisher w modelu Publish(er)/Subscribe(r) import org.jmock.*; class PublisherTest extends MockObjectTestCase { public void testonesubscriberreceivesamessage() { Mock mocksubscriber = mock(subscriber.class); Publisher publisher = new Publisher(); publisher.add((subscriber) mocksubscriber.proxy());
JMock final String message = "message"; //oczekiwane warunki mocksubscriber.expects(once()).method("receive").with(0eq(message) ); publisher.11publish(message); } } Jeś li oczekiwane warunki nie zostan ą spenione ł test się nie powiedzie
} Fluent Interface private void makenormal(customer customer) { Order o1 = new Order(); customer.addorder(o1); OrderLine line1 = new OrderLine(6, Product.find("TAL")); o1.addline(line1); OrderLine line2 = new OrderLine(5, Product.find("HPK")); o1.addline(line2); OrderLine line3 = new OrderLine(3, Product.find("LGV")); o1.addline(line3); line2.setskippable(true); o1.setrush(true);
Fluent Interface private void makefluent(customer customer) { customer.neworder().with(6, "TAL").with(5, "HPK").skippable().with(3, "LGV").priorityRush(); }
Fluent Interface Kluczem s ą settery zwracaj ą ce this customer.neworder().with(6, "TAL").with(5, "HPK").skippable().with(3, "LGV").priorityRush(); A czasem inny obiekt (dobre do podpowiedzi w IDE) mock.expects(once()).method("m").with( ); or(stringcontains("hello"),stringcontains("howdy")) W celu walidacji warto mie ć trigger table.select( name ).where( id, equal(6)).query()
Testowanie GUI Dla MVC mo żna testowa ć interakcj ę Modelu i Controllerów. Jeś li View ma zo ł żon ą logik ę to mo żna tworzy ć jego fragmenty i je odpytywa ć w trakcie testu. Istnieje mo żliwo ść nagrania klikni ęć i odtworzenie ich (java.awt.robot) ale jest to bardzo kruche rozwi ązanie Mo żna korzysta ć z istniej ących frameworków do testowania GUI np. Abbot
Testy są super!!! Niech komputer wyr ęczy Ci ę w nudnej robocie Zostaw debugger pisz testy! Rozwi ąż problem zaczynaj ąc od testów Nie daj si ę przyt łoczy ć testowaniem starego kodu: testuj tylko to co konieczne Dokumentuj testami: swój program, użyte API itp. itd. Wypracuj nawyk testowania. Odrzu ć zasad ę jak dziaa, ł nie dotykaj. Refactor Mercilessly. Znajd ź swój styl metody s ą dla ludzi, a nie ludzie dla metody