Interaktywne testowanie mutacyjne w procesie tworzenia oprogramowania w środowisku Visual Studio

Wielkość: px
Rozpocząć pokaz od strony:

Download "Interaktywne testowanie mutacyjne w procesie tworzenia oprogramowania w środowisku Visual Studio"

Transkrypt

1 POLITECHNIKA WARSZAWSKA Rok akademicki 2013/2014 WYDZIAŁ ELEKTRONIKI I TECHNIK INFORMACYJNYCH INSTYTUT INFORMATYKI PRACA DYPLOMOWA MAGISTERSKA Piotr Trzpil Interaktywne testowanie mutacyjne w procesie tworzenia oprogramowania w środowisku Visual Studio Opiekun pracy dr inż. Anna Derezińska Ocena Podpis Przewodniczącego Komisji Egzaminu Dyplomowego

2 Specjalność: Inżynieria Systemów Informatycznych Data urodzenia: 20 lutego 1989r. Data rozpoczęcia studiów: 21 lutego 2012r. Życiorys Od roku 2008 studiuję na wydziale Elektroniki i Technik Informacyjnych Politechniki Warszawskiej, gdzie w roku 2012 obroniłem pracę inżynierską o tytule Testowanie mutacyjne na platformie ASP.NET MVC. Od roku 2011 do 2013 w ramach pracy zawodowej tworzyłem aplikacje mobilne na platformę Android oraz aplikacje internetowe na platformie Java. Interesuję się przede wszystkim nowymi technikami programowania związanymi z platformami Java oraz.net, takimi jak wstrzykiwanie zależności, programowanie reaktywne, połączenie technik obiektowych z funkcyjnymi oraz przetwarzanie asynchroniczne. Regularnie biorę aktywny udział w spotkaniach grup zainteresowań językami Java i Scala Podpis studenta EGZAMIN DYPLOMOWY Złożył egzamin dyplomowy w dniu r. z wynikiem Ogólny wynik studiów Dodatkowe wnioski i uwagi Komisji

3 STRESZCZENIE Testowanie mutacyjne to technika pomiaru jakości i pomocy w rozwoju zestawu testów dla aplikacji. Swoje działanie opiera na tworzeniu i testowaniu mutantów zmodyfikowanych wersji oryginalnego programu zawierających błędy w celu sprawdzenia, jaką część błędów jest w stanie wykryć zestaw testów. Jednak na przeszkodzie większej popularności testowania mutacyjnego i łatwości jego zastosowania stoi długi czas potrzebny na uzyskanie kompletnych wyników oraz ich analizę przez użytkownika. W tej pracy magisterskiej staram się przedstawić pomysły na większą interaktywność testowania mutacyjnego, dzięki którym użytkownik szybciej może uzyskać użyteczne informacje. Pozwala to łatwiej zastosować ten proces w ramach zwinnych technik programowania. Praca opisuje także rozwój programu VisualMutator, narzędzia zintegrowanego ze środowiskiem Visual Studio, implementującego proces testowania mutacyjnego. Zostało ono użyte do przeprowadzenia eksperymentów sprawdzających usprawnienia wydajnościowe oraz szybkość uzyskiwania częściowych, użytecznych wyników. Słowa kluczowe: testowanie mutacyjne, jakość testów, interaktywność, programowanie sterowane testami INTERACTIVE MUTATION TESTING IN A SOFTWARE ENGINEERING PROCESS IN VISUAL STUDIO ENVIRONMENT Mutation testing is a technique for measuring and improving the quality of a test suite created for a computer program. It is based on creating and testing mutants modified copies of an original program, each containing artifically injected fault(s) in order to verify the error detection ability of the test suite. However, mutation testing is in need of wider adoption. The main obstacles are a long time required to compute complete results and for the user to analize them. This master thesis describes the ideas that can be used to enhance interactivity of the mutation testing process in order to deliver useful results earlier. It helps to integrate the process with agile programming techniques. The thesis also describes development of VisualMutator mutation testing tool a Visual Studio IDE extension. VisualMutator tool was used to perform experiments related to interactivity and performance of gaining useful results. Keywords: mutation testing, test quality, interactivity, test-driven development

4 Spis treści Wstęp 2 1. Testowanie mutacyjne Operatory mutacyjne Problem mutantów równoważnych Zmniejszanie liczby powstałych mutantów Szybsze tworzenie mutantów i ich testowanie Przegląd narzędzi do testowania mutacyjnego w C# i Java Narzędzia dla języka Java Narzędzia dla języka C# Lokalne i interaktywne testowanie mutacyjne Programowanie kierowane testami Ograniczenie zakresu działania testowania mutacyjnego Przyspieszenie otrzymywania wyników Interaktywne i lokalne rozwiązania w innych narzędziach Przebieg procesu interaktywnego Silnik mutacji Fazowość mutacji Deskryptor mutacji Zarządzanie kopiami programu źródłowego Reprezentacja modelu programu Implementacja programu VisualMutator Architektura Budowa silnika mutacji opartego na bibliotece CCI Kieszeń kodu oryginalnego Kieszeń mutantów Proces testowania mutantów Eksperymenty i wyniki Testowane projekty Konfiguracje biorące udział w eksperymentach Przewidywania co do wyników Scenariusz i wykonanie eksperymentu Zagrożenia co do ważności wyników Zakończenie 64 Przyszły rozwój programu VisualMutator Rozwój interaktywności w testowaniu mutacyjnym Bibliografia 67 Dodatek A: VisualMutator 2.0 User Manual 75 Dodatek B: Creating mutation operators for VisualMutator Dodatek C: Przegląd operatorów mutacyjnych w programie VisualMutator Dodatek D: Zawartość płyty CD 105 i

5 Wstęp Testowanie mutacyjne jest znanym i badanym od dawna [10] pomysłem na ocenę i usprawnianie zestawu testów przygotowanych dla programu komputerowego [27][48]. Stanowi proces uzupełniający wytwarzanie i testowanie kodu aplikacji wprowadzając dodatkową metrykę jakości zestawu testów obok badania pokrycia kodu. Zastosowanie testowania mutacyjnego opiera się na automatycznym wstrzykiwaniu sztucznych błędów do kodu programu (mutowaniu) i następnym uruchamianiu na nim zestawu testów w celu sprawdzenia, czy jest on w stanie znaleźć wprowadzone błędy. Wynikowe informacje mają za zadanie określić testy brakujące lub niskiej jakości, które niewystarczająco sprawdzają pokrywaną logikę programu. Ulepszenie na tej podstawie testów lub stworzenie nowych pozwoli zwiększyć szansę znalezienia jeszcze nie wykrytych, prawdziwych błędów w aplikacji[49]. Pomimo wyraźnych zalet stosowania testowania mutacyjnego prowadzących do zwiększenia jakości programu i jego testów, technika ta jest wciąż bardzo mało popularna poza światem akademickim i badawczym[48]. Dzieje się tak z powodu problemów związanych z zastosowaniem tego procesu: dużej złożoności obliczeniowej wymaganej do tworzenia i testowania wielu mutantów oraz konieczności silnego zaangażowania użytkownika. Przez lata znaleziono wiele częściowych rozwiązań problemów testowania mutacyjnego [25][43] [59][41], jednak nie wpłynęły one znacząco na zwiększenie popularności tej techniki. Wciąż istnieje potrzeba znalezienia podejścia, które przekonania do tego procesu większą część środowiska programistów. Proces testowania mutacyjnego zwykle ma postać wsadową jest wykonywany w jednym przebiegu w ramach całego kodu aplikacji i wszystkich jej testów. Powoduje to, że czas oczekiwania na wyniki jest długi, które następnie wymagają długotrwałej analizy wykonywanej przez programistę. W tej pracy staram się przekonać, że skuteczne może być także bardziej interaktywne podejście do testowania mutacyjnego. W takim sposobie interakcji, odpowiednie narzędzie powinno ułatwić sterowanie procesem oraz umożliwić uzyskanie możliwie najwięcej częściowych, ale użytecznych, wyników bardzo szybko. Wtedy potrzeba ingerencji człowieka jest bardziej równomiernie rozłożona w czasie działania procesu. Interaktywny sposób przeprowadzania testowania mutacyjnego można połączyć z podejściem lokalnym, w którym mutacje są ograniczone do pewnej funkcji programu nad którą w danej chwili pracuje użytkownik. W takim sposobie pracy, mutacje dotyczą jednej, aktualnie tworzej funkcji programu (najczęściej odpowiadającej jednej metodzie) i jej testów, co może lepiej sprawdzać się w iteracyjnym, przyrostowym tworzeniu testów 2

6 jednostkowych, razem z kolejnymi fragmentami aplikacji. Pomysły na interaktywność procesu testowania mutacyjnego zostały zaimplementowane w programie VisualMutator 2.0 (jako rozwinięcie wersji 1.0 powstałej ramach mojej pracy magisterskiej), będącym rozszerzeniem środowiska programistycznego Visual Studio. W rozdziale pierwszym tej pracy staram się przybliżyć najważniejsze informacje na temat testowania mutacyjnego, jego problemy i ich możliwe rozwiązania, które zostały zaproponowane na przestrzeni lat badań. Natomiast w rozdziale drugim znajduje się omówienie narzędzi do testowania mutacyjnego działających w ramach języków Java i C#, wraz z rozwiązaniami, jakie stosują. W rozdziale trzecim tej pracy staram się przekonać, że lokalny i interaktywny sposób pracy jest, jak uważam, bardziej zgodny ze zwinnymi (ang. agile) metodami programowania[1] oraz testowania, takimi jak programowanie sterowane testami TDD (ang. Test-driven Development)[4]. W dalszej części pracy, w rozdziale czwartym, znajduje się opis pomysłów dotyczących możliwej architektury narzędzia do mutacji, prowadzących do uzyskania większej interaktywności w testowaniu mutacyjnym. Wyszczególnione zostało pojęcie silnika mutacji, jako abstrakcyjnego podzespołu odpowiedzialnego za wprowadzanie mutacji, mogącego być częścią implementacji programu do przeprowadzania procesu testowania mutacyjnego. Opisane zostały funkcje, jakie silnik mutacji powinien posiadać ze względu na wsparcie dla interaktywności. W powiązaniu z silnikiem mutacji opisany jest rozwój programu VisualMutator (zaimplementowanego jako rozszerzenie środowiska Visual Studio) - praktycznego narzędzia dla platformy.net przygotowanego w ramach tej pracy magisterskiej. Od poprzedniej wersji programu, opisanej w mojej pracy inżynierskiej Testowanie mutacyjne na platformie ASP.NET MVC [58] nastąpiło wiele zmian dotyczących wydajności i elastyczności w celu wsparcia interaktywnego procesu mutacji. W rozdziale szóstym przedstawiłem eksperymenty, jakie zostały wykonane w związku z zaimplementowanymi pomysłami na większą interaktywność procesu testowania mutacyjnego. Dotyczą one pomiaru czasu wykonania pewnych operacji w zależności ustawień programu VisualMutator. Ich celem jest też pokazanie, jaki jest wpływ rozwiązań optymalizacyjnych na szybsze uzyskanie niektórych informacji o mutantach. 3

7 Rozdział 1 Testowanie mutacyjne Testowanie mutacyjne jest procesem, który podobnie jak pokrycie kodu testami (ang. code coverage), jest wykonywany w kontekście grupy testów dla aplikacji i prowadzi uzyskania metryki mówiącej o jakości testów i ich umiejętności wykrywania błędów. Pokazano, że testowanie mutacyjne może być skuteczniejsze w znajdywaniu błędów od badania pokrycia kodu [44][50], a także być lepszą metodą pomiaru ich jakości. Nawet w przypadku pokrycia 100% kodu testami testowanie mutacyjne pozwala znaleźć potrzebne, dodatkowe przypadki testowe. Testowanie mutacyjne (diagram 1.1) opiera się na pomyśle wprowadzania sztucznych, niewielkich błędów do kodu programu i sprawdzaniu czy zestaw testów jest w stanie je wykryć. Zgodnie z tym tokiem myślenia, jeśli testy potrafią znaleźć takie usterki, pomogą wykryć także prawdziwe błędy w aplikacji [49] już istniejące lub możliwe do popełnienia w przyszłości. Osiągnięcia badań nad testowaniem mutacyjnym ostatnich lat zostały podsumowane w pracach [27] [48]. Centralnym pojęciem w testowaniu mutacyjnym jest mutacja zmiana w programie utożsamiana z wprowadzanym błędem, oraz mutant - kopia źródłowego programu w której zastosowano jedną lub więcej mutacji. Mutacje są wprowadzane do kodu programu poprzez operatory mutacyjne - algorytmy, z których każdy specjalizuje się w tworzeniu błędów określonego typu (na przykład zamiana działania arytmetycznego na inne), starając się naśladować prawdziwe błędy możliwe do popełnienia przez człowieka. Mutanty, do których wprowadzono po jednej mutacji zwane są mutantami pierwszego poziomu. Sposób wprowadzania zmiany do kodu (opisany na diagramie jako kompilacja) jest silnie związany z językiem programowania lub platformą programistyczną w której tworzona jest testowana aplikacja. Dla języków kompilowanych, takich jak C++, będzie to wymagać kompilacji każdego mutanta do kodu maszynowego, stając się kosztowną obliczeniowo operacją. W przypadku języków związanych z maszynami wirtualnymi, np..net CLR lub Java VM, wykonujących kod pośredni pomiędzy kod maszynowym a kodem wysokiego poziomu, kosztu kompilacji można w dużej części uniknąć, edytując kod pośredni 4

8 źródłowego programu. Mutanty stworzone z pomocą operatorów mutacyjnych są poddawane procesowi sprawdzania przez zestaw testów przygotowany dla oryginalnego programu. Jeśli dla danego mutanta przynajmniej jeden test zakończy się niepowodzeniem (zasygnalizuje znalezienie błędu), taki mutant zwany jest zabitym (ang. killed). Natomiast jeżeli wszystkie testy zakończą się powodzeniem, mutant nazywany jest żywym (ang. live). W przypadku gdy mutant przetrwał proces testowania, wprowadzona zmiana nie została znaleziona. Znając zmianę w kodzie na bazie które powstał taki mutanta, programista jest w stanie stworzyć nowy test (lub poprawić istniejący) który wykryje błąd, tym samym zabijając mutanta. Rys Proces testowania mutacyjnego (źródło: P. Trzpil, Testowanie mutacyjne na platformie ASP.NET MVC [58]) Mutant, który przetrwał proces testowania nie zawsze sygnalizuje problem w zestawie testów. Przy tworzeniu mutantów nie da się całkowicie uniknąć ryzyka wprowadzenia zmiany, która nie powoduje odmiennego działania programu zmutowana logika jest równoważna źródłowej z punktu widzenia funkcji jaką spełnia. Mutant, w którym zastosowano taką mutację zwany jest równoważnym (ang. equivalent)[38]. Istnienie takich mutantów jest niepożądane, ponieważ przekłamuje wyniki testowania mutacyjnego, a w ogólnym przypadku nie jest możliwe automatyczne wykrycie ich wszystkich[6]. Rezultatem procesu testowania mutacyjnego jest wartość zwana wynikiem mutacji (ang. mutation score), zdefiniowana jako: wynik mutacji = liczba mutantów zabitych 100% (1.0.1) liczba mutantów żywych nierównoważnych Im bardziej wartość wyniku mutacji jest bliska 100%, tym jakość zestawu testów może 5

9 być uważana za wyższą. Jednak wynik przekłamują niewykryte mutanty równoważne, których problem znajdywania jest opisany w późniejszej części tego rozdziału. Metryka ta jest uzupełniająca względem wskaźników pokrycia kodu testami. Jest tak naprawdę jej rozszerzeniem, ponieważ z jednej strony najczęściej nie ma sensu testowanie mutacyjne fragmentów kodu niepokrytego testami (żadne mutanty stworzone w takim obszarze nie mogą zostać zabite), a z drugiej strony, jeśli pokrycie istnieje, wtedy wynik mutacji jest silniejszym wskaźnikiem Operatory mutacyjne Operatory mutacyjne to w ogólnym znaczeniu definicje rodzin zmian do wprowadzenia w kodzie. Dobrze zdefiniowany operator powinien wyczerpująco określać na jakich strukturach w kodzie programu zostanie zastosowany oraz jaki będzie rezultat zmiany (lub rodzina rezultatów) dla każdego przypadku. Operatory są tradycyjnie podzielone ze względu na rodzaj zmiany (podział nie jest silnie sprecyzowany) jaką mogą zastosować w kodzie programu i często posiadają standardową nazwę w ramach wszystkich badań nad testowaniem mutacyjnym. Przykładowo operator mutacyjny o nazwie ROR (ang. Relational Operator Replacement - podmiana operatora relacyjnego) zajmuje się zmianą operatora działania relacyjnego na jeden z innych (między innymi < na >, > na >=, całe wyrażenie na stałą wartość true). Mutanta z wprowadzoną zmianą najłatwiej zaprezentować za pomocą fragmentu kodu przedstawiającego program przed i po mutacji. We fragmentach 1.1 i 1.2 przedstawiony jest jeden z możliwych przypadków działania operatora ROR (ang. Relational Operator Replacement), zmieniający operator porównania mniejszy na mniejszy lub równy. Kod 1.1. Mutacja: Zmiana operatora relacyjnego (ROR) - kod C# oryginalny 1 void WorkLook(int[] input) 2 { 3 for (int i=0; i < input.count; i++) 4 { 5 DoWork(input[i]); 6 } 7 } Kod 1.2. Mutacja: Zmiana operatora relacyjnego (ROR) - kod C# zmodyfikowany 1 void WorkLook(int[] input) 2 { 3 for (int i=0; i <= input.count; i++) 4 { 6

10 5 DoWork(input[i]); 6 } 7 } Najczęściej każdą ze zmian możliwych do wprowadzenia w jednym miejscu w kodzie uważa się za osobną mutacje, więc biorąc pod uwagę liczbę operatorów mutacyjnych (najczęściej od kilku do dwudziestu-kilku) i liczbę struktur w kodzie obsługiwanych przez te operatory, liczba możliwych mutacji staje się bardzo duża. We fragmentach kodu 1.1 i 1.2 dla wyrażenia i < input.count operator ROR jest w stanie przygotować 7 różnych mutacji (zgodnie z implementacją opisaną w pracy [27]). Operator mutacyjny ROR należy do szerszej kategorii operatorów tradycyjnych (zwanych także standardowymi) pierwotnie zastosowanych w strukturalnych, imperatywnych językach programowania. Później, razem z rozwojem języków obiektowych powstała kategoria operatorów obiektowych, operujących na składni i rozwiązaniach tych języków, na przykład dla Java [35] i C# [20][13]. Można określić także inne kategorie, których operatory mają zastosowanie w węższych obszarach, takich jak osobne języki dostosowane do dziedziny (ang. domain specific languages) [22] lub są stworzone dla konkretnej biblioteki kodu i jej sposobów użycia. Operatory standardowe i obiektowe, które często pojawiają się w literaturze, są opisane w dodatku C. Jednak w wielu programach do testowana mutacyjnego powstałych niezależnie od środowiska akademickiego pojawiają się operatory mutacyjne o innych nazwach lub działaniu (na przykład te opisane w rozdziale 2, w podrozdziale poświęconym programowi PIT) Problem mutantów równoważnych Mutantem równoważnym nazywa się kopię źródłowego programu w którym wprowadzona mutacja nie powoduje żadnej zmiany jego działania. Taka sytuacja może zajść, jeśli: Mutacja spowoduje podmianę pewnej operacji na jej równoważną w danym miejscu w kodzie (fragment kodu 1.4); W wyniku zmiany, podczas działania programu zmieni się jego stan (na inny względem programu oryginalnego dla identycznych danych wejściowych), lecz nigdy nie zostanie wyprowadzony na zewnątrz, pozostanie lokalny dla metody, do której wprowadzono mutację. Tego typu mutanty mogą zostać jednak wykryte przy zastosowaniu słabych mutacji, opisanych w podrozdziale; 7

11 W takiej sytuacji nie jest możliwe stworzenie testu, który zabije mutanta. Kod 1.3. Program oryginalny 1 for (int i=0; i<10; i++) 2 { 3 DoWork(); 4 } Kod 1.4. Mutant równoważny z programem oryginalnym 1 for (int i=0; i!=10; i++) 2 { 3 DoWork(); 4 } Mutant może być też uważany za równoważnego w ramach wymaganej dokładności zestawu testów dla aplikacji. Przykładowo, mutacja instrukcji zapisu informacji debugujących do pliku z logiem może zostać uznana za równoważną, jeśli w danej aplikacji zapadnie decyzja o nie testowaniu poprawności takich operacji, uznanych za nieistotne z biznesowego punktu widzenia. Problem mutantów równoważnych w testowaniu mutacyjnym jest trudny do rozwiązania, ponieważ, jak udowodniono, nie jest możliwe ich wykrycie w ogólnym przypadku. Część z nich musi być odrzucona przez człowieka, a to wymaga dodatkowej pracy i czasu. Poczyniono jednak badania, w jaki sposób zmniejszyć negatywny wpływ takich mutantów na łatwość zastosowania testowania mutacyjnego. Jedno z nowszych badań[31] prezentuje częściowe rozwiązanie problemu, polegające na wykrywaniu podobnych fragmentów kodu w programie. Jeśli jeden z mutantów powstałych z takich fragmentów (zwanych mutantami lustrzanymi) zostanie uznany za równoważny, wtedy pozostałe także zostają uznane za równoważne i odrzucone. Mutanty można oceniać po ich wpływie (ang. impact) na wykonanie programu[25]. Praca [32] mówi o podejściu polegającym na łączeniu mutantów w pary stanowiące mutanty drugiego rzędu i pomiaru ich wzajemnego wpływu. Jeśli jeden z mutantów ma niewielki wpływ na drugi - nie prowadzi do silnej zmiany stanu wykonania pogramu, z wysokim prawdopodobieństwem może być uznany za równoważny. Inna praca opisuje wykrywanie mutantów równoważnych za pomocą porównania pokrycia kodu testami dla mutanta oraz programu oryginalnego. Jeśli są bardzo podobne, z większym prawdopodobieństwem można stwierdzić, że mutant jest równoważny[53]. 8

12 1.3. Zmniejszanie liczby powstałych mutantów W wyniku działania wielu operatorów mutacyjnych powstaje zazwyczaj bardzo duża liczba mutantów, która rośnie nawet kwadratowo wraz ze wzrostem liczby linii kodu w testowanym programie. Sprawia to, że w większych programach sprawedzenie wszystkich mutantów staje się trudne lub niemożliwe, co utrudnia uzyskanie przydatnych wyników. Od wielu lat prowadzone są badania nad poprawieniem skalowalności liczby mutantów, jednocześnie nie zwiększając błędu uzyskiwanych wyników. Do określenia różnych sposobów to umożliwiających używa się nazwy selektywne mutacje (ang. selective mutations) [41] Techniki zmniejszania liczby mutantów dzielą się na dwie gałęzie, dotyczące odpowiednio szukania niewielkiego, wystarczającego podzbioru operatorów mutacyjnych do użycia oraz odrzucania pewnych mutantów wybranych w losowy, lub nie, sposób. Techniki te można stosować zamiennie lub w połączeniu, jako uzupełniające się. Wyniki niektórych badań pokazały, że użycie podzbioru wszystkich mutantów zaproponowanych przez operatory mutacyjne często będzie prowadzić do podobnych wyników, co użycie ich wszystkich. Przede wszystkim, losowe wybranie małego podzbioru mutantów daje wyniki niewiele gorsze od nominalnych oraz bardzo podobne do wybrania ich w sposób kierowany. Techniki te są zwane próbkowaniem mutantów[65][33]. Prowadzone były też badania nad określeniem zbioru operatorów mutacyjnych, który byłby wystarczający, to znaczy: jego zastosowanie w typowych przypadkach prowadziłoby do uzyskania podobnych wyników jak przy użyciu wszystkich możliwych operatorów[45] [43]. Jednocześnie taki zbiór operatorów powinien zapewniać lepsze, bliższe liniowemu, skalowanie się liczby mutantów wraz ze wzrostem wielkości programu. W ostatnim czasie poczyniono eksperymenty dotyczące użycia tylko jednego operatora mutacyjnego, zwanego SDL (ang. Statement Deletion) [60]. Jego działanie polega na usunięciu pojedynczej instrukcji z kodu. Wyniki badań [12] pokazują, że zastosowanie go jako jedynego używanego operatora prowadzi do: Liniowej skalowalności liczby mutantów wraz ze wzrostem linii kodu (80% mniej mutantów dla jednego z programów); Produkcji 41% mniejszej liczby mutantów równoważnych; Łatwiejszego wykrywania mutantów równoważnych przez człowieka (zrozumienie efektu usunięcia instrukcji jest szybsze niż przeanalizowanie efektów działania większości innych operatorów mutacyjnych); Uzyskania wyniku mutacji przekłamanego o 8 punktów procentowych względem procesu używającego większej liczby operatorów; 9

13 1.4. Szybsze tworzenie mutantów i ich testowanie Wprowadzenie zmiany przy tworzeniu każdego mutanta wymaga modyfikacji jego kodu źródłowego, a tym samym, w przypadku kompilowanych języków programowania, jego osobnej kompilacji. Zazwyczaj znacznie zwiększa to czas potrzebny na stworzenie mutanta, dlatego jest niepożądane. Można ten problem zmniejszyć, wprowadzając zmiany w programie na poziomie kodu bezpośrednio wykonywanego przez maszynę wirtualną lub interpreter. W przypadku maszyn wirtualnych, np..net CLR lub Java VM, będzie to kod pośredni, który tworzony jest szybko, a jego odwzorowanie w kod wysokiego poziomu nie jest skompilowane. Dzięki temu opis mutacji może być łatwo zrozumiały dla użytkownika, a także łatwo jest wprowadzić błędy odpowiadające prawdziwym błędom możliwym do popełnienia w języku wysokiego poziomu. Inny pomysł na skrócenie czasu tworzenia mutantów to tworzenie jednego mutanta zawierającego w sobie wszystkie poszczególne. Taki program jest nazywany metamutantem lub schematem mutantów (ang. mutant schemata)[59], którego z założenia łatwo jest przekształcić w każdego z poszczególnych mutantów z osobna. Implementuje się to często poprzez wprowadzenie przełączników przy każdej zmianie w kodzie dotyczącej każdego właściwego mutanta z osobna. W takim programie, zawierającym wszystkie mutanty, pojedynczego mutanta uzyskuje się poprzez aktywację tylko jednego przełącznika oraz wyłączenie pozostałych. Wadami zastosowania pomysłu metamutanta są większa komplikacja logiki mutacji związana z aktywacją przełączników i mniejsza szybkość wykonania kodu mutantów. Dodatkowo, główna zaleta: brak potrzeby osobnej kompilacji każdego mutanta, ma mniejsze znaczenie w przypadku mutacji na poziomie języków programowania z opóźnioną kompilacją, np. język pośredni.net. W niektórych badaniach zastanawiano się też nad zmniejszeniem czasu testowania mutantów. Jednym z łatwych do wprowadzenia usprawnień jest wykonywanie testów tylko do momentu, w którym jeden z nich zabije mutanta, zamiast wykonywania wszystkich. W ten sposób tracona jest informacja o tym, ile testów znalazło błąd ukryty w mutancie (jednak ta informacja rzadko bywa przydatna interesujące są głównie żywe mutanty), ale może znacznie zmniejszyć czas wykonania testów i tym samym długość całego procesu. Bardziej złożonym podejściem, jakie było rozwijane w celu zmniejszenia czasu działania każdego testu z osobna są słabe mutacji (ang. weak mutations)[46]. Zgodnie z tym pomysłem, osłabione jest kryterium zabicia mutanta przez test, a zmiana działania programu w wyniku mutacji nie musi być wyprowadzona na zewnątrz metody, lecz jedynie musi być zauważona przez pewnego obserwatora śledzącego jej wykonanie. Słabe mutacje narzucają przez to słabsze wymagania na zestaw testów: potrzebne jest jedynie doprowadzenie do wykonania zmutowanej instrukcji (przy założeniu, że mutacja jest nierównoważna), nie są natomiast sprawdzane żadne asercje zawarte w testach. 10

14 Podsumowanie Na przestrzeni lat prowadzonych było wiele badań nad usprawnieniem i przyspieszeniem procesu testowania mutacyjnego, tak aby zwiększyć jego skuteczność w pomiarze jakości testów i w pomocy w ich ulepszaniu. Częściowo rozwiązano problem wykrywania mutantów równoważnych oraz zastosowano wiele technik prowadzących do zmniejszenia czasu trwania procesu. Stosując selektywne mutacje można przybliżyć się do liniowej skalowalności liczby mutantów wraz ze wzrostem wielkości projektu. Zastosowanie pomysłu schematu mutantów pozwala znacznie zmniejszyć czas trwania operacji tworzenia każdego mutanta, a słabe mutacje prowadzą do skrócenia czasu ich testowania. W kolejnym rozdziale tej pracy te i inne sposoby na usprawnienie wykonania procesu testowania mutacyjnego opisane są w ramach różnych narzędzi. 11

15 Rozdział 2 Przegląd narzędzi do testowania mutacyjnego w C# i Java Przez lata badań nad testowaniem mutacyjnym powstało wiele narzędzi implementujących ten proces w różny sposób. Z uwagi na podobieństwo platformy Java do środowiska.net używanego w ramach programu VisualMutator opisywanego w tej pracy, warto przyjrzeć się narzędziom dla obu platform Narzędzia dla języka Java Poniżej opisane narzędzia zostały stworzone dla programów działających na maszynie wirtualnej Java. Operują na kodzie źródłowym Java wysokiego poziomu lub na kodzie bajtowym, będącym odpowiednikiem kodu pośredniego platformy.net. Do wykonywania testów często używane jest narzędzie JUnit, będące popularną biblioteką dla testów jednostkowych na platformie Java Jester (wersja 1.22) Narzędzie rozwijane do roku Jester [73][68] jest uruchamiany z linii poleceń. Zgodnie z dokumentacją, jego uruchomienie jest dość kłopotliwe w konfiguracji, wymagając podania ścieżek do plików programu oraz dodatkowych bibliotek. Po uruchomieniu, program automatycznie wykonuje mutacje na poziomie języka Java (wprowadzając proste zmiany, np. zmiana stałej lub dodanie stałej logicznej do warunku), kompiluje mutanty, po czym uruchamia wykryty w plikach programu zestaw testów JUnit[42]. Wyniki procesu są zapisywane jako dokument HTML oraz XML, przedstawiając zmiany w kodzie dotyczące tylko mutantów żywych, dla każdego pliku źródłowego języka Java. 12

16 Jumble (wersja 1.2) Jumble (rozwijany w latach ) jest opisany jako program do testów mutacyjnych skutecznie używany w dużym komercyjnym projekcie (głównie na potrzeby którego został stworzony)[75]. Połączony z systemem ciągłej integracji (ang. continuos integration), jest uruchamiany z linii poleceń, przyrostowo na nowych klasach i testach JUnit. Implementacyjnie, Jumble operuje na programach na poziomie kodu bajtowego platformy Java co niweluje proces kompilacji mutantów Mujava (wersja 4) Program rozwijany i utrzymywany od 2003 roku na George Mason University[80][37]. Najnowsza wersja pojawiła się w roku Mujava pozwala na wybór maksymalnie 29 obiektowych operatorów mutacyjnych[47] oraz 15 standardowych[36] do użycia w procesie testowania mutacyjnego. Graficzny interfejs użytkownika programu służy do wyboru operatorów, plików testowanej aplikacji do przeprowadzenia mutacji oraz podglądu kodu stworzonych mutantów. Wygenerowane mutanty mogą być następnie testowane za pomocą wybranego pojedynczego testu. Mujava posługuje się różnymi sposobami generacji mutantów w zależności od operatora mutacyjnego. Dla operatorów behawioralnych (zmieniających działanie programu w ramach metody) stosuje metamutanta (schemat mutantów) kompilowanego tylko jeden raz z języka Java. Natomiast dla operatorów strukturalnych, działających np. na poziomie pól klasy, stosuje zmianę kodu bajtowego za pomocą mechanizmu refleksji platformy Java MuClipse (wersja 1.3) Do roku 2011 rozwijane było narzędzie MuClipse [81] - rozszerzenie środowiska Eclipse współpracujące z silnikiem mutacji programu Mujava. MuClipse pozwala wykonywać mutacje wybranego projektu (zarówno na poziomie kodu Java jak i kodu bajtowego), prezentuje wyniki (mutanty) w formie drzewa (podobnie jak w programie VisualMutator) oraz pozwala wyświetlić podgląd kodu opisujący zmianę w każdym mutancie. Utrudniona jest konfiguracja programu, pomimo integracji ze środowiskiem programistycznym. Wymagane jest podanie ścieżek do katalogów ze źródłami programu, skompilowanymi plikami, a także identyfikatory klasy do mutacji i testów do uruchomienia na mutantach. MuClipse pozwala w konfiguracji wybrać do użycia dowolne z operatorów mutacyjnych programu Mujava. 13

17 Judy (wersja 1.3) Program Judy jest dość nowym narzędziem, rozwijanym w roku Z zewnątrz działa podobnie do narzędzi opisanych powyżej: uruchamiany z linii poleceń wykonuje mutacje na poziomie kodu bajtowego Java, przeprowadza testowanie oraz zapisuje na dysku plik XML z wynikami. Od innych programów odróżnia go możliwość uruchomienia procesu testowania mutacyjnego równolegle w klastrze [74]. W wersji 1.3 programu pojawiła się możliwość wykonywania mutacji drugiego poziomu, wywoływania testów na podstawie pokrycia kodu oraz użycia operatorów mutacyjnych modyfikujących wywołania kolekcji Java Javalanche (wersja 0.4) Javalanche[54][52] to program do testów mutacyjnych uruchamiany przez narzędzie Ant, automatyzujące proces budowania projektu. Projekt posiada otwarte źródła i był rozwijany w latach Rozbudowany sposób konfiguracji programu pozwala na sprawdzenie poprawnego wykonania testów jeszcze przed testowaniem mutacyjnym, wygenerowanie mutantów do bazy danych (sekwencyjnie lub równolegle) oraz uruchomienie na nich procesu testowania. Javalanche używa wielu technik zmniejszenia czasu wykonania testowania mutacyjnego: działa w sposób równoległy, tworzy mutanty (na poziomie kodu bajtowego) poprzez stworzenie jednego metamutanta[59] zawierającego wszystkie oraz używa tylko 4 operatorów mutacyjnych będących zbiorem wystarczającym[45]. Oprócz tego, na każdym mutancie uruchamia tylko te testy, które pokrywają zmodyfikowany fragment programu. Program posiada też zaimplementowane zaawansowane sposoby radzenia sobie z mutantami równoważnymi. Jednym z nich jest sprawdzanie niedotrzymania niezmienników[21] pomiar wpływu mutacji na zmiany w działaniu programu, np. przy odwołaniach do zmiennnych. Innym jest śledzenie wpływu mutacji na obszar pokrycia testami[25] PIT (wersja 0.32) PIT[87] jest nowym i aktywnie rozwijanym od roku 2010 narzędziem testowania mutacyjnego dla platformy Java. Nie ma źródła w środowisku akademickim, jest tworzony przede wszystkim przez jednego autora. Wzmianki o nim pojawiały się w kilku dokumentach i książce[30] [51] [50]. Projekt początkowo powstał w celu implementacji pomysłu uruchamiania wielu testów JUnit równolegle do siebie, używając oddzielnych podzespołów 14

18 wczytujących klasy (ang. classloaders), tak aby odseparować w nich od siebie statyczny stan używany w testach. Później, przy powstawaniu PIT już jako narzędzia do testów mutacyjnych, przyświecał pomysł stworzenia rozwiązania bardzo szybkiego, które będzie mogło być używane w dużych komercyjnych projektach. Aby to osiągnąć, przy tworzeniu mutantów PIT operuje na kodzie bajtowym platformy Java oraz zawiera wiele rozwiązań skracających czas wykonania procesu. badania pokrycia kodu testami (pokrycie linii), tak aby wybrać dla każdego mutanta tylko te przypadki testowe, które wykonają zmodyfikowany kod. PIT zasługuje na uwagę (i obszerny opis w porównaniu do innych programów) z powodu ukierunkowania swojego rozwoju w stronę przemysłu oraz cech wyróżniających go, kładących bardzo silny nacisk na maksymalizację szybkości działania (lista powstała w wyniku analizy informacji na stronie internetowej PIT oraz kodu źródłowego): Używa konfigurowalnej liczby wątków do wykonania procesu testowania mutacyjnego; Jest kompatybilny z platformami obiektów naśladujących (ang. mocks); Posiada podział na fazy wyszukiwania mutacji i ich wprowadzania; Wykonuje mutację poprzez podmianę załadowanej klasy w pamięci na egzemplarz zawierający błąd, co trwa bardzo krótko i pozwala natychmiastowo przejść do etapu testowania; Kontroluje wykonanie pojedynczych testów oddzielnie od siebie, używa wielu kryteriów priorytetyzacji testów; Nigdy nie zapisuje zmutowanych plików na dysk twardy; Używa badania pokrycia kodu testami do wyznaczenia zakresu kodu do wygenerowania mutantów; Wykonuje wszystkie testy na mutantach w pamięci, bez użycia zewnętrznych programów; Wykonywane są tylko testy pokrywające zmianę w danym mutancie (pokrycie linii); Testy wykonywane są do chwili gdy jeden z nich zakończy się niepowodzeniem; Operatory mutacyjne w PIT W programie zawartych jest 14 operatorów mutacyjnych, z czego domyślnie aktywnych jest 7, a 3 są w stanie eksperymentalnym. Mutanty domyślnie wyłączone nie są zalecane 15

19 do użycia głównie z powodu generacji zbyt dużej liczby mutantów równoważnych lub zbyt łatwych do wykrycia. Operatory mutacyjne zawarte w PIT nie podążają za tradycją nazewniczą towarzyszącą testowaniu mutacyjnemu od jego początków. Utworzone są w wyniku innych założeń, tak, że nie istnieje jednoznaczne ich odwzorowanie w zbiór tradycyjnych operatorów (opisanych w dodatku C). PIT w pierwszych etapach rozwoju korzystał z kodu mutującego programu Jumble, przez co niektóre operatory mutacyjne są w działaniu podobne do operatorów w nim używanych[76]. Conditionals Boundary Mutator Zamienia operator porównania na jego podobny odpowiednik, np. >= na > lub < na <=. Jego działanie jest podzbiorem działania operatora ROR; Negate Conditionals Mutator Zamienia operator porównania na przeciwny, np. == na!= lub > na <=. Jego działanie jest podzbiorem działania operatora ROR i jest podobne do działania operatora Conditionals w programie Jumble; Remove Conditionals Mutator (domyślnie wyłączony) Zmienia wyrażenia w warunkach (if ) na stałe true. Powoduje to, że taki blok jest zawsze wykonywany. Działanie tego operatora można porównać z działaniem operatora ROR gdy zmienia on wyrażenie logiczne na stałą true. Jednak Remove Conditionals Mutator jest w swoim działaniu bardziej specyficzny, zmieniając wyrażenie na true niezależnie od liczby i rodzaju operacji relacyjnych w warunku; Math Mutator Zmienia operator arytmetyczny lub operacją bitową na inną, np. + na -, / na *, > > na < <. Ten operator łączy w sobie podstawowe działanie operatorów AOR, ASR, LOR, SOR. Działanie tego operatora jest identyczne z działaniem operatora Binary Arithmetic Operations z programu Jumble; Increments Mutator Zmienia operację inkrementacji na dekrementację i odwrotnie. Jest to operator równoważny z UOR oraz z operatorem Increments z programu Jumble; Invert Negatives Mutator Usuwa operację negacji zmiennych liczbowych. Jego działanie jest podzbiorem działania operatora UOI; Inline Constant Mutator (domyślnie wyłączony) Podmienia stałe znajdujące się w kodzie na inne, np. false na true lub 2 na 0. Operator został zaimplementowany na podstawie działania operatora Inline Constants w programie Jumble; Return Values Mutator Zmienia wartości zwracane z metod na inne, np. referencje do obiektów na null lub true na false. Działanie tego operatora jest identyczne z działaniem operatora Return Values z programu Jumble; 16

20 Void Method Call Mutator Usuwa z programu wywołania metod nie zwracających wartości; Non Void Method Call Mutator (domyślnie wyłączony) Usuwa z programu wywołania metod zwracających wartości zamieniając je na stałe odpowiedniego typu; Constructor Call Mutator (domyślnie wyłączony) Zmienia wywołania konstruktorów na wartości null. Najczęściej powoduje tworzenie wielu łatwych do zabicia mutantów; Experimental Inline Constant Mutator (eksperymentalny) Działa podobnie jak Inline Constant Mutator zachowując się inaczej dla pewnych wartości stałych, np. zmienia Short.MAXVALUE na Short.MINVALUE; Experimental Member Variable Mutator (eksperymentalny) Zmienia przypisania stałych do pól klas na przypisania wartości domyślnych, np. 0. Jednak może stworzyć mutanta równoważnego, jeśli taka wartość jest już do danego pola przypisana. Operator jest podobny w działaniu do JID Member variable initialization deletion (Field initialization deletion); Experimental Switch Mutator (eksperymentalny) Zmienia wartości znajdujące się w etykietach struktury switch na inne. Operator został zaimplementowany na podstawie działania operatora Switch Statements w programie Jumble; Analiza przyrostowa w PIT Ważną funkcją PIT, szczególnie dla dużych projektów, jest możliwość zastosowania analizy przyrostowej, która śledzi zmiany w plikach źródłowych pomiędzy sesjami testowania mutacyjnego. Dzięki temu przy kolejnym uruchomieniu procesu testowania mutacyjnego działa on tylko na kodzie, który jest związany z ostatnimi zmianami. Jednak określenie stref wpływu śledzonych zmian na nowe mutacje w porównaniu z poprzednim wynikiem mutacji oraz mutantami żywymi i zabitymi nigdy nie może być idealne i gwarantować pewność nowego wyniku. Rozszerzenia PIT PIT umożliwia tworzenie kilku rodzajów rozszerzeń, które zmieniają działanie programu w ramach procesu testowania mutacyjnego. Możliwa jest implementacja komponentów odbierających notyfikacje co do działania programu, filtrowanie mutantów, priorytetyzacja wykonania testów, a także podmiana całego silnika mutacji na własny. Nie jest możliwa 17

21 implementacja nowych operatorów mutacyjnych poprzez rozszerzenia, a jedynie w ramach zmian w kodzie samego programu Narzędzia dla języka C# Nester (wersja 0.3) Nester[82] to program stworzony na wzór narzędzia Jester dla języka Java, posiadający graficzny interfejs użytkownika. Działa na poziomie kodu C#, jednak unika kompilacji kodu oddzielnie dla każdego mutanta, ponieważ wprowadza do kodu specjalne wywołania metod, pozwalające tworzyć mutanty dynamicznie. Jest to sposób implementacji metamutanta, opisywanego w poprzednim rozdziale. Nester także w dość nietypowy sposób implementuje zastosowanie operatorów mutacyjnych. Nie używa tradycyjnego pojęcia operatora, a zamiast tego operuje na pojedynczych transformacjach wyrażeń języka C#. Reprezentuje transformacje w formie dokumentu XML (modyfikowalnego przez użytkownika) opisującego zmiany, jakie mają się dokonać na wybranych elementach gramatyki języka C#. Taki opis zmian zawiera wywołania metod specjalnych, przekierowujących działanie testowanej aplikacji do logiki programu Nester pozwalających na dynamiczne tworzenie mutantów. W pomysłowy sposób przedstawiana jest wizualizacja mutantów. W plikach źródłowych testowanego programu podświetlone są fragmenty kodu odpowiadające grupom mutantów stworzonych w danym miejscu. Jeśli wszystkie mutanty z tej grupy zostały zabite, fragment podświetlony jest kolorem zielonym. Jeśli przynajmniej jeden mutant przeżył, kolorem podświetlenia jest czerwony. Natomiast kolorem niebieskim oznaczone są fragmenty, w których stworzono mutanty, ale nie zostały one pokryte przez żaden test. Program był rozwijany w latach i działa jedynie na platformie.net w wersji 2.0, co nie pozwala go użyć przy większości obecnie tworzonych projektów CREAM (wersja 3.0) CREAM (Creator of Mutants)[70] jest narzędziem rozwijanym w Instytucie Informatyki PW od roku 2007[16][17][18][15]. Posiada rozbudowany graficzny interfejs użytkownika, pozwalający na wczytanie pliku rozwiązania programu Visual Studio z projektem do mutacji, wybranie operatorów mutacyjnych do użycia, użycie innych funkcji oraz uruchomienie procesu tworzenia mutantów w kodzie projektu i ich testowania. Pozwala na użycie trybów selektywnych mutacji (ang. selective mutations), próbkowania mutacji (ang. muta- 18

22 tion sampling) oraz grupowania mutacji (ang. mutation clustering). Opcjonalnie, CREAM umożliwia wczytanie wyników analizy pokrycia kodu i użycie ich do określenia zbioru testów wykonywanych na mutantach. Rzadko spotykaną w innych programach funkcją jest także możliwość zapisania częściowych wyników procesu na dysku, np. wygenerowanych mutantów i późniejszej kontynuacji ich analizy. Patrząc od strony implementacyjnej, program wykonuje mutacje na poziomie kodu C# (każdy mutant wymaga kompilacji) oraz posiada 8 operatorów standardowych i 18 obiektowych ILMutator (wersja 1.0) ILMutator jest aplikacją stworzoną w Instytucie Informatyki PW w roku 2011 z myślą o mutacjach programów.net na poziomie kodu pośredniego [14]. Wśród operatorów mutacyjnych programu przeważają operatory obiektowe (11) nad standardowymi (4). Możliwość wyboru operatorów, jak i załadowania podzespołów do przeprowadzenia testowania mutacyjnego daje graficzny interfejs użytkownika Ninja Turtles (wersja 0.8) NinjaTurtles[83] to narzędzie, które było rozwijane w roku Jest projektem otwartoźródłowym, stworzonym przede wszystkim przez jedną osobę. Poza kodem źródłowym i częściową dokumentacją na jego stronie domowej, niewiele informacji można o tym programie znaleźć (na przykład dostępna jest częściowa kopia niedostępnego aktualnie bloga autora[9]). Istnieją dwa sposoby uruchamiania NinjaTurtles: 1. Poprzez dodanie referencji do jego bibliotek do testowanego projektu i stworzenie testów mutacyjnych, czyli testów jednostkowych oznaczonych specjalnym atrybutem MutationTest, odwołujących się do biblioteki NinjaTurtles w celu przekazania identyfikacji klasy i metody do przetestowania mutacyjnie. Ten sposób uruchamia NinjaTurles wewnątrz testu jednostkowego. Dla każdej klasy i metody, która zostać przetestowana mutacyjne powinien powstać osobno przygotowany test mutacyjny. 2. Poprzez osobny program NinjaTurles.ConsoleRunner w którym możliwe jest zewnętrzne uruchomienie procesu po podaniu podzespołu zawierającego testy dla aplikacji oraz identyfikatora klasy i/lub metody do mutacji. 19

23 NinjaTurtles wykonuje mutacje wybranej klasy lub metody na poziomie kodu IL za pomocą kilku standardowych operatorów mutacyjnych (np. zmiana operatora bitowego, arytmetycznego, zmiana warunku bloku warunkowego). Następnie, niezależnie od sposobu uruchomienia, automatycznie określa przypadki testowe do wykonania. Zapewnia to poprzez przeszukanie kodu wszystkich testów, znajdujących się w podzespole z testami i wybranie tych, które odwołują się do klasy lub metody, dla której dany był identyfikator. Tylko one używane są w procesie testowania, wykonywanego równolegle dla wszystkich mutantów i tylko do pierwszego testu zakończonego porażką. Na koniec procesu program prezentuje wyniki w postaci tekstowej lub HTML. Nie jest prezentowany dokładny kod zawierający mutacje, lecz liczba mutantów (zabitych i wszystkich) dla każdej linii pliku źródłowego języka wysokiego poziomu VisualMutator (wersja 1.0) VisualMutator[19] to program stworzony jako rozszerzenie środowiska Visual Studio w ramach mojej pracy inżynierskiej, Testowanie mutacyjne na platformie ASP.NET MVC [58] w roku Jest narzędziem implementującym proces testowania mutacyjnego aktualnie otwartego projektu w środowisku deweloperskim. Oprócz integracji ze środowiskiem pracy jego inną cechą charakterystyczną jest rozszerzalność o nowe operatory mutacyjne w formie wtyczek ładowanych dynamicznie, w trakcie działania programu. Program opiera swoje działanie na mutacjach kodu pośredniego i dzięki temu może przetwarzać dowolny program napisany na platformie.net. Jednak w ramach prac nad wersją 1.0 zostały zaimplementowane wyłącznie operatory mutacyjne przydatne w programach zaimplementowanych w oparciu na platformie ASP.NET MVC, służącej do budowania aplikacji internetowych Specification-based Mutation Engine Narzędzie używające pojęcia silnika mutacji w ramach swojej nazwy, opierające swoje działanie na Kontraktach Kodu (ang. Code Contracts) zostało stworzone dla mutacji na poziomie kodu C# i Visual Basic [3][64]. Powstało w ramach prac badawczych nad tworzeniem tylko poprawnych mutantów, stosując się do kontraktów zawartych w kodzie. Dzięki temu tylko mutanty spełniające dane warunki przed i po oraz niezmienniki zawarte w mutowanej metodzie zostaną ostatecznie stworzone i przetestowane. Mutation Engine został zaimplementowany jako rozszerzenie środowiska Visual Studio 2010 i dzięki temu może używać informacji o aktualnie otwartych projektach w celu lokalizacji plików źródłowych, zewnętrznych bibliotek oraz innych zasobów używanych przez aplikację[64]. 20

24 Używając Kontraktów Kodu, aplikacja jest w stanie odrzucić mutanty, w których wykonanie zmienionego kodu spowoduje jawne złamanie istniejących kontraktów. Dokument nie mówi jednak o sposobie w jaki odbywa się to - czy wykrycie złamania kontraktu odbywa się za pomocą analizy statycznej, czy dynamicznej, lub czy odbywa się za pomocą samego kompilatora języka C# lub Visual Basic wspierającego Kontrakty. Autorzy nie podają również informacji o ograniczeniach wykrywania złamania kontraktu - w jakich przypadkach mutant nie zostanie uznany za niepoprawny. Mowa jest tylko o sprawdzeniach złamania kontraktu podczas działania programu (runtime), nie wyjaśniając w jaki sposób odbywa się odrzucanie mutantów bez wykonywania na nich testów. Aplikacja operuje na plikach źródłowych C# i Visual Basic (VB), przeprowadzając proces kompilacji do języka pośredniego (IL). Czyni to jednak w ogólny sposób dla obu języków, czasami prowadząc do powstania niemożliwych do skompilowania mutantów (na przykład po użyciu symbolu % w języku Visual Basic, co jest niepoprawne - odpowiednikiem tego działania jest słowo kluczowe mod). W innych przypadkach tworzy mutanty poprawne z punktu widzenia kompilatora VB jednak nie posiadające zmienionego działania w ramach tego języka (na przykład zmiana przypisania na przypisanie dodające stałą do zmiennej). Dokument informuje o możliwych zmianach w przyszłych wersjach programu unikających powyższych problemów. W ramach eksperymentów, autorzy programu przeprowadzali też działania polegające na próbach poprawy błędu wprowadzonego za pomocą pewnego operatora mutacyjnego, za pomocą innych operatorów. Niektóre z poprawek prowadzą do powstania mutantów równoważnych, a niektóre powodują odwrócenie poprzednio wprowadzonej zmiany. Brak jednak informacji o powodach przydatności automatycznego poprawiania zmian. Mutation Engine posiada zaimplementowanych 16 operatorów standardowych, w tym: AOR, ROR, COR, LOR, SOR, a także ich usuwające i wstawiające odpowiedniki, np. AOI - Arithmetic Operator Insertion oraz PR - Parameter Replacement i LVR - Local Variable Replacement (opisanych w dodatku C). 21

25 Rozdział 3 Lokalne i interaktywne testowanie mutacyjne Patrząc na działanie większości narzędzi opisanych w poprzednim rozdziale, domyślne jest w nich przeprowadzanie procesu testowania mutacyjnego w kontekście całego programu. Niektóre z nich pozwalają wybrać podzbiór kodu programu do przeprowadzenia mutacji, jednak w sposób tekstowy, wymagający podania identyfikatora tego fragmentu, co może nie być wygodne dla użytkownika, utrudniając szybkie przeprowadzenie procesu. Całościowe podejście do procesu testowania mutacyjnego powoduje, że najczęściej uruchamiana jest długa operacja, która generuje rozległe wyniki wymagające analizy. Patrząc na niską popularność testowania mutacyjnego w przemyśle, być może taka skala przeprowadzania testów mutacyjnych nie jest odpowiednia, ponieważ uwypukla problemy związane z długim czasem trwania oraz potrzebą odrzucenia mutantów równoważnych. W tym rozdziale staram się przybliżyć pomysły na taką zmianę sposobu przeprowadzania testowania mutacyjnego, aby te problemy były mniej odczuwalne dla użytkownika Programowanie kierowane testami W ciągu ostatnich lat popularność zdobywały zwinne (ang. agile) techniki tworzenia oprogramowania[1][66]. Oparte na elastycznej adaptacji do zmian zachodzących w wymaganiach lub technologii używanej w projekcie, zmieniły sposób pisania kodu w proces iteracyjny, łatwy do modyfikacji i zabezpieczony testami. Technika zwana programowaniem kierowanym testami (TDD - ang. Test-driven development)[4] [24][7] jest często używana w ramach podejść zwinnych jako ukierunkowany i skupiony na celu proces tworzenia kodu. Polega na wielokrotnym powtarzaniu sekwencji działań: 1. Stworzenie testu jednostkowego specyfikującego wymagane działanie niewielkiej funkcji programu. W tym kroku warto też wygenerować odpowiednie zaślepki w kodzie związane z testowaną funkcją i doprowadzić do poprawnej kompilacji programu; 22

26 2. Uruchomienie testu i upewnienie się, że kończy się niepowodzeniem; ma to na celu wyeliminowanie ewentualnych błędów w konfiguracji lub w kodzie powodujących, że test zawsze będzie kończył się sukcesem; 3. Napisanie najbardziej prostej, poprawnej implementacji funkcji programu; 4. Uruchomienie testu i upewnienie się o jego poprawnym zakończeniu; 5. Refaktoryzacja kodu funkcji, ulepszająca jego strukturę i przygotowująca pod łatwiejsze wprowadzenie późniejszych zmian; 6. Ponowne uruchomienie testu i upewnienie się o jego sukcesie; Główną zaletą tego podejścia tworzenia testu przed implementacją danej funkcji jest skupienie się na celu pisanego fragmentu aplikacji: co powinien robić, jaką wartość zapewniać? Jak sprawdzić, że działa zgodnie z oczekiwaniami? Odpowiadanie na te pytania poprzez tworzenie testu (oraz prostej implementacji) często przyśpiesza pracę, ponieważ kładzie nacisk na myślenie przed implementacją oraz ogranicza tworzenie kodu niepotrzebnego z punktu widzenia testu (chociaż jedno z badań pokazało niewielki wpływ TDD na produktywność zespołu[61]). Taki przyrostowy i iteracyjny sposób pisania testów i aplikacji w technice TDD jest jednocześnie lokalny. Najczęściej programista zajmuje się w danej chwili jednym testem (lub niewielką ich grupą) i pokrywaną przez niego funkcją. Po wykonaniu kilku iteracji procesu, dany fragment programu uznawany jest za skończony. Podczas tej pracy pomocne jest lokalne uruchamianie pomiaru pokrycia kodu tworzonej funkcji, tak aby sprawdzić, czy zbiór kilku napisanych do niej testów jest wystarczający. Na podobnej zasadzie powinna działać proponowana technika lokalnego, interaktywnego testowania mutacyjnego, dobrze wpisując się w ten proces Ograniczenie zakresu działania testowania mutacyjnego Chcąc uzupełnić technikę TDD o użycie testowania mutacyjnego, potrzebny jest taki tryb jego uruchomienia, który będzie działał szybko i w łatwy sposób dostarczy użyteczne informacje o testach dla funkcji, nad którą aktualnie pracuje użytkownik. Jedna funkcja programu zazwyczaj jest implementowana poprzez metodę lub zbiór metod w pojedynczej klasie projektu. Używając techniki TDD użytkownik pracuje w danej chwili nad pojedynczym testem, który należy do grupy kilku testów pokrywających tę 23

27 funkcję (np. sprawdzających kilka przypadków danych wejściowych metody). Z kolei metoda często stanowi część składową funkcji w ramach jednej klasy. Dlatego ograniczenie zakresu testowania mutacyjnego pozwoli oszczędzić czas potrzebny na działanie procesu oraz zmaksymalizować ilość użytecznych informacji przydatnych tylko w ramach pewnej metody lub zbioru metod. Jednak wybór zakresu o odpowiedniej granulacji nie jest oczywisty. Dobrą i często stosowaną praktyką testowania jednostkowego jest takie tworzenie testów, aby jeden test sprawdzał tylko jedną metodę programu. Oznacza to, że dla każdej metody będzie istniała oddzielna grupa testów, rozłączna z grupami testów sprawdzających pozostałe metody. Korzystając z tej wiedzy można stwierdzić, że kontekstem działania procesu testowania mutacyjnego powinna być wybrana jedna metoda (mutacje) oraz grupa testów, które sprawdzają jej działanie (testowanie mutantów). W ten sposób proces będzie trwał krótki czas i przyniesie przydatne informacje o testach dla metody. Na taki sposób użycia pozwala przygotowany w ramach tej pracy program VisualMutator oraz opisane w poprzednim rozdziale narzędzie NinjaTurtles. Patrząc z innej strony, być może nie pojedyncza metoda, lecz jedna klasa jest dobrym poziomem granulacji. Zdarza się, że kilka metod klasy operuje na wspólnym stanie i jest ze sobą koncepcyjnie ściśle związana. Także dobre praktyki (SOLID)[40] mówią o klasie, jako o bycie stanowiącym zamkniętą całość, w którym zawarta powinna być tylko jedna funkcjonalność. Dodatkowo, wiele operatorów mutacyjnych działających na polach będzie mogło być zastosowanych tylko w przypadku rozciągnięcia zakresu testowania mutacyjnego do całej klasy. Dobrym sposobem działania, który korzysta z obu tych strategii, może być szybkie testowanie mutacyjne każdej metody po stworzeniu dla niej testów, natomiast po zakończeniu tworzenia wszystkich jej metod oraz testów - wykonanie dłużej trwającego procesu w ramach całej klasy. Można zastanowić się nad jeszcze szerszymi zakresami testowania mutacyjnego, takimi jak klasa wraz jej klasami pochodnymi, klasa wraz z innymi klasami, z którymi się komunikuje lub wszyskie klasy w jednej przestrzeni nazw. Są to jednak duże zakresy, których zastosowanie ciężko uzasadnić w ramach techniki TDD, która w przypadku testowania jednostkowego dotyczy funkcji co najwyżej jednej klasy na raz Przyspieszenie otrzymywania wyników Pomysł lokalnego przeprowadzania testów mutacyjnych pozwala zmniejszyć całkowity czas potrzebny na wykonanie procesu oraz prowadzi do otrzymania tylko wartościowych wyników w ramach testowania wybranej metody. Nadal jednak liczba mutantów do przetestowania może być duża jest zależna od wielkości metody i liczby aktywnych operatorów 24

28 mutacyjnych przy czym czas oczekiwania na wyniki wszystkich będzie długi i niepraktyczny w ramach techniki TDD. Zastosowanie tradycyjnych sposobów na przyśpieszenie procesu (selektywne mutacje, słabe mutacje) może zmniejszyć skalę problemu, jednak warto też zastanowić się na innym, ortogonalnym rozwiązaniem. Wyniki testowania każdego mutanta z osobna stanowią pewną wartość, niezależnie od wyników testowania pozostałych mutantów. Użytkownik chce jak najszybciej uzyskać informację, czy mutant przeżył (kryterium jego zabicia jest dowolne), zobaczyć kod prezentujący zmianę oraz upewnić się co do jego nierównoważności, tak aby móc zastanowić się nad możliwym nowym testem. Dlatego można umożliwić (w sposób opcjonalny) większą interakcję użytkownika z narzędziem, wymagającą jego aktywnego zaangażowania, która jednak może dodatkowo przyspieszyć uzyskanie częściowych wyników zgodnie z jego wyborami. Elementami takiej interakcji mogą być: Nadanie wysokiego priorytetu testowania wybranego mutanta; Wyświetlenie podglądu kodu zmiany wybranego mutanta, przetestowanego lub nie, możliwe w dowolnej chwili w trakcie trwania całego procesu; Głównym problemem w osiągnięciu dużej skuteczności takiej interakcji jest przekazanie użytkownikowi informacji pomocnych w dokonaniu wyboru mutanta, z którym interakcja przyniesie korzyść. W przeciwnym przypadku, nieprzetestowane mutanty będą zbyt podobne do siebie, uniemożliwiając inteligentny wybór pewnego z nich spośród wszystkich Interaktywne i lokalne rozwiązania w innych narzędziach Opisany sposób przeprowadzania procesu testowania mutacyjnego przypomina sposób użycia narzędzi Pex i CodeDigger[86][56][67]. Są to rozszerzenia środowiska Visual Studio służące do wyszukiwania interesujących danych wejściowych dla wybranej metody. Ich działanie opiera się na technice zwanej dynamicznym wykonaniem symbolicznym (ang. dynamic symbolic execution) poszukującej takich przypadków wartości wejściowych, które doprowadzą do wykonania możliwie największej liczby ścieżek w kodzie metody [63]. Pex dodatkowo pozwala automatycznie wygenerować parametryzowane testy jednostkowe zapewniające pokrycie tych przypadków[55]. Na rysunku 3.1 zaprezentowana jest metoda, w kontekście której uruchomiony został proces wyszukiwania interesujących wartości wejściowych i znalazł dwie wartości prowadzące do niepożądanych wyjątków. 25

29 Rys Kontekst i wyniki programu CodeDigger Interakcja z użytkownikiem narzędzi Pex i CodeDigger czerpie swoje możliwości z integracji ze środowiskiem programistycznym. Użytkownik wybiera pewną metodę i uruchamia proces działania programu za pomocą myszy lub używając skrótu klawiszowego. Zakres pracy użytkownika jest tym szybkim sposobem ograniczony do niewielkiego fragmentu programu, o którym łatwo jest myśleć i natychmiast uzyskać wyniki potrzebne do dalszych działań w tym samym miejscu. Po wyszukaniu przez program przypadków kombinacji danych, użytkownik może zobaczyć o każdym z nich dodatkowe informacje (np. czy spowodował wystąpienie wyjątku) i, w przypadku Pex, wygenerować testy jednostkowe Przebieg procesu interaktywnego W ujęciu ogólnym, kroki działania testowania mutacyjnego można opisać jako (zgodnie z rysunkiem 1.1 z rozdziału 1): 1. Stworzenia mutantów poprzez zastosowanie operatorów mutacyjnych na kodzie programu - całym lub pewnym jego podzbiorze; 2. Uruchomienia pewnego zbioru testów na wszystkich mutantach lub ich podzbiorze; 3. Uzyskanie informacji o jakości zbioru testów i o brakujących testach; 26

30 Jeśli zostanie znaleziony wygodny sposób na zawężenie zakresu testowania mutacyjnego, wtedy te czynności będą mogły być łatwo wykonywane przez programistę pomiędzy kilkoma iteracjami techniki TDD. Wybór metody do mutacji to odpowiedzialność użytkownika. Pisząc pewien fragment programu (funkcję) i testy do niej, chce on rozpocząć proces testowania mutacyjnego w ramach metody, która odpowiada tworzonej funkcji. Sposób w jaki się to może odbyć jest dowolny, jednak aby proces uruchomić jak najszybciej, powinien posiadać on wsparcie środowiska programistycznego, w którym aplikacja jest rozwijana. Wtedy identyfikacja odpowiedniej metody może odbyć się automatycznie, na przykład na podstawie położenia kursora w kodzie, podobnie jak w programach Pex i CodeDigger. Gdy już metoda do mutacji została wybrana, problemem jest określenie zbioru testów które powinny zostać użyte do wykrywania mutantów. Powinna to być tylko ta grupa testów, która bezpośrednio wywołuje wybraną przez użytkownika metodę i nakłada asercje na jej wyniki lub sposób działania. Użycie tych kryteriów w praktyce jest trudne, ponieważ silnie zależy od przyjętych konwencji i używanych narzędzi do tworzenia testów. Jednym ze sposobów przybliżenia tych informacji jest zastosowanie pomiaru pokrycia kodu dla danej metody. Podsystem mierzący pokrycie kodu powinien udostępnić w takim przypadku informacje o konkretnych testach, których uruchomienie doprowadziło do wykonania metody. To rozwiązanie można zastąpić jeszcze prostszym, po zauważeniu, że typowe testy jednostkowe napisane zgodnie z dobrymi praktykami ([5]) posiadają budowę pozwalającą łatwo odróżnić wywołanie testowanej metody od innych. Są to wywołania metod na referencji typu konkretnej klasy w odróżnieniu od wywołań na referencjach typu jej interfejsu, najczęściej odpowiadającym obiektom naśladującym (ang. mocks). Podsumowując, w ramach takiej implementacji, interaktywny sposób pracy z testowaniem mutacyjnym można opisać poprzez następujący przypadek użycia: 1. Użytkownik wybiera klasę lub pewną metodę programu poprzez wybranie pewnego elementu graficznego za pomocą myszy; 2. System mutacji wyszukuje wszystkie przypadki testowe, które odwołują się do danej klasy lub metody i prezentuje je użytkownikowi. Użytkownik przed zatwierdzeniem ma możliwość odrzucenia niektórych przypadków testowych; 3. System mutacji rozpoczyna przygotowanie do procesu testowania mutacyjnego wybranego zakresu kodu. Tworzy mutanty i rozpoczyna ich testowanie za pomocą pewnego podzbioru przypadków testowych; 4. Użytkownik obserwuje wyniki. Ma też możliwość przeglądania zmian wprowadzonych w każdym mutancie; 27

31 5. Użytkownik może także oznaczyć wybranego mutanta (przetestowanego lub nie) jako równoważnego; Proces testowania mutacyjnego w trybie interaktywnym będzie uruchamiany wiele razy, więc sumarycznie jego wykonanie na całym programie zajmie więcej czasu niż proces wsadowy. Jednak będzie łatwiejszy do wykonania dla mniejszej części programu na raz. 28

32 Rozdział 4 Silnik mutacji Typowe narzędzie (system testowania mutacyjnego) implementujące proces testowania mutacyjnego zawiera w sobie, między innymi, dwa podzespoły, mogące być intuicyjnie od siebie odseparowane: moduł mutacji oraz moduł testujący. Wynika to z budowy samego procesu, w którym w związku z pojedynczym mutantem wykonywane są 2 czynności: Silnik mutacji: Stworzenie mutanta, poprzez wprowadzaniem mutacji do kodu, używając do tego celu operatorów mutacyjnych (automatyczne przygotowanie danych do procesu weryfikacji); Moduł testujący: Sprawdzenie, czy zestaw testów jest w stanie znaleźć błąd opisany przez mutanta (automatyczna weryfikacja jakości testów na podstawie przygotowanych wcześniej danych); Ustalenia o których mowa w kolejnych sekcjach rozdziału dotyczą budowy pierwszego modułu silnika mutacji jako sprawnego podsystemu do wprowadzania mutacji w środowisku.net, na poziomie kodu pośredniego, nie stosującego modyfikacji za pośrednictwem metamutanta. Każdy mutant jest niezależnym programem, odseparowanym od innych. Pominięty jest także problem wykrywania mutantów równoważnych. Ważne jest określenie roli operatorów mutacyjnych w kontekście silnika mutacji. Z jednej strony, patrząc na całość systemu mutacji, operatory są częścią silnika, ponieważ biorą ścisły udział w modyfikacjach kodu. Jednak w ramach silnika mutacji, pomimo ich pozycji w centrum logiki mutacyjnej, korzystne jest ich odseparowanie od właściwego silnika i uznanie za osobne byty. Pozwala to uspójnić działanie silnika mutacji i uniknąć duplikacji jego logiki. Silnik mutacji jest jeden, natomiast operatorów mutacyjnych - wiele, przy czym ich liczba może być zmienna. W kolejnych sekcjach rozdziału operatory mutacyjne są rozumiane jako osobne moduły (pasywne), współpracujące z silnikiem mutacji (aktywnym). 29

33 4.1. Fazowość mutacji Głównym zadaniem silnika mutacji jest tworzenie mutantów na podstawie plików źródłowych programu oraz operatorów mutacyjnych. Tutaj ważne jest ustalenie, jaki stopień mutacji zostanie zastosowany (liczba zmian w pojedynczym mutancie), a także modyfikacje procesu, takie jak selektywne mutacje. Nie mniej ważny jest jednak sposób implementacji samego przebiegu wprowadzania zmiany, ponieważ od tego zależy łatwość zastosowania innych aspektów procesu, związanych z wydajnością obliczeniową i pamięciową. Zgodnie z tradycyjną definicją mutanta, każdy z nich jest zmodyfikowaną kopią programu, którą (jako jedną z wielu podobnych) tworzy się poprzez uruchomienie operatorów mutacyjnych na programie źródłowym. Przez to istnieje problem tworzenia i przechowywania (w pamięci RAM lub w pamięci masowej) dużej liczby mutantów, z których dodatkowo część należy odrzucić z uwagi na zastosowanie sposobów redukcji ich liczności, na przykład selektywnych mutacji. Jednak problemu przechowywania mutantów można całkowicie uniknąć po zauważeniu, że mutantem nie musi być zmodyfikowany program, lecz zamiast tego para {oryginalny program, zmiany do wykonania}, gdzie grupa zmian do wykonania w przypadku mutacji pierwszego poziomu będzie miała liczność 1. Jako że program oryginalny jest w każdym przypadku identyczny, jako każdego mutanta przechowywać tylko opis zmian do wykonania. Faktyczne stworzenie mutanta odbędzie się na żądanie, gdy będzie to konieczne. Interpretacja pojęcia mutanta jako opisu zmian pociąga za sobą rozdzielnie procesu mutacji na dwie fazy: 1. Wyszukiwanie miejsc do mutacji (tworzenie opisów zmian); 2. Wykonanie mutacji (faktyczne stworzenie zmodyfikowanego programu); Taki rozdział stawia inne wymagania przed implementacją operatorów mutacyjnych, które w przypadku jednofazowego sposobu na mutowanie wprowadzają zmianę natychmiast po odnalezieniu odpowiedniego miejsca w kodzie programu. Natomiast każdy operator mutacyjny implementujący dwufazowe mutacje jest odpowiedzialny za wykonanie oddzielnych operacji: Wyszukanie wszystkich możliwych miejsc do mutacji w danym programie; Wykonanie zadanych zmian w jednym programie oryginalnym; 30

34 Obie fazy (operacje) przestawione są na rysunku 4.1. Operacja pierwsza dotyczy całości mutowanego kodu i wykonywana jest tylko jeden raz. W jej trakcie każdy operator tworzy opisy zmian do późniejszego wykorzystania. Natomiast operacja druga, bardziej wymagająca obliczeniowo, będzie wykonana na żądanie dla części mutantów, wybranych ze wszystkich zdefiniowanych w fazie pierwszej. W niej, opis zmian każdego wybranego mutanta zostanie przekazany wraz z oryginalnym programem do operatora mutacyjnego, który posłużył do stworzenia tego opisu. Operator na tej podstawie wprowadzi faktyczną mutację do kodu. Rys Dwie fazy mutacji 4.2. Deskryptor mutacji Reprezentacja opisu zmiany do wykonania (deskryptor mutacji) jest związana z oryginalnym programem, dla którego jest tworzona i na którym ma zostać wykonana. W kontekście tego programu, powinna przechowywać dwie podstawowe informacje: Gdzie należy dokonać zmiany lub czego ona dotyczy, jakiego istniejącego elementu 31

35 kodu; Na czym polega zmiana, jaki jest efekt mutacji; System mutacji zastosowany w jednym z pierwszych tego typu narzędzi Mothra, także stosował deskryptory mutantów (mutant descriptor record MDR)[46]. Informacje zawarte w deskryptorze jednoznacznie identyfikują zmianę wprowadzaną do programu przez określony operator mutacyjny. W ramach jego implementacji powinny zawsze prowadzić do powstania identycznego mutanta. Dla silnika mutacji deskryptor jest wygodnym odpowiednikiem mutanta, wymagającym bardzo niewielkiej ilości miejsca w pamięci. Deskryptor powinien zawierać informacje dzielące się na dwa obszary: Informacje niezbędne operatorowi mutacyjnemu do wykonania mutacji; Informacje przydatne silnikowi mutacji do wizualizacji mutanta, decyzji o jego odrzuceniu i innych działań. Może to dostarczyć częściowych informacji umożliwiających analizę mutanta, np. w jakiej klasie, metodzie zostanie wykonana zmiana, a to z kolei może potem posłużyć do łatwego wyświetlenia podglądu zmutowanego kodu; 4.3. Zarządzanie kopiami programu źródłowego Obie fazy wprowadzania mutacji opierają swoje działanie na oryginalnym kodzie programu, jednak w odmienny sposób. Faza pierwsza wykonywana jest raz i nie modyfikuje kodu programu, natomiast faza druga, wykonywana wielokrotnie, powoduje za każdym razem mutację kodu, na którym operuje. W pierwszej fazie mutacji jest pewność, że zostanie wykonana jednokrotnie, oraz że kod oryginalny nie zostanie zmodyfikowany przez żaden z operatorów, dlatego problem zarządzania programem źródłowym w tym przypadku można pominąć. Natomiast w wymagających obliczeniowo fazach drugich, część lub całość programu oryginalnego będzie wielokrotnie musiała zostać dostarczona do zmiany. Możliwymi rozwiązaniami implementacyjnymi tej kwestii jest wielokrotne tworzenie kopii programu oryginalnego lub odwracanie mutacji (wprowadzanie zmiany przywracającej zmodyfikowany wcześniej kod do oryginalnej postaci). Wymienione w kolejnych sekcjach sposoby zarządzania programem źródłowym biorą pod uwagę czynniki: Koszt obliczeniowy tworzenia nowej kopii programu oryginalnego; 32

36 Koszt obliczeniowy tworzenia kopii fragmentu programu oryginalnego; Trudność implementacji odwracania wprowadzonych mutacji (w celu uniknięcia tworzenia kopii); Ryzyko w uzyskaniu poprawności kodu z odwróconą mutacją; Sposób 1: Osobna kopia programu dla każdego tworzonego mutanta Najbardziej podstawowe podejście polega na użyciu osobnej kopii kodu programu oryginanego do stworzenia każdego mutanta. Wtedy, po wprowadzeniu mutacji do kodu, kopia uznawana jest za niemożliwą do ponownego użycia przy innym mutancie i po wykorzystaniu jest usuwana. Zaletą jest w tym przypadku łatwość implementacji oraz całkowita niezależność mutantów od siebie. Dużą wadą jest niska wydajność tworzenia kopii całego programu przy tworzeniu każdego mutanta, dodatkowo zmniejszająca skalowalność procesu testowania mutacyjnego. Wtedy czas trwania procesu jest zależny kwadratowo od wielkości programu, biorąc pod uwagę liczbę mutantów powstałych na linijkę kodu i obliczenia potrzebne na stworzenie każdego mutanta Sposób 2: Osobna kopia podzespołu programu dla każdego tworzonego mutanta Pierwszy sposób można częściowo zoptymalizować po zauważeniu, że przy każdej mutacji ogromna większość kodu programu pozostaje niezmieniona. Ponowne użycie przynajmniej części tego kodu pozwoliłoby zmniejszyć czas potrzebny na stworzenie każdego mutanta. Na platformie.net program składa się z części zwanych podzespołami (ang. assemblies). Każdy podzespół stanowi fragment właściwego programu lub bibliotekę zewnętrzną (zazwyczaj niezbędną do jego działania). Kod zawarty w podzespołach łączony jest dopiero w trakcie działania programu. Dlatego jeśli dwa mutanty są opisywane przez zmianę do wykonania w tym samym podzespole, do ich stworzenia wystarczy podmiana jedynie tego podzespołu. Co więcej, nawet jeśli zmiany są zdefiniowane w różnych podzespołach, do stworzenia każdego z mutantów potrzebne jest przetworzenie kopii tylko jednego podzespołu. Pozostałe mogą zostać dostarczone jako kopie plików z oryginalnego programu, bez ich modyfikacji przez silnik mutacji. Dodatkowym usprawnieniem może być zastosowanie pamięci podręcznej przechowującej kilka kopii najczęściej kopiowanego podzespołu. 33

37 Sposób 3: Osobna kopia fragmentu programu dla każdego tworzonego mutanta Możliwe jest zastosowanie jeszcze mniejszej granulacji fragmentów programu niż jeden podzespół. Jeśli każdy operator mutacyjny posiada dobrze zdefiniowany obszar działania, przykładowo jedną metodę lub pojedynczą klasę, możliwe jest ponowne wykorzystanie zmutowanego podzespołu poprzez podmianę metody lub klasy na wersję oryginalną. Wymaga to dodatkowego przetworzenia podzespołu przez silnik mutacji, jednak umożliwia bardzo szybkie otrzymanie kodu oryginalnego całego podzespołu Sposób 4: Ponowne użycie całej kopii programu dla każdego tworzonego mutanta W teorii, każdy zmutowany program może zostać zmodyfikowany ponownie do jego oryginalnej postaci. Wymaga to cofnięcia każdej zmiany kodu podzespołu po stworzeniu każdego mutanta w celu jego przywrócenia do pierwotnego stanu. Jednym pomysłem osiągnięcia tego może być przełożenie tej odpowiedzialności na operatory mutacyjne. W takim przypadku każdy operator musiałby potrafić odwrócić każdą wprowadzoną przez siebie zmianę, a zaletą byłby brak ograniczenia co do istoty wprowadzanych modyfikacji każdy operator może wprowadzić dowolną zmianę, o ile potrafi ją odwrócić. Jednak takie rozwiązanie bardzo komplikuje implementację operatorów mutacyjnych i może stwarzać dodatkowe ryzyko powstania błędów w implementacji, związanych z niepoprawnym przywróceniem kodu do poprzedniej postaci. Tym samym pociąga to za sobą brak niezależności i niepoprawność wszystkich kolejnych mutantów powstałych z kodu, w którym w błędny sposób cofnięto zmianę Reprezentacja modelu programu Platformy programistyczne z kompilacją do kodu pośredniego, będącego pomiędzy postacią wysokiego poziomu a instrukcjami maszynowymi, dają procesowi mutacji dodatkową elastyczność. Należy dokonać wyboru pomiędzy reprezentacją mutacji na poziomie kodu wysokiego poziomu lub kodu pośredniego. Kod IL (przykład znajduje się we fragmencie kodu 4.2) składa się z płaskiej listy prostych instrukcji przypominających kod maszynowy, ale operujących na stosie, w odróżnieniu od działania na rejestracji. Przed wykonaniem danej operacji, jej operandy zdejmowane są z wierzchołka stosu, a po jej wykonaniu odkładany jest tam wynik. Instrukcje mogą także 34

38 zawierać polecenia wczytania lub zapisania zmiennej lokalnej lub pola klasy. Zaletą operowania na poziomie C# jest łatwiejsze zrozumienie programu oraz bliskość odwzorowania kodu do prawdziwego programu z błędami, które mutacje mają odzwierciedlić. Taki sposób operacji przyjmuje CREAM[16]; Zaletą operowania na poziomie kodu IL jest brak konieczności kompilacji programu po wykonaniu mutacji oraz generalizacja, uproszczenie kodu w wyniku braku udogodnień składniowych. Na poziomie kodu IL operują narzędzia ILMutator[14], NinjaTurtles[83] i VisualMutator[19]; Aby sprawnie operować na kodzie programu, konieczne jest określenie warstwy abstrakcji modelu dobrze opisującego strukturę kodu i pozwalającą łatwo ją modyfikować. W przypadku mutacji kodu języka wysokiego poziomu jednym ze sposobów jest reprezentacja kodu jako drzewo składni (ang. syntax tree), odwzorowujące wszystkie elementy kodu programu zawarte w pliku źródłowym. Przy mutacjach kodu języka pośredniego możliwe jest posługiwanie się modelem prostszym, zawierającym mniej typów elementów (wynika to z budowy programu zapisanego w kodzie pośrednim). Poniższa sekcja zawiera omówienie sposobów reprezentacji programu (modeli programu) zapisanego w kodzie pośrednim, przechowywanego w podzespołach.net. W środowisku.net binarny model podzespołu jest podzielony na dwa obszary: Metadane opis wysokiego poziomu, elementy zawierające między innymi typy i metody. Zapisany jest w obszarze metadanych podzespołu.net. Informacje te nie wymagają dekompilacji i są łatwo dostępne dla środowiska programistycznego i kompilatora, aby obsłużyć odwołania z innych podzespołów[77]. Metadane są strukturą drzewiastą w której korzeniem jest obiekt opisujący moduł.net, który zwykle znajduje się w podzespole.net w liczbie 1; Kod programu reprezentuje instrukcje programu zawarte w metodach, zapisane w binarnej postaci kodu pośredniego (jednocześnie łatwo reprezentowalnego w postaci tekstowej); Analiza informacji z obszaru metadanych jest łatwa, ponieważ jego reprezentacja jest interpretowana bezpośrednio. Natomiast kod pośredni znajdujący się w metodach zawiera w sobie wiele optymalizacji i jest przygotowany w taki sposób, aby jego przetłumaczenie do kodu maszynowego odbyło się jak najszybciej, kosztem czytelności i bez zachowania pierwotnej struktury wywołań. Poniżej przedstawiony jest kod przykładowej metody w wersji C# (4.1) oraz tekstowej wersji kodu IL (4.2). 35

39 Kod 4.1. Przykładowa metoda - kod C# 1 public int AdditionOrSubstraction(int a, int b, bool condition) 2 { 3 if(condition) 4 { 5 return a + b; 6 } 7 else 8 { 9 return a - b; 10 } 11 } Kod 4.2. Przykładowa metoda - kod IL 1.method public hidebysig instance int32 AdditionOrSubstraction(int32 a, int32 b, bool condition) cil managed 2 { 3.maxstack 8 4 L_0000: ldarg.3 5 L_0001: brfalse.s L_ L_0003: ldarg.1 7 L_0004: ldarg.2 8 L_0005: add 9 L_0006: ret 10 L_0007: ldarg.1 11 L_0008: ldarg.2 12 L_0009: sub 13 L_000a: ret 14 } Metoda zwraca sumę argumentów a i b jeśli spełniony jest warunek lub ich różnicę w przeciwnym przypadku. Najbardziej prosty model jej kodu pośredniego można przedstawić w formie listy obiektów odpowiednich typów, gdzie każdy obiekt odpowiada jednej instrukcji kodu. W poniższym fragmencie kodu (4.3) przedstawiony jest odpowiadający tej metodzie pseudokod określający typy oraz wartości pól za pomocą pewnego hipotetycznego modelu. Klamry oznaczają zawieranie się elementów. Kod 4.3. Pseudokod modelu płaskiego 1 LoadArgument {Ordinal = 3} 2 BranchIfFalse { Label = "L_0007" } 3 LoadArgument { Ordinal = 1 } 4 LoadArgument { Ordinal = 2 } 36

40 5 Add 6 Return 7 LoadArgument { Ordinal = 1 } 8 LoadArgument { Ordinal = 2 } 9 Substract 10 Return Jednak pożądana jest reprezentacja instrukcji języka pośredniego poprzez model na wyższym poziomie abstrakcji. Strukturalny modelu kodu, będący drzewem AST, pozwala na dużo łatwiejsze wykonywanie złożonych transformacji. Poniższy pseudokod (4.4) przedstawia przykładowy model strukturalny formujący drzewo (klamry oznaczają zawieranie się w sobie elementów, wyrażenia ze znakiem = oznaczają pary {nazwa pola w elemencie zawierającym, typ i/lub wartość elementu zawieranego}), odpowiadający metodzie z omawianego przykładu. Kod 4.4. Pseudokod modelu strukturalnego 1 Condition 2 { 3 Expression = ParameterExpression { Name = "condition" } 4 BranchTrue = StatementList 5 { 6 ReturnStatement = ExpressionStatement 7 { 8 Expression = Addition 9 { 10 Left = ParameterExpression { Name = "a" } 11 Right = ParameterExpression { Name = "b" } 12 } 13 } 14 } 15 BranchFalse = StatementList 16 { 17 ReturnStatement = ExpressionStatement 18 { 19 Expression = Substraction 20 { 21 Left = ParameterExpression { Name = "a" } 22 Right = ParameterExpression { Name = "b" } 23 } 24 } 25 } 26 } 37

41 Uzyskanie takiego modelu wymaga jednak większej statycznej analizy kodu niż bezpośrednie odwzorowanie listy instrukcji, stając się prostą formą dekompilacji. Stworzone drzewo AST kodu pośredniego jest uproszczonym, niskopoziomowym odpowiednikiem drzewa AST skonstruowanego na podstawie języka wysokiego poziomu. W przypadku języka C#, jego drzewo AST zawiera w sobie wszystkie jego elementy składni, nawet jeśli stanowią jedynie udogodnienia dla programisty, np. wyrażenia lambda, pętle foreach. Drzewo AST zbudowane na podstawie języka pośredniego pozbawione jest takich elementów, opisując tylko składnię niższego poziomu (np. wyrażenia if-else, wywołania metod), które można łatwo odwzorować z kodu IL. Operowanie na bardziej złożonym modelu składni jest główną cechą wpływającą na łatwość zrozumienia implementacji operatora mutacyjnego i łatwość wykonania bardziej skomplikowanych działań na mutowanym programie. Porównanie budowy obu rodzajów modeli (płaskiego i strukturalnego) znajduje się na rysunku 4.2. Przedstawione są na nim modele przykładowej metody omawianej w tym podrozdziale. Obydwa modele są w górnej części strukturą drzewiastą, opisującej metadane podzespołu.net. Jednak w niższej części, bliżej szczegółów, model strukturalny także jest drzewem, w przeciwieństwie do modelu z płaską listą instrukcji. Rys Porówanie modelu z płaską listą instrukcji oraz w pełni strukturalnego 38

42 Przeglądanie modelu Kwestią powiązaną z budową modelu jest jego przeglądanie podczas wyszukiwania miejsc do mutacji oraz podczas samego wprowadzania zmian. Możliwe są tutaj dwie drogi implementacyjne: Przeglądaniem modelu zajmuje się każdy operator mutacyjny z osobna. Ma on pełną wolność analizy całego modelu w celu wyszukania i później wprowadzenia zmian; Przeglądaniem modelu zajmuje się silnik mutacji. Zawiera w sobie jedną logikę przeglądania drzewa, przekazując kontrolę działania do operatorów mutacyjnych tylko gdy jest to konieczne; Najbardziej prostym sposobem wydaje się przełożenie odpowiedzialności przegladania modelu na operatory mutacyjne. Każdy operator otrzymuje od silnika mutacji korzeń drzewa modelu i przeglądając drzewo w dowolny sposób (na przykład poprzez przechodzenie w głąb) musi znaleźć elementy, które go interesują i stworzyć na ich podstawie odpowiednie deskryptory mutacji. Natomiast operacja wprowadzenia zmian polega na ponownym wyszukaniu potrzebnych elementów w innej kopii modelu (przekazanej przez silnik mutacji i reprezentującej ten sam program) i wprowadzeniu w nich zleconych zmian. Obie fazy tego sposobu przedstawione są na rysunku 4.3. W tym przykładzie silnik mutacji przekazuje model do operatora, który następnie przechodząc drzewo znajduje dwa miejsca do mutacji - metody M1 i M2 i tworzy na tej podstawie 2 deskryptory mutacji (które przekazuje do silnika mutacji). W drugiej fazie silnik mutacji przekazuje do operatora model oraz jeden z deskryptorów. Operator wyszukuje element drzewa modelu na podstawie deskryptora i wprowadza opisaną w nim zmianę. Jednak lepszym, bardziej ogólnym rozwiązaniem w kwestii przechodzenia drzewa jest obciążenie silnika mutacji tym zadaniem (rysunek 4.4). Podczas fazy wyszukiwania miejsc do mutacji silnik przekazuje kolejne elementy modelu do operatora, a ten z kolei może zadecydować czy stworzyć dla danego elementu deskryptor mutacji. Na rysunku, podobnie jak w poprzednim sposobie, operator zwraca dwa deskryptory mutacji dotyczące metod M1 i M2. Natomiast podczas drugiej fazy wprowadzania zmiany silnik mutacji przekazuje do operatora tylko jeden element, i stworzony wcześniej na jego podstawie deskryptor. To rozwiązanie sprawia, że silnik mutacji ma większą kontrolę nad modelem oraz prowadzi do skupienia większości wspólnej logiki w silniku mutacji, upraszczając budowę operatorów. 39

43 Rys Przeglądanie drzewa przez operator mutacyjny Rys Przeglądanie drzewa przez silnik mutacji 40

44 Rozdział 5 Implementacja programu VisualMutator 2.0 Program VisualMutator jest narzędziem przeprowadzającym proces testowania mutacyjnego na projektach platformy.net. Zaimplementowany jako rozszerzenie środowiska programistycznego Visual Studio, pozwala w łatwy sposób przeprowadzać testowanie mutacyjne na aktualnie otwartych projektach. Wersja 2.0 jest rozwinięciem wcześniej powstałego programu VisualMutator 1.0 opisanego w ramach mojej pracy inżynierskiej[58]. Główne cechy programu VisualMutator to: Integracja ze środowiskiem VisualStudio w celu uzyskania możliwości łatwego przeprowadzania procesu testowania mutacyjnego na aktualnie tworzonych projektach; Możliwość wyboru operatorów mutacyjnych, elementów kodu do mutacji oraz testów do uruchomienia na mutantach; Udostępniane informacji o poszczególnych mutantach (podglądu kodu, wykonanych testów) w trakcie całego czasu trwania sesji testowania mutacyjnego (pojedynczego przebiegu procesu); Możliwość sterowania kolejnością testowania mutantów; Zapis ogólnych lub szczegółowych wyników do pliku w formacie XML; VisualMutator 2.0 skupia się na interakcji z użytkownikiem w celu ułatwienia rozpoczęcia procesu testowania mutacyjnego - dając możliwość wyboru metody do mutacji za pomocą wybrania dwóch poleceń oraz automatyczne wyszukując testy do uruchomienia na mutantach. Zapewnienia też możliwość kontroli przebiegu procesu - w każdej chwili działania sesji jest możliwość analizy kodu zmiany wprowadzonej w każdym mutancie oraz wpłynięcia na kolejność testowania mutantów. Dzięki temu użytkownik ma wybór: czy aktywnie 41

45 uczestniczyć w sesji i uzyskać pewne rezultaty wcześniej lub czy zdać się na wykonanie automatyczne. Interfejs użytkownika programu został przedstawiony na rysunku 5.1. Obok górnej części kontrolnej, prezentuje on poniżej drzewo mutantów oraz podgląd kodu wybranego mutanta. Rys Okno programu VisualMutator 2.0 Główną częścią procesu testowania mutacyjnego w VisualMutator jest sesja. Jej przebieg odpowiada pojedynczemu przypadkowi użycia programu przez użytkownika, w którym chce on uzyskać informacje o jakości i możliwościach usprawnienia zestawu testów. Sesja rozpoczyna się po wybraniu przez użytkownika wszelkich opcji dotyczących zakresu testowania mutacyjnego i jego sposobu działania, a następnie działa zgodnie z krokami: 1. Tworzony i testowany jest program niezmodyfikowany, w celu sprawdzenia poprawności testów; 2. Przeprowadzana jest pierwsza faza mutacji, czyli stworzenie listy wszystkich mutantów w postaci deskryptorów; 3. Każdy mutant jest przetwarzany w celu wykonania na nim testów lub wyświetlenia podglądu kodu. Sumarycznie jest to długa operacja, dająca możliwość obserwacji oraz wpłynięcia na jej przebieg poprzez akcje użytkownika; 42

46 W czasie działania sesji, mutanty są tworzone i testowane w sposób równoległy, konfigurowalny przez użytkownika. Obsługiwane są testy NUnit i XUnit, uruchamiane na mutantach w oddzielnych procesach, zapewniając niewrażliwość VisualMutatora na błędy powstałe w testowanych programach. Po zakończeniu procesu testowania mutacyjnego, jego sesja nadal jest dostępna, umożliwiając podgląd wyników mutacji oraz ich zapis do pliku XML. W ramach tej pracy magisterskiej, program VisualMutator został rozwinięty z wersji 1.0[58] do 2.0. W porównaniu z wersją 1.0, w VisualMutator 2.0 najważniejszymi zmianami są: Całkowita zmiana implementacji silnika mutacji; Stworzenie standardowych i obiektowych operatorów mutacyjnych (na miejsce wcześniej istniejących operatorów specjalizowanych dla platformy ASP.NET MVC); Wygodniejsza i bardziej interaktywna komunikacja z użytkownikiem; Asynchroniczne oraz równoległe wykonywanie długotrwałych operacji; Usprawnienia wykorzystania pamięci; Możliwość uruchomienia programu z linii poleceń, niezależnie od środowiska Visual Studio; Architektura oparta na relacji rodzic-potomek kontenerów wstrzykiwania zależności; 5.1. Architektura Konfiguracja modułów w VisualMutator odbywa się z pomocą wstrzykiwania zależności[23] (ang. dependency injection - DI ) z użyciem biblioteki Ninject[84]. Typ zakresu to zbiór zasad określających w jakich sytuacjach obiekt pewnej klasy jest tworzony i jak długo pozostaje w użyciu. Typ zakresu jest ustawiany na typ (klasę) obiektu; Zakres (ang. scope) to podzbiór obiektów, które mogą uzyskać dostęp do siebie nawzajem. Dany zakres (zwany wtedy węższym) może mieć także jednokierunkowy dostęp do innych zakresów (zwanymi wtedy szerszymi); Graf zależności to struktura obiektów (acykliczny graf skierowany) zbudowana na podstawie ich zależności konieczności istnienia pewnych obiektów (dzieci) w celu 43

47 skonstruowania innych (rodziców). Graf może zostać zbudowany tylko w taki sposób, w którym rodzic należy do tego samego lub szerszego zakresu niż jego dziecko; Kontener wstrzykiwania zależności to komponent (najczęściej stanowiący główną część biblioteki do wstrzykiwania zależności) zarządzający zakresami obiektów oraz konstruujący z nich grafy zależności. Przydzielenie wybranych typów zakresów typom obiektów zawsze odbywa się w ramach kontenera. W jednej aplikacji typowo używany jest jeden kontener; Typy zakresów zwykle możliwe do ustawienia dla typów obiektów w bibliotekach do wstrzykiwania zależności to singleton (kontener udostępnia zawsze ten sam globalny egzemplarz obiektu danej klasy), request (w przypadku aplikacji internetowych oznacza stworzenie nowego obiektu przy każdym żądaniu HTTP) lub nowa instancja (new instance) (kontener nie zarządza czasem życia obiektów klasy za każdym razem tworzy nowe instancje, według potrzeb innych komponentów). Przykładowe konfiguracje zakresów przestawione są na rysunkach 5.2 (aplikacja okienkowa) i 5.3 (aplikacja internetowa). Rys Podstawowe typy zakresów Typ zakresu singleton przydatny jest dla obiektów będących serwisami. Każdy serwis może mieć wtedy dostęp do innych serwisów oraz każdy z nich może przechowywać stan dostępny dla pozostałych (oznacza to, że znajdują się one w jednym zakresie). Jest to najszerszy zakres; Typ zakresu request w aplikacjach internetowych może być lepszą alternatywą dla serwisów niż typ singleton. Dzięki niemu, z każdym żądaniem HTTP będzie związany nowy zakres w którym będą żyły nowe instancje serwisów (którym nałożony został ten zakres). Mają one także dostęp do obiektów w zakresie globalnym; Typ zakresu new instance jest zwykle używany w stosunku do obiektów, które znajdują się w pobliżu lub na obrzeżu grafu zależności. Obiekty z tym typem zakresu 44

48 Rys Typy zakresów w aplikacji internetowej znajdują się w osobnych, jednoobiektowych zakresach i mają dostęp do obiektów znajdujących się w zakresie globalnym (typu zakresu singleton) oraz aktualnym zakresie typu request, jeśli istnieje. Jednak żaden obiekt, oprócz rodzica, nie ma dostępu do takiego obiektu jest to najwęższy możliwy zakres; Typy zakresów dostępne dla aplikacji nie-internetowych, singleton i new instance, są dość ograniczające przy budowie większej aplikacji. Istnieje tylko zakres globalny, i tylko w nim żyje stan dzielony między obiektami z pomocą wstrzykiwania zależności. W strukturze komponentów aplikacji można by osiągnąć większą elastyczność, jeśli byłby dostępny typ zakresu podobny do request, ale umożliwiający dokładniejszą kontrolę nad czasem życia zakresu. Taką możliwość daje użycie wielu kontenerów znajdujących się w hierarchii [85][57]. Użycie kontenerów w hierarchii (drzewie) polega na dynamicznym tworzeniu osobnych kontenerów-potomków, a wraz z nimi nowych, węższych zakresów, w trakcie działania aplikacji. Nazwy typów zakresów singleton, request, new instance przestają mieć wtedy znaczenie, ponieważ głębokość drzewa zakresów może być dowolnie duża (podczas gdy w typowym kontenerze ta głębokość wynosi 3 dla singleton, request, new instance lub 2 dla singleton, new instance). Typ zakresu singleton istnieje tylko jeden w każdym kontenerze, dlatego dobrze nadaje się do modelowania zakresów zdefiniowanych przez kontenerypotomki. Dynamiczne tworzenie zakresów wymaga istnienia mechanizmu kontroli czasu życia zakresów potomków. W przypadku typowych zakresów singleton i request, jest on z góry ustalony i sterowany przez bibliotekę. Jednak wprowadzając własne rozwiązanie tworzenia zakresów, możliwym sposobem jest skojarzenie czasu życia zakresu z pewnym obiektem w nim się znajdującym, funkcjonującym jako uchwyt do zakresu. Jeśli czas życia pewnego zakresu dobiegnie końca, wszystkie istniejące w nim obiekty oraz zakresy potomne 45

49 są zwalniane z pamięci. Przykład na rysunku 5.4 pokazuje sytuację w której stworzonych zostało kilka zakresów potomnych. Można na nim zauważyć, że trzymanie uchwytu do zakresu przez pewien obiekt wcale nie pociąga za sobą zależności w drugą stronę: relacja rodzic-potomek między zakresami definiuje jedynie możliwość uzyskania zależności obiektu w zakresie niżej w hierarchii od dowolnego obiektu w zakresie wyżej w hierarchii. Rys Hierarchia zakresów wstrzykiwania zależności (źródło: P. Trzpil, Scoping Hierarchical Dependency Injection [57]) Graf zakresów w programie VisualMutator jest przedstawiony na rysunku 5.5. Zakresy odpowiadają w programie kolejnym cyklom życia procesu testowania mutacyjnego, zawartym w sobie: Zakres globalny (wraz z głównym kontrolerem) istnieje przez cały czas działania programu. Za każdym razem, gdy zostanie zbudowany kod tworzonego projektu, zostaje powołany do życia nowy zakres konfiguracji w tle. Działa w nim logika kieszeni kodu oryginalnego (opisana w dalszej części rozdziału), która rozpoczyna tworzenie kopii kodu jeszcze zanim użytkownik zdecyduje się na uruchomienie procesu testowania mutacyjnego; Gdy użytkownik zainicjuje proces testowania mutacyjnego, powstaje nowy zakres, w którym odbywa się przetwarzanie nowo dostępnych informacji: o kontekście uruchomienia procesu. Jeśli jest to pojedyncza metoda, następuje jej wyszukanie w modelu kodu oryginalnego oraz poszukiwanie przypadków testowych ją pokrywających; Jeśli użytkownik zdecyduje się na rozpoczęcie procesu, powstaje zakres zawierający 46

50 kontroler sesji. Jest on odpowiedzialny za zarządzanie operacjami wykonywanymi podczas całego przebiegu procesu testowania mutacyjnego; Dodatkowe, pomniejsze zakresy tworzone są w przypadku tworzenia oraz testowania lub wizualizacji mutanta; Rys Hierarchia zakresów w programie VisualMutator 5.2. Budowa silnika mutacji opartego na bibliotece CCI W programie VisualMutator w wersji 1.0 do implementacji wprowadzania zmian w kodzie była używana biblioteka Mono.Cecil[79]. Jest to projekt o otwartych źródłach pomagający w edycji podzespołów i kodu pośredniego.net. Inną dojrzałą biblioteką służącą do analizy, tworzenia i edycji metadanych i kodu pośredniego jest Common Compiler Infrastructure (CCI) [69]. Jej dwie części: Metadata oraz Code Model służą do analizy i modyfikacji, kolejno, modelu metadanych w podzespołach.net (klasy, metody) oraz kodu języka pośredniego za pomocą hierarchicznej struktury, której budowa przypomina opisany wcześniej strukturalny model kodu. Z powodu dużo łatwiejszej analizy i edycji kodu, a także lepszej dokumentacji, właśnie ten model został użyty przy implementacji operatorów mutacyjnych w VisualMutator 2.0. Miał też duży wpływ na przebudowę silnika mutacji w programie. Dokładniejsze informacje na temat tworzenia nowych operatorów mutacyjnych są przedstawione w Dodatku B tej pracy. 47

51 Dwufazowy sposób mutacji, opisany w poprzednim rozdziale, polega na oddzieleniu od siebie fazy wyszukiwania oraz wprowadzania zmian. Innymi słowy, zmiany nie są wprowadzane w jednym przebiegu analizy kodu programu. Celem zastosowania dwufazowych mutacji, a także skupienia jak największej części logiki mutacyjnej w silniku mutacji (w przeciwieństwie do skupienia jej w operatorach mutacyjnych) jest: Zwiększenie elastyczności całego procesu mutacji: scentralizowane podejmowanie decyzji, łatwość przetwarzania równoległego, łatwiejsze zarządzanie mutantami w pamięci; Brak duplikacji logiki: jedna wspólna implementacja. Dzięki temu tworzenie operatorów mutacyjnych jest mniej skomplikowane; Zmniejszenie trudności implementacji operatorów: ponowna identyfikacja obiektu w drzewie jest skomplikowanym działaniem przy wprowadzaniu mutacji silnik mutacji działa na innej instancji tego samego drzewa; Łatwość późniejszej automatycznej dekompilacji kodu operator nie musi określać dodatkowej informacji jaką metodę lub klasę należy umieścić w podglądzie kodu dla użytkownika; Jednak pociąga to za sobą konieczną komplikację budowy silnika mutacji spowodowaną przez potrzebę identyfikacji miejsca do mutacji (deskryptora) w innej kopii modelu kodu. Podstawowym problemem przy tworzeniu deskryptora mutacji jest zapisanie jednoznacznej informacji gdzie czyli zidentyfikowanie konkretnego obiektu w drzewie modelu kodu, tak aby było możliwe ponowne jego odnalezienie w celu wykonania mutacji. Decydując się na wybór CCI jako biblioteki do edycji kodu, niezbędne stało się znalezienie dobrego sposobu implementacji deskryptora mutanta. W rozwiązaniu zastosowanym w VisualMutator 1.0, używającym płaskiej listy instrukcji, każdy element kodu metody może być łatwo zidentyfikowany po indeksie instrukcji w liście. Z drugiej strony, w CCI natomiast brakuje wbudowanego rozwiązania do identyfikacji dowolnego obiektu w drzewie modelu. Wybrany sposób przeglądania elementów w drzewie odpowiada podejściu opisanemu wcześniej w rozdziale Silnik mutacji w podrozdziale Przeglądanie modelu. Biblioteka CCI zawiera w sobie implementację algorytmów umożliwiających odwiedzanie kolejno wszystkich obiektów zawartych w drzewie modelu kodu i dających możliwość ich podmiany na inne. Służą temu klasy: CodeVisitor odwiedza wszystkie obiekty w modelu kodu w kolejności wzdłużnej (ang. preoder) lub wstecznej (ang. postorder). Jego celem jest pomoc w zebraniu informacji potrzebnych do wykonania mutacji; 48

52 CodeRewriter odwiedza wszystkie obiekty w modelu kodu i umożliwia ich zmianę lub podmianę na inne. Jego celem jest pomoc w zamianie pewnych elementów drzewa na wersje zmodyfikowane; Typowe użycie każdej z tych klas polega na dziedziczeniu z nich i przeciążeniu niektórych z około 200 metod obsługujących oddzielnie każdy typ elementu drzewa modelu metadanych. Aby odwiedzić wszystkie elementy w drzewie następnie wystarczy uruchomić odpowiednią metodę na korzeniu drzewa obiekcie reprezentującym podzespół.net Identyfikacja obiektów w drzewie Rozwiązanie implementacyjne co do identyfikacji obiektów w drzewie, które przyjąłem to zliczanie odwiedzanych elementów podczas przechodzenia drzewa. Przy założeniu że drzewa są identyczne i elementy odwiedzane są w dokładnie takiej samej kolejności, każdemu elementowi można przypisać liczbę jednoznacznie go identyfikującą. Liczba ta, będąca głównym elementem deskryptora mutacji, pozwala na późniejsze odnalezienie obiektu odpowiadającego źródłowemu, w innej kopii tego samego drzewa. W przykładzie na rysunku 5.6 w fazie 1 następuje przechodzenie wszystkich węzłów w drzewie i przypisanie ich identyfikatorów. Natomiast w fazie 2 odbywa się wyszukanie pewnego obiektu na podstawie danego identyfikatora 4. Następuje ponowne przechodzenie wszystkich elementów w drzewie i znaleziona na podstawie tego identyfikatora zostaje metoda M2. Rys Identyfikacja obiektu w drzewie modelu Zastosowanie identyfikacji i wyszukiwania obiektów przez silnik mutacji powoduje znaczne uproszczenie budowy operatorów mutacyjnych, ponieważ przeglądanie modelu może zostać scentralizowane w jednym miejscu nie ma potrzeby implementacji tego procesu w każdym operatorze. 49

53 5.3. Kieszeń kodu oryginalnego W drugiej fazie mutacji - wprowadzania konkretnej zmiany do kodu oryginalnego, kod ten za każdym razem jest taki sam, a różny jest jedynie deskryptor zmiany do wprowadzenia. Kod oryginalny potrzebny jest wielokrotnie i może zostać stworzony wcześniej, niezależnie o jego użycia. Służy temu kieszeń kodu oryginalnego - zajmuje się przechowywaniem pewnej liczby egzemplarzy kopii oryginalnego kodu oraz ich tworzeniem tak długo, jak limit ten nie jest osiągnięty. Cele, jakim służy kieszeń kodu oryginalnego to: 1. Tworzenie kopii programu oryginalnego jeszcze przed rozpoczęciem procesu testowania mutacyjnego gdy nie jest jeszcze dostępna żadna informacja o mutantach, ale kopie będą mogły być użyte po niedługim czasie; 2. Przyspieszenie uzyskiwania podglądu kodu mutanta wybranego przez użytkownika w trakcie trwania procesu kieszeń zapewnia, że kopia będzie dostępna natychmiast; Kieszeń kodu oryginalnego może działać według jednego z dwóch sposobów (przełączanych w ustawieniach programu) opisanych w rozdziale 4, podrozdziale Zarządzanie kopiami kodu oryginalnego: Sposób 1: Osobna kopia programu dla każdego tworzonego mutanta - Do produkcji każdego mutanta zużywany jest cały zestaw wszystkich podzespołów składających się na program oryginalny; Sposób 2: Osobna kopia podzespołu programu dla każdego tworzonego mutanta - W tej implementacji, kieszeń kodu zarządza oddzielnie każdym podzespołem składającym się na program. Do tworzenia mutanta zużywana jest kopia tylko jednego podzespołu. Ogranicza to zbędne tworzenie kopii podzespołów, które nigdy nie zostaną zmodyfikowane; Jednak w obu tych sposobach, kod modyfikowanego podzespołu musi za każdym razem zostać wczytany z pamięci masowej, przeanalizowany i zmodyfikowany. Kosztowną operację wczytania oryginalnego podzespołu można, w teorii, zastąpić modyfikacją już wcześniej zmodyfikowanych podzespołów w celu przywrócenia ich stanu do źródłowego (sposób 3 na zarządzanie kopiami opisany w rozdziale 4). Jednak przeszkodą w implementacji tego sposobu jest problematyczne kopiowanie fragmentu drzewa modelu biblioteki CCI, gdzie procedura ta prowadzi do otrzymania kopii nieco innej od źródłowej. Różnice w strukturze 50

54 drzew uniemożliwiają późniejszą identyfikację elementów do mutacji. Sposobem na rozwiązanie tego problemu może być odpowiednia modyfikacja dwóch drzew (oryginalnego i kopii) w celu doprowadzenia ich do identycznej struktury. Wymaga to znalezienia wspólnego podzbioru elementów zawartego przez obydwa drzewa, co można osiągnąć poprzez rozwiązania: Wykonanie operacji znalezienia różnicy pomiędzy drzewami, na przykład po obliczeniu odległości edycji drzewa (ang. tree edit distance)[11] i sprowadzenia obu drzew do wspólnej postaci; Zapisanie strukury drzew jako tekst i obliczenie różnicy pomiędzy takimi tekstowymi ich reprezentacjami. Następnie odwzorowanie różnic w liniach tekstu na elementy drzew (linie będą odwzorowane w tekście zgodnie z kolejnością przechodzenia elementów) i sprowadzenie ich do wspólnej postaci. Ten sposób jest prostszy w implementacji, ponieważ postać tekstowa może być łatwo przetworzona przez zewnętrzne narzędzia do scalania plików tekstowych; Jako że budowanie drzewa modelu na podstawie plików źródłowych na dysku jest bardziej kosztowną obliczeniowo operacją w porównaniu do kopiowania modelu w pamięci, zastosowanie powyższych pomysłów może doprowadzić do znacznego przyspieszenia tworzenia każdego mutanta Kieszeń mutantów Kieszeń mutantów to komponent programu VisualMutator zajmujący się przechowywaniem stworzonych mutantów w pamięci. Przechowywanie kilku mutantów na raz lub jednego mutanta przez dłuższy czas w pamięci nigdy nie jest niezbędne (dzięki ciągłemu istnieniu deskryptorów mutacji), jednak pomaga przyspieszyć niektóre operacje. Celem istnienia kieszeni mutantów jest łatwiejsza integracja ze sobą jednoczesnego procesu testowania mutantów i wyświetlania podglądu mutantów z inicjatywy użytkownika. Gdy pojawia się konieczność użycia jednego mutanta kilka razy, to dzięki przechowywaniu kilku ostatnio stworzonych, są one dostępne do wykorzystania natychmiast, bez potrzeby kilkukrotnego wykonywania tych samych obliczeń przez operator mutacyjny. Najdawniej stworzony mutant jest zwalniany, gdy wartość wykorzystanej pamięci operacyjnej zajmowana przez przechowywane mutanty przekroczy pewien limit. 51

55 5.5. Proces testowania mutantów Operacje tworzenia kopii oraz testowania mutantów działają według wzorca producentkonsument. Tworzenie kopii odbywa się niezależnie od pozostałych operacji (w aktualniej implementacji programu VisualMutator operacje kieszeni kodu oryginalnego rozpoczynają się z chwilą zbudowania projektu jeszcze zanim użytkownik wyrazi chęć stworzenia nowej sesji testowania mutacyjnego). Możliwa jest w programie konfiguracja liczby wątków przetwarzających: zarówno producentów (tworzących kopie) jak i konsumentów (tworzących i testujących mutanty). Cykl życia każdego mutanta przestawia się następująco: 1. Mutant zostaje powołany do życia w procesie modyfikacji kodu źródłowego przez operator mutacyjny na podstawie wybranego deskryptora. Do stworzenia mutanta wymagana jest jedna kopia źródłowego programu (pobrana z kieszeni kodu oryginalnego), która jest zużywana w trakcie tego procesu. Mutant jest następnie przechowywany w pamięci, w kieszeni mutantów; 2. Jeśli mutant ma zostać przetestowany, jest zapisywany na dysk twardy wraz ze wszystkimi podzespołami niezbędnymi do jego działania (niezmodyfikowane podzespoły są kopiowane z lokalizacji źródłowej); 3. Na podzespołach mutanta, które zawierają testy wybrane do uruchomienia, wykonywany jest zewnętrzny program testujący. W aktualnej implementacji jest to program NUnit lub XUnit uruchamiany z linii poleceń. Wynik programu jest analizowany i na podstawie testów zakończonych sukcesem lub porażką określony zostaje stan mutanta; 4. Mutant pozostaje w kieszeni mutantów do chwili jego zwolnienia z pamięci. W tym czasie może zostać udostępniony na każde żądanie, w celu wyświetlenia podglądu jego kodu lub uruchomienia na nim testów. W przypadku żądania nadeszłego już po jego zwolnieniu z pamięci, musi zostać stworzony ponownie (i ponownie zostaje umieszczony w kieszeni); 52

56 Rozdział 6 Eksperymenty i wyniki W celu sprawdzenia skuteczności usprawnień wydajnościowych w programie VisualMutator, zostały przeprowadzone pomiary czasu wykonania charakterystycznych operacji na przykładowym kodzie projektu, przy pewnych ustawieniach programu. Wykonane pomiary należą do dwóch kategorii: Pomiar szybkości działania całego procesu testowania mutacyjnego; Pomiar szybkości uzyskiwania częściowych informacji w trakcie trwania procesu, przy użyciu funkcji interaktywnych; Opcje konfiguracyjne, jakie zostały użyte przy wykonaniu pomiarów to: A) Liczba wątków kieszeni kodu oryginalnego, tworzących niezmodyfikowane kopie (producentów). Kieszeń kodu oryginalnego jest opisana w rozdziale Implementacja programu VisualMutator 2.0 ; B) Liczba wątków tworzących, a następnie testujących mutanty (konsumentów). Proces testowania mutantów jest opisany w rozdziale Implementacja programu VisualMutator 2.0 ; C) Sposób zarządzania podzespołami źródłowymi: kopiowanie wszystkich lub tylko jednego. Sposoby zarządzania kopiami programu źródłowego są opisane w rozdziale Silnik mutacji; D) Uruchomienie testowania mutacyjnego w kontekście całego projektu lub tylko jednej metody. Testowanie mutacyjne w kontekście jednej metody w kontraście do procesu wsadowego jest opisane w rozdziale Lokalne i interaktywne testowanie mutacyjne; Stałym ustawieniem programu był limit na liczbę stworzonych mutantów przez każdy operator z osobna: 100. Tworzone były wszystkie możliwe deskryptory mutantów, po 53

57 czym część, wybrana losowo, była odrzucana, aby dla każdego operatora ich liczba nie przekroczyła limitu. Celem pomiarów było znalezienie odpowiedzi na pytania: 1. Jaki wpływ na szybkość procesu testowania mutacyjnego ma przetwarzanie pewnej liczby mutantów oraz tworzenie pewnej liczby kopii programu oryginalnego równolegle w tle (opcje konfiguracyjne A i B). Te zmienne są od siebie zależne związane są z szybkością przetwarzania producentów i konsumentów tego samego zasobu (kopii oryginalnego programu); 2. Jaka konfiguracja równoległego przetwarzania mutantów i kopii dla komputerów biorących udział w eksperymencie daje najlepsze wyniki (A i B) jaka wartość jednej z tych konfiguracji względem drugiej jest najlepsza; 3. Czy wykonanie procesu dobrze skaluje się na większą liczbę rdzeni procesora; 4. Jaki wpływ na szybkość procesu testowania mutacyjnego ma zastosowanie sposobu na kopiowanie wyłącznie tego podzespołu, do którego ma zostać wprowadzona zmiana (C). Ten aspekt konfiguracyjny i jego wpływ na szybkość procesu jest niezależny od konfiguracji producentów i konsumentów; 5. Jaki czas oczekiwania jest potrzebny na uzyskanie pełnych informacji o jednym dowolnie wybranym mutancie, w ramach danej konfiguracji. Równolegle z trwającym procesem, użytkownik może wybrać pewnego mutanta w celu wyświetlenia podglądu jego kodu. Jednoczesne wykonywanie przez program wielu innych konsumentów (B) może mieć duży wpływ na szybkość uzyskania pogdlądu; 6.1. Testowane projekty Pomiary zostały wykonane na otwartoźródłowych projektach MiscUtil[78] i Dsa[78]; Za pomocą programu NDepend zostały znalezione statystyki ilościowe kodu projektów (tabela 6.1), oddzielnie dla aplikacji i jej zestawu testów. Komentarze nie są wliczane w sumę linii kodu. 54

58 Tab Projekty MiscUtil i Dsa - liczba typów, metod i linii kodu Aplikacja Zestaw testów MiscUtil Dsa MiscUtil Dsa Liczba typów Liczba metod Liczba linii kodu Konfiguracje biorące udział w eksperymentach Głównym aspektem wykonania eksperymentów było określenie szybkości wykonania procesu mutacyjnego w ramach różnych ustawień programu. Z powodu dużej liczby możliwych kombinacji ustawień, wykonanie pomiarów zostało podzielone na następujące 4 zestawy konfiguracji: 1. (2 - projekt MiscUtil, dwukrotnie) W ramach uruchomienia procesu testowania mutacyjnego tylko dla jednej metody projektu, sprawdzone zostały oba sposoby zarządzania podzespołami źródłowymi, a także szybkość uzyskiwania podglądu kodu mutanta pełnych informacji o wybranym przez użytkownika mutancie (tabela i 6.4.1); 2. (2 - projekt MiscUtil i Dsa) W ramach uruchomienia procesu testowania mutacyjnego całego projektu, przyjęty został sposób kopiowania tylko jednego podzespołu (tabela i 6.4.2); W ramach każdej z powyższych zestawów konfiguracji, sprawdzona została każda konfiguracja liczby wątków (x, y), gdzie x, y należy do zbioru {1, 2, 3}. Inną sprawdzaną kwestią była interaktywność wyświetlania częściowych informacji w trakcie trwania procesu, czyli pełnych informacji o wybranym przez użytkownika mutancie. Równolegle z trwającym procesem, użytkownik może wybrać pewnego mutanta, który zostanie następnie przetestowany w pierwszej kolejności oraz wyświetlony zostanie kod prezentujący modyfikację kodu. Wpływ konfiguracji liczby wątków na możliwości wyświetlenia informacji o mutancie został sprawdzony w ramach uruchomienia procesu testowania mutacyjnego w kontekście jednej metody. 55

59 6.3. Przewidywania co do wyników Największe oraz niezależne od innych ustawień przyspieszenie może zostać osiągnięte dzięki tworzeniu kopii tylko jednego podzespołu źródłowego (ustawienie C). Jest to całkowite uniknięcie niepotrzebnych obliczeń, dlatego ta hipoteza była najbardziej silna. Przed wykonaniem pomiarów przewidywałem, że zwiększenie równoległości do około 4 wykonywanych operacji na raz (łącznie producentów i konsumentów) (ustawienia A i B) powinno doprowadzić do przyspieszenia wykonania całego procesu (biorąc pod uwagę liczbę rdzeni procesora oraz negatywnie na to wpływające blokowanie się niektórych operacji na odczycie i zapisie na dysk). Liczba producentów i konsumentów powinna być podobna, ponieważ operacje przez nich wykonywane mają podobny charakter (zapis lub odczyt modelu z dysku, przechodzenie drzewa modelu) Scenariusz i wykonanie eksperymentu W celu wykonania eksperymentów, stworzony został niewielki program w języku Scala, uruchamiający z linii poleceń program VisualMutator kolejno dla każdej konfiguracji (zadanej za pomocą argumentów sterujących z linii poleceń) w danym zestawie konfiguracji. W trakcie wykonania jednego przebiegu programu VisualMutator obywały się kolejne operacje (automatycznie) związane z jego działaniem: 1. Uruchomienie programu VisualMutator z odpowiednimi argumentami. Była to między innymi liczba wątków producentów oraz konsumentów, a także osobno: ścieżka do podzespołu do wprowadzenia mutacji oraz do podzespołu z testami do wykonania; 2. Analiza kodu oryginalnego i wyszukanie pokrywających przypadków testowych (tylko w przypadku uruchomienia w kontekście jednej metody. W przeciwnym przypadku wykonywane były wszystkie testy); 3. Wyszukanie miejsc do wykonania mutacji (zgodnie z parametrem sterującym: w całym podzespole do mutacji lub tylko w zadanej metodzie) i stworzenie deskryptorów wszystkich mutantów; 4. Wykonanie całego procesu testowania mutacyjnego. W tym czasie wątki producentów tworzą kolejne kopie programu oryginalnego. Natomiast każdy wątek konsumenta pobiera kolejny dostępny deskryptor mutanta z kolejki i po uzyskaniu kopii programu oryginalnego tworzy i testuje mutanta; 56

60 5. Zapisanie ogólnych wyników procesu testowania mutacyjnego do pliku XML; 6. Zakończenie działania programu VisualMutator; Po uzyskaniu wyników z przebiegu każdej konfiguracji, osobny program posłużył do odczytu każdego z wynikowych plików i stworzenia fragmentu kodu LaTeX, stanowiącego wnętrze tabeli z wynikami, gotowego do przeniesienia do tej pracy. W tabelach z wynikami eksperymentów w dalszej części rozdziału, nazwy kolumn mają następujące znaczenie: 1. WO - liczba wątków tworzących kopie programu oryginalnego (producentów); 2. WM - liczba wątków tworzących i testujących mutanty (konsumentów); 3. Cały proces - czas pomiędzy rozpoczęciem tworzenia pierwszego mutanta, a zakończeniem testowania ostatniego (mierzony poprzez obliczenie różnicy tych chwil w czasie). W przypadku eksperymentów w kontekście tylko jednej metody, czas ten został uśredniony po 3 przebiegach procesu dla danej konfiguracji; 4. Tworzenie - czas trwania jednej operacji stworzenia mutanta. Tworzenie mutanta składa się z wykonania mutacji oraz zapisu mutanta na dysk. Dla każdego mutanta, czas jest różnicą wartości chwil czasu po i przed wykonaniem tych operacji. Wynikowy czas jest uśredniony po wszystkich mutantach w programie VisualMutator, po uzyskaniu wyników cząstkowych wszystkich mutantów; 5. Testowanie - czas trwania jednej operacji testowania mutanta. Jest to czas, jaki upłynął od chwili wywołania zewnętrznego programu testującego do chwili wczytania jego wyników. Wynikowy czas jest uśredniony po wszystkich mutantach w programie VisualMutator, po uzyskaniu wyników cząstkowych wszystkich mutantów; 6. Podgląd kodu - średni czas oczekiwania na wyświetlenie poglądu kodu jeszcze nie przetestowanego mutanta (ściśle: nie znajdującego się w kieszeni mutantów - w przeciwnym przypadku wyświetlenie podglądu jest natychmiastowe). Mierzony był czas upływający pomiędzy wybraniem pewnego mutanta przez użytkownika a wyświetleniem podglądu (w oddzielnych uruchomieniach procesu testowania mutacyjnego poprzez interfejs graficzny w środowisku Visual Studio nie z linii poleceń). Ostateczna wartość jest średnią czasów z prób na trzech mutantach; 7. CP/LM - iloraz czasu trwania całego procesu i liczby wszystkich mutantów. Wartość ta pozwala lepiej określić średni czas przetwarzania jednego mutanta w kontekście całego procesu. W przypadku działania tylko jednego wątku konsumenta, powinna być równa sumie średniego czasu tworzenia i testowania mutanta; 57

61 Szybkość testowania mutacyjnego pojedynczej metody Do przeprowadzenia pomiarów został użyty komputer z podzespołami: Pentium Core i5 2450M 2.40 GHz (liczba rdzeni: 2), 12GB RAM, dyskiem 256GB SSD. Proces testowania mutacyjnego został ograniczony do metody z projektu MiscUtil: MiscUtil.Collections.Range<T>.Contains(T) (kod 6.1). Kod 6.1. Metoda MiscUtil.Collections.Range<T>.Contains(T) 1 public bool Contains(T value) 2 { 3 int lowerbound = comparer.compare(value, start); 4 if (lowerbound < 0 (lowerbound == 0 &&!includesstart)) 5 { 6 return false; 7 } 8 int upperbound = comparer.compare(value, end); 9 return upperbound < 0 (upperbound == 0 && includesend); 10 } W trakcie każdej z uruchomionych konfiguracji, tworzone były 32 mutanty, z czego 30 zostawało zabitych, a 2 utrzymywały się przy życiu. Wynik mutacji wyniósł 93%. Zostały wykonane dwa eksperymenty odpowiadające dwóm zestawom konfiguracji z punktu 1 opisanego we wcześniejszym podrozdziale. Wyniki pomiarów znajdują się w tabelach 6.2 i

62 Tab Wyniki pomiarów dla metody MiscUtil.Collections.Range<T>.Contains(T) i przy aktywnym sposobie kopiowania wszystkich podzespołów WO WM Cały proces [s] Tworzenie [s] Testowanie [s] Pogląd kodu [s] ,6 7,5 2,1 3, ,8 19,1 2,9 7, ,0 31,1 2,0 11, ,5 17,9 2,8 3, ,1 19,1 2,9 3, ,5 20,0 2,6 4, ,4 18,3 2,3 3, ,8 20,6 2,2 3, ,2 20,1 2,3 7,9 Tab Wyniki pomiarów dla metody MiscUtil.Collections.Range<T>.Contains(T) i przy aktywnym sposobie kopiowania tylko jednego podzespołu WO WM Cały proces [s] Tworzenie [s] Testowanie [s] Pogląd kodu [s] ,4 3,2 1,6 2, ,1 6,7 1,8 3, ,0 12,3 2,1 6, ,6 3,5 1,7 2, ,1 6,5 2,1 2, ,6 11,9 2,8 6, ,4 7,1 2,6 2, ,8 13,4 1,9 3, ,4 10,7 2,6 4,4 Wyniki wskazują, że zastosowanie częściowego kopiowania plików źródłowych zmniejsza czas trwania procesu testowania mutacyjnego prawie o połowę w przypadku projektu MiscUtils (składającego się z dwóch podzespołów). Pokazuje to, że zastosowanie tego sposobu tworzenia kopii jest kluczowe z punktu widzenia szybkości działania procesu. Można spodziewać się, że różnica szybkości byłaby jeszcze większa przy zastosowaniu projektu z większą liczbą podzespołów i plików źródłowych. Można też zauważyć, że 59

63 wartości średniego czasu tworzenia mutantów także są około dwamrazy Różnice w szybkości wykonania całego procesu dla poszczególnych konfiguracji w obu zestawach konfiguracji nie są duże względne odchylenie standardowe czasów trwania procesu wyniosło dla powyższych dwóch eksperymentów odpowiednio: 7,5% i 12,0%. Z tego powodu wpływ konfiguracji wątków na szybkość całego procesu został przeanalizowany w kolejnych eksperymentach. Szybkość uzyskiwania podglądu jest zależna od liczby wątków konsumenckich większa ich liczba wpływa na szybkość negatywnie. Wynika to z faktu, że operacja stworzenia mutanta na potrzeby podglądu musi konkurować z równoległymi operacjami tworzącymi i testującymi inne mutanty, dodatkowo jeszcze zawierającymi kosztowny proces zapisu mutanta na dysk Szybkość testowania mutacyjnego całego projektu Pomiary zostały w tym przypadku wykonane na szybszym komputerze z następującymi podzespołami: Pentium Core i7-3770, 3.40 Ghz (liczba rdzeni: 4), 8GB RAM, dyskiem 500GB HDD. Proces testowania mutacyjnego został wykonany dla 9 konfiguracji wartości liczby wątków producentów oraz liczby wątków konsumentów od 1 do 3, oraz dodatkowo dla przypadków liczby wątków konsumentów 4 i 5. W przypadku projektu Dsa tworzone i testowane było 567 mutantów, z czego 432 zostawały zabite. Wynik mutacji wyniósł 76%. Wyniki zawarte są w tabeli

64 Tab Wyniki pomiarów dla całego projektu Dsa, przy aktywnym sposobie kopiowania tylko jednego podzespołu WO WM Cały proces [s] Tworzenie [s] Testowanie [s] CP/LM [s] ,2 0,9 1,3 2, ,6 1,7 1,2 1, ,7 2,4 1,4 1, ,8 0,9 1,3 2, ,2 1,7 1,3 1, ,2 2,5 1,4 1, ,1 0,9 1,2 2, ,1 1,7 1,3 1, ,3 2,6 1,3 1, ,3 3,5 1,3 1, ,0 4,4 1,3 1,2 W przypadku projektu MiscUtil tworzone i testowane było 896 mutantów, z czego 399 zostawało zabitych. Wynik mutacji wyniósł 44%. Wyniki zawarte są w tabeli 6.5. Tab Wyniki pomiarów dla całego projektu MiscUtil, przy aktywnym sposobie kopiowania tylko jednego podzespołu WO WM Cały proces [s] Tworzenie [s] Testowanie [s] CP/LM [s] ,0 1,9 3,3 5, ,1 3,0 3,4 3, ,7 4,5 3,6 2, ,3 1,9 3,3 5, ,3 3,3 3,4 3, ,3 4,6 3,6 2, ,6 1,9 3,3 5, ,7 3,3 3,5 3, ,2 4,8 3,6 2, ,0 6,3 3,6 2, ,8 8,0 3,6 2,5 61

65 Nieco zaskakującą, pierwszą obserwacją jest, że zastosowanie większej liczby wątków producentów nie ma znaczenia dla szybkości przebiegu procesu. We wszystkich przypadkach, w kórych liczba wątków konsumentów jest taka sama, wartości czasów są bardzo niewiele od siebie różne. Pokazuje to, że kieszeń kodu oryginalnego ma znaczenie jedynie w związku z funkcjami interaktywnymi, do użycia w celu wyświetlenia podglądu kodu mutanta. Służy także jako możliwość stworzenia kilku kopii kodu oryginalnego jeszcze przed rozpoczęciem procesu testowania mutacyjnego, podczas gdy użytkownik dokonuje wyborów. Zaobserwować można także znaczne zwiększenie szybkości wykonania procesu przy większej liczbie wątków konsumentów 1 i 2 oraz 2 i 3. Dla obu projektów, po dwa dodatkowe przebiegi procesu dla większej liczby wątków konsumentów (4 i 5) pokazują tylko nieznaczne przyspieszenie. Rysunek 6.1 przedstawia wykres czasu trwania całego procesu testowania mutacyjnego dla obu projektów, w zależności od liczby wątków konsumentów (i wątków producentów w liczbie 3). Rys Czas wykonania całego procesu w zależności od liczby wątków konsumentów Nie jest dobrze widoczna korelacja pomiędzy liczbą rdzeni procesora, a szybkością procesu jeśli uruchomione są jednocześnie więcej niż 3 wątki konsumentów. Różnica szybkości przestaje być wtedy znacząca. Obserwacje obciążenia procesora w przypadku testowania całych projektów pokazały też, że procesor jest obciążony tylko w 25-50% jego wydajności, co źle świadczy o skalowalności procesu na większą liczbę rdzeni. Problemem może być częsty i długotrwały dostęp do dysku. 62

66 Optymalnym ustawieniem wątków dla komputera z 4 rdzeniami procesora wydaje się być około 4 wątków konsumentów oraz od 1 do 3 wątków producentów (wartość ta nie wpływa na wyniki). Należy jednak pamiętać, że przy większej liczbie wątków równoległych, szybkość uzyskiwania podglądu kodu mutantów będzie mniejsza Zagrożenia co do ważności wyników Zagrożenia zasadności (ważności) wyników to możliwe do popełnienia błędy analizy wyników lub wniosków z nich wyciągniętych. Dzielą się na zagrożenia ważności wewnętrznej oraz zewnętrznej[62]. Ważność wewnętrzna dotyczy umiejętności poprawnego wysuwania wniosków co do zależności przyczynowo-skutkowej zmiennych obecnych w doświadczeniu. W przypadku pomiarów wykonanych dla programu VisualMutator błędne mogą być wnioski dotyczące optymalnej konfiguracji liczby wątków przetwarzających i wzajemny wpływ producentów i konsumentów na siebie. Aby zmniejszyć to ryzyko, pomiary odbyły się dla różnych projektów oraz dla różnych pozostałych ustawień programu. Ważność zewnętrzna dotyczy poprawnej generalizacji wniosków z eksperymentu na szerszą grupę podmiotów. Jeśli eksperyment zostałby powtórzony na innych podmiotach z tej grupy, wysunięte z niego wnioski powinny być zgodne z poprzednimi. Tutaj istnieje zagrożenie, że wnioski z wykonanych pomiarów nie będą rozszerzalne do innych projektów lub mutowanych metod. Z tego powodu, pomiary na procesie testowania mutacyjnego całego projektu zostały wykonane na dwóch projektach. 63

67 Zakończenie W procesie testowania mutacyjnego jedną z dwóch największych trudności stanowi długi czas wykonania, powiązany ze złożonością obliczeniową. W ramach tej pracy magisterskiej starałem się podejść do problemu z dwóch stron: Poprzez wprowadzenie do procesu interaktywności: jego podziału na mniejsze części (wykonanie w kontekście pojedynczych metod), zastosowania łatwego uruchomiania, dużej widoczności działania procesu dla użytkownika oraz możliwości wpływu na to działanie podczas jego trwania; Poprzez optymalizacje szybkości: równoległe przetwarzanie kodu źródłowego oraz wielu mutantów na raz; Pomysły związane z interaktywnością oraz wydajnością zostały opisane w pracy oraz zaimplementowane w programie VisualMutator 2.0. Do rozwiązań interaktywnych można zaliczyć: Możliwość podglądu informacji o wybranym mutancie (kodu prezentującego zmianę w nim oraz, po przetestowaniu, wyniki wykonania testów) w każdej chwili w trakcie trwania procesu. Dostępność informacji o każdym, nawet jeśli jeszcze nie stworzonym, mutancie została osiągnięta dzięki zastosowaniu dwufazowych mutacji; Łatwość uruchomienia procesu w środowisku VisualStudio zarówno w kontekście całego projektu, jak i pojedynczej metody; Natomiast z rozwiązań wydajnościowych można wymienić: Równoległe przetwarzanie wielu mutantów na raz; Przyspieszenie uzyskiwania podglądu mutanta przez użytkownika dzięki zastosowaniu komponentów: kieszeni kodu oryginalnego oraz kieszeni mutantów; Wykonane eksperymenty pokazały względną skuteczność usprawnień wydajnościowych. Zastosowanie równoległego przetwarzania mutantów pozwala osiągnąć przyspieszenie wykonania procesu testowania mutacyjnego, ale jest ono znaczące tylko w miarę zwiększania 64

68 równoległości od 1 do 3 przetwarzanych mutantów na raz. Większa skalowalność procesu na dużą liczbę wątków obliczeniowych nie została osiągnięta, przede wszystkim z powodu konieczności częstego dostępu do dysku twardego. Natomiast zastosowanie kieszeni kodu oryginalnego okazało się mniejszym zyskiem wydajnościowym niż spodziewany. Nie ma wpływu na szybkość trwania procesu, pomaga jedynie w nieznacznym stopniu przyspieszyć szybkość otrzymywania podglądu kodu mutanta. Przyszły rozwój programu VisualMutator Główne usprawnienia programu VisualMutator w wersji 2.0 polegały na zwiększeniu interaktywności oraz zwiększeniu ogólnej szybkości przeprowadzania procesu testowania mutacyjnego. Każdy z tych aspektów jest otwarty na dalsze ulepszenia. Szybsze tworzenie kopii programu oryginalnego Najbardziej pożądanym usprawnieniem w programie VisualMutator jest przyspieszenie tworzenia mutantów. W aktualnej implementacji przy każdym tworzeniu mutanta jest tworzony model całego podzespołu na podstawie danych wczytanych z dysku, który następnie zostaje ponownie zapisany na dysku twardym. Powoduje to, że stworzenie mutanta jest zazwyczaj bardziej kosztowną operacją niż jego testowanie. Poprawa szybkości tego procesu może zostać osiągnięta jeśli zostanie znaleziony sposób na częściowe kopiowanie drzewa modelu. Całkowita eliminacja dostępu do dysku twardego przy tworzeniu i testowaniu mutantów Kwestią powiązaną z eliminacją dostępu do dysku przy tworzeniu kopii jest problem zapisu mutanta w celu wykonania na nim testów zewnętrznym narzędziem. Takiego zapisu można uniknąć poprzez imlementację tworzenia podzespołów z modelu w pamięci, ich wczytywania, a następnie wykonywania ich testów w izolacji od programu. W ten sposób działa, na platformie Java, opisywany w rozdziale 2 program PIT. Przeniesienie tego pomysłu z działania na wirtualnej maszynie Java na platformę.net przynosi jednak inne problemy związane z wczytywaniem podzespołów oraz zachowaniem izolacji ich działania w obliczu wystąpienia niektórych wyjątków, na przykład przepełnienia stosu. 65

69 Szybsze rozpoczęcie procesu W kwestii interaktywności bardzo ważna jest szybkość rozpoczęcia procesu. Dopóki mutanty nie zostaną zaprezentowane użytkownikowi, nie ma on dużych możliwości interakcji z programem i jest zmuszony czekać. W aktualnej implementacji (wersja 2.0) użytkownik po wybraniu metody do mutacji w najepszym przypadku musi oczekiwać na znalezienie metody w kodzie programu źródłowego, wyszukanie przypadków testowych ją pokrywających oraz ostatecznie na przetestowanie programu źródłowego. Aby ograniczyć ten czas oczekiwania, możliwy jest szereg usprawnień: Szybsze wyszukiwanie metody do mutacji, na podstawie jej przestrzeni nazw; Szybsze wyszukiwanie przypadków testowych, na podstawie typowej ich obecności w tej samej przestrzeni nazw, co pokrywana przez nie metoda; Wyszukiwanie przypadków testowych wykonywane w tle, w trakcie pracy użytkownika: wyniki tej operacji nie są potrzebne do rozpoczęcia tworzenia mutantów, a jedynie do ich testowania; Testowanie programu oryginalnego w tle: ta operacja ma na celu jedynie ochronę przed rzadką sytuacją, w której testy są niepoprawne i wyniki testowania mutacyjnego nie przyniosą żadnej korzyści użykownikowi. Dlatego ta operacja w celu potencjalnego wyświetlenia ostrzeżenia może być wykonywana w tle, równolegle z analizą pierwszych mutantów; Rozwój interaktywności w testowaniu mutacyjnym Narzędzie PIT opisane w rozdziale 2, działające na platformie Java, pokazało, że możliwe jest bardzo szybkie tworzenie i testowanie mutantów jeśli wyeliminowany zostanie dostęp do dysku twardego. Mutanty powinny też korzystać z ponownego użycia większości kodu potrzebnego do ich tworzenia oraz powinny być przetwarzane niezależnie od siebie. W połączeniu ze skalowalnością procesu na wiele wątków obliczeniowych i w obliczu rosnącej z biegiem czasu łatwości przetwarzania równoległego, można mieć nadzieję na szersze zastosowanie testowania mutacyjnego w przemyśle oprogramowania. Patrząc na rozwój zintegrowanych środowisk programistycznych oraz łatwość wykonywania testów jednostkowych, równie ważnym elementem wzrostu popularności testowania mutacyjnego wydaje się być zwiększenie łatwości jego zastosowania. Należy dążyć do eliminacji aspektów konfiguracyjnych, stosować wygodne konwencje uruchomienia i 66

70 przebiegu testowania mutacyjnego oraz jak najszybciej prezentować użytkownikowi użyteczne wyniki. Pomysły te zostały częściowo zastosowane w programie VisualMutator, jednak inspirując się innymi programami można pomyśleć o jeszcze większej integracji ze środowiskiem programistycznym. Być może w pewnym momencie rozwoju testowania mutacyjnego będzie ono prawie niezauważalnym procesem działającym w tle, stając się dodatkową pomocą nie wymagającą wiele uwagi. Podgląd zmodyfikowanego kodu może zostać zintegrowany z edytorem środowiska, jasno prezentując miejsca, w które zostały wprowadzone mutacje (być może na wzór programów Nester i NinjaTurtles). Pozwoli to scalić testowanie mutacyjne z ogólnym procesem testowania, prowadząc do zmniejszonych kosztów tworzenia oprogramowania, z mniejszą liczbą błędów. 67

71 Bibliografia [1] Agile Alliance, The Manifesto for Agile Software Development, vol.2003: Agile Alliance, 2000 [2] Ammann P., Offutt J., Introduction to Software Testing, Cambridge University Press; 1 edition, 2008, ISBN: [3] Andreou A. S., Yiasemis P., A Specifications-Based Mutation Engine for Testing Programs in C#, ICSEA, pp.70-75, 2011, ISBN: [4] Beck K., Test Driven Development: By Example, Addison-Wesley Professional 2002, TDD. Sztuka tworzenia dobrego kodu, Helion 2014 [5] Bloch J.. Effective Java (2nd Edition) (The Java Series) (2 ed.). Prentice Hall PTR, 2008 [6] Budd T., Angluin D., Two notions of correctness and their relation to testing, Acta Informatica vol.18, no.1, pp.31-45, 1982, doi: /BF [7] Cauevic A., Punnekkat S., Sundmark D., Quality of Testing in Test Driven Development, QUATIC 2012, pp , 2012, doi: /QUATIC [8] Chevalley P., Applying mutation analysis for object-oriented programs using a reflective approach, APSEC 2001, pp , 2001, doi: /APSEC [9] Davida Musgrove, Blog, Tests and Testability, web/ / testing/ninjaturtles/, odczytano [10] DeMillo R., Guindi D., McCracken W., Offutt J., King K., An extended overview of the Mothra software testing environment, Software Testing, Verification, and Analysis, Proceedings of the Second Workshop on, pp , 1988, doi: /WST [11] Demaine E. D., Mozes S., Rossman B., Oren Weimann, An Optimal Decomposition Algorithm for Tree Edit Distance, ACM Transactions on Algorithms, vol.6, no.1, art. 2, 2009, doi: / [12] Deng L., Offutt J., Li N., Empirical evaluation of the statement deletion mutation operator, ICST 2013, pp.84-93, 2013, doi: /ICST

72 [13] Derezińska A., Advanced mutation operators applicable in C# programs, In: K. Sacha (ed.) IFIP vol.227, Software Engin. Techniques: Design for Quality, Springer, pp , 2006 [14] Derezińska A., Kowalski K., Óbject-Oriented Mutation Applied in Common Intermediate Language Programs Originated from C#", Mutation Workshop 11 col at ICSTW, Proc. of IEEE 4th Inter. Conf. Software Testing Verification and Validation Workshops (ICSTW), IEEE Comp. Soc., 2011, pp , doi: /ICSTW [15] Derezińska A., Rudnik M., Quality Evaluation of Object-Oriented and Standard Mutation Operators Applied to C# Programs, C.A. Furia and S. Nanz (Eds.): TO- OLS Europe 2012, LNCS 7304, pp.42 57, 2012, doi: / _5 [16] Derezińska A., Szustek A., CREAM - a system for object-oriented mutation of C# programs, red. Szczepański S., Kłosowski M., Felendzer Z., Gdańsk University of Technology Faculty ETI Annals, no.5, Information Technologies vol.13, pp , 2007 [17] Derezińska A., Szustek A., Tool-supported mutation approach for verification of C# programs, Proc. of Inter. Conf. on Dependability of Computer Systems, DepCoS- RELCOMEX 2008, IEEE Comp. Soc., pp , 2008, doi: /DepCoS- RELCOMEX [18] Derezińska A., Szustek A., Object-Oriented Testing Capabilities and Performance Evaluation of the C# Mutation System, CEE-SET 2009, LNCS 7054, pp , doi: / _18 [19] Derezińska A., Trzpil P., Mutation Testing of ASP.NET MVC, Swacha J. (Ed.) Advances in Software Development, Scientific Papers of the Polish Information Processing Society Scientific Council, pp , 2013 [20] Derezińska A., Specification of mutation operators specialized for C# code, ICS Res. Raport 2/05, Warsaw University of Technology, 2005 [21] Ernst M. D., Cockrell J., Griswold W. G., and Notkin D.. Dynamically discovering likely program invariants to support program evolution. IEEE Transactions on Software Engineering, vol.27, no.2, pp , 2001, doi: / [22] Estero-Botaro A., Palomo-Lozano F., Medina-Bulo I., Quantitative Evaluation of Mutation Operators for WS-BPEL Compositions, ICSTW 2010, pp , 2010, doi: /ICSTW [23] Fowler M., Inversion of Control Containers and the Dependency Injection pattern, Martin Fowler s Blog, odczytano

73 [24] Geras A., Smith M., Miller J., A prototype empirical evaluation of test driven development, Software Metrics, 2004, Proceedings. 10th International Symposium on, pp , 2004, doi: /METRIC [25] Grun B., Schuler D., Zeller A., The impact of equivalent mutants, ICSTW 2009, pp , 2009, doi: /ICSTW [26] Harman M., Yue Jia, Langdon W. B., A Manifesto for Higher Order Mutation Testing, ICSTW 2010, pp.80-89, 2010, doi: /ICSTW [27] Jia Y., Harman M.: An analysis and survey of the development of mutation testing, IEEE Transactions on Software Engineering, vol.37, no.5, pp , 2011, doi: /tse [28] Jingyu Hu, Nan Li, Offutt J., An Analysis of OO Mutation Operators, ICSTW 2011, pp , 2011, doi: /ICSTW [29] Jingyu Hu, Nan Li, Offutt J., An Analysis of OO Mutation Operators, ICSTW 2011, pp , 2011, doi: /ICSTW [30] Kaczanowski T., Practical Unit Testing with TestNG and Mockito, Tomasz Kaczanowski, [31] Kintis M., Malevris N., Identifying More Equivalent Mutants via Code Similarity, APSEC 2013, vol.1, pp , 2013, doi: /APSEC [32] Kintis M., Papadakis M., Malevris N., Isolating First Order Equivalent Mutants via Second Order Mutation, ICST 2012, pp , 2012, doi: /ICST [33] Lingming Zhang, Gligoric M., Marinov D., Khurshid S., Operator-based and random mutant selection: Better together, ASE 2013, pp.92,102, 2013 doi: /ASE [34] Ma Y. S., Kwon Y. R., Offutt J., Inter-class mutation operators for Java, in Proceedings of the 13th International Symposium on Software Reliability Engineering. Annapolis MD: IEEE Computer Society Press, pp , 2002, ISBN: [35] Ma Y. S., Offutt J., Description of Class Mutation Mutation Operators for Java, Pobrany dnia [36] Ma Y. S., Offutt J., Description of Method-level Mutation Operators for Java, odczytano [37] Ma Y., Offutt J., Kwon Y., MuJava: An Automated Class Mutation System, Journal of Software Testing, Verification and Reliability, vol.15, no.2, pp , 2005, doi: /stvr.v15:2 70

74 [38] Madeyski L., Orzeszyna W., Torkar R., Jozala M., Overcoming the Equivalent Mutant Problem: A Systematic Literature Review and a Comparative Experiment of Second Order Mutation, IEEE Transactions on Software Engineering, vol.40, no.1, pp.23-42, 2014, doi: /TSE [39] Madeyski L., Radyk N., Judy - a mutation testing tool for Java, Software, IET, vol.4, no.1, pp.32-42, 2010, doi: /iet-sen [40] Martin R. C., Principles Of OOD, butunclebob.com, odczytano http: //butunclebob.com/articles.unclebob.principlesofood [41] Mathur A. P., Performance, effectiveness, and reliability issues in software testing, Proceedings of the Fifteenth Annual International, pp , 1991, doi: /CMPSAC [42] Moore I., Jester: A JUnit Test Tester, 2nd International Conference on extreme Programming and Flexible Processes in Software Engineering, 2001, doi: [43] Namin A.S., Andrews J.H., Murdoch D., Sufficient mutation operators for measuring test effectiveness, ICSE 2008, pp , 2008, doi: / [44] Nan Li, Praphamontripong U., Offutt J., An Experimental Comparison of Four Unit Test Criteria: Mutation, Edge-Pair, All-Uses and Prime Path Coverage, ICSTW 2009, pp , 2009, doi: /ICSTW [45] Offutt J., Lee A., Rothermel G., Untch R.H., Zapf C., An experimental determination of sufficient mutant operators, TOSEM, vol.5, no.2, pp , 1996, doi: / [46] Offutt J., Lee D., An Empirical Evaluation of Weak Mutation, IEEE Trans. on Soft. Eng., vol.20, no.5, pp , 1994, doi: / [47] Offutt J., Yu-SeungMa, Yong-Rae Kwon, The Class-Level Mutants of Mu- Java, Workshop on Automation of Software Test (AST 2006), pp.78-84, 2006, doi: / [48] Offutt J., A Mutation Carol: Past, Present and Future, Information Software Technology, vol.53, no.10, pp , 2011, doi: /j.infsof [49] Offutt J., Investigations of the software testing coupling effect, ACM Transactions on Software Engineering Methodology, vol.1, no.1, pp.3 18, 1992, doi: / [50] Overload Magazine 108, May Mutation Testing. pp.16-22, var/uploads/journals/overload108.pdf, odczytano

75 [51] Rimmer C., Getting Mutants to Test your Tests, Oxford Geek Nights 35 presentation, 2012 [52] Schuler D., Dallmeier V., and Zeller A.. Efficient mutation testing by checking invariant violations, Proceedings of the 2009 International Symposium on Software Testing and Analysis, pp.69-80, 2009, doi: / [53] Schuler D., Zeller A., (Un-)Covering Equivalent Mutants, ICST 2010, pp.45-54, 2010, doi: /ICST [54] Schuler D., Zeller A., Javalanche: efficient mutation testing for Java, Proceedings of the the 7th joint meeting of the European software engineering conference and the ACM SIGSOFT symposium on The foundations of software engineering, pp , 2009, doi: / [55] Tao X., Tillmann N., Halleux, J. de, Schulte W., Mutation Analysis of Parameterized Unit Tests, ICSTW 2009, pp , doi: /ICSTW [56] Tillmann N., Halleux J. de, Pex - White Box Test Generation for.net, in Proc. of Tests and Proofs (TAP 08), Springer Verlag, 2008, ISBN: X [57] Trzpil P., Scoping Hierarchical Dependency Injection, com/2014/03/scoping-dependency-injection.html, odczytano [58] Trzpil P., Testowanie mutacyjne na platformie ASP.NET MVC, B.Sc. Thesis, Institute of Computer Science, Warsaw University of Technology, 2012 [59] Untch R., Offutt J., Harrold M., Mutation analysis using program schemata, Proceedings of the 1993 International Symposium on Software Testing and Analysis, pp , 1993, doi: / [60] Untch R., On reduced neighborhood mutation analysis using a single mutagenic operator, ACM Southeast Regional Conference, Clemson SC, pp.19 21, 2009, doi: / [61] Vu J.H., Frojd N., Shenkel-Therolf C., Janzen D. S., Evaluating Test-Driven Development in an Industry-Sponsored Capstone Project, ITNG 2009, pp , 2009, doi: /ITNG [62] Wohlin C., Runeson P., Heost M., Ohlsson M. C., Regnell B., Wesslen A., Experimentation in Software Engineering: An Introduction, Kluwer Academic Publishers, 2000 [63] Xie T., Tillmann N., Halleux P. de, Schulte W., Fitness-Guided Path Exploration in Dynamic Symbolic Execution, DSN 2009, pp doi: /DSN

76 [64] Yiasemis P., Andreou S. A., Testing Object-Oriented Code Through a Specifications-Based Mutation Engine, International Journal on Advances in Software, vol.5 no.3&4, pp , 2012 [65] Zhang L., Hou S.; Hu J.; Xie T.; Hong Mei, Is operator-based mutant selection superior to random mutant selection?, Software Engineering, 2010 ACM/IEEE 32nd International Conference on, vol.1, pp , 2010, doi: / [66] Zhong Shi, Liping C., Tian-en C., Agile planning and development methods, IC- CRD 2011, vol.1, pp , 2011, doi: /ICCRD [67] Advanced Concepts: Parameterized Unit Testing with Microsoft Pex, Microsoft Corporation, 2010, pexconcepts.pdf, oczytano [68] Test your tests with Jester ( library/j-jester/): Odczytano wersję z dnia pod adresem: developerworks/library/j-jester/ ( ) Strony programów [69] Common Compiler Infrastructure - http: //cciast.codeplex.com/, odczytano [70] CREAM - strona programu: odczytano [71] Dsa - strona projektu: odczytano [72] Javalanche - strona programu: odczytano [73] Jester - strona programu: odczytano [74] Judy - dokumentacja: [75] Jumble - strona programu: odczytano [76] Jumble - operatory mutacyjne odczytano

77 [77] Metadane w komponentach plaftormy.net: library/xcd8txaw(v=vs.110).aspx, odczytano [78] MiscUtil - strona projektu: odczytano [79] Mono.Cecil - strona projektu: odczytano [80] Mujava - strona projektu [81] MuClipse - strona projektu: odczytano [82] Nester - strona projektu: odczytano [83] NinjaTurtles - strona projektu: odczytano [84] Ninject, odczytano [85] Ninject, Child Kernel Extension, extensions.childkernel, odczytano [86] Pex - strona projektu: wyświetlona [87] PIT - strona projektu: odczytano

78 Dodatek A VisualMutator 2.0 User Manual A1. Overview VisualMutator is a mutation testing tool and a Visual Studio extension that can be used to verify quality of a test suite in the active solution. It operates by executing the tests on automatically created original program copies (mutants), each containing one artificial fault. The test suite quality is based on a number of killed mutants - programs in which at least one test failed as a result of an artificial fault. Features Creating first-order mutants by using built-in and custom mutation operators; Ability to view modified code fragments in C# and IL languages; Running NUnit and XUnit tests on generated mutants; Interactive user interface: ability to view details about any mutant right after the start of the mutation testing process; Presenting results: the mutation score and an information about passed and failed tests; Option to write detailed results to an XML file; A2. Installation The Visual Studio (Professional version or higher) version 2012 or 2013 IDE is required to install and use the VisualMutator extension. In order to install VisualMutator, execute the.vsix file and follow the instructions. The extension will be registered in Visual Studio 75

79 after relaunching the IDE. If VisualMutator was installed earlier, you must uninstall it before installing the new version. To uninstall VisualMutator from within Visual Studio, click menu item Tools -> Extensions and Updates, choose VisualMutator from the list in the Extensions and Updates window and click the Uninstall button. A3. Getting started VisualMutator consists of a one Visual Studio tool window that can be placed and resized anywhere in the parent environment. Its dimensions may be freely customized; the vertical layout is recommended however. Tip: If the VisualMutator tool window is not visible, open it by selecting menu item: View -> Other Windows -> VisualMutator. A3.1 Creating a mutation testing session A mutation testing session, in the context of VisualMutator, is a single instance of a coherent process involving automated program operations and feedback for the user. It consists of creating and testing mutated program copies while allowing the user to control the process and gain useful information about the quality of the test suite. Note: The Visual Studio solution should be built and up-to-date before creating a new mutation testing session. Note: It is strongly recommended to ensure that all the tests pass before starting a mutation testing session. If this is not the case, all mutants are going to be killed, leading to invalid and useless results. 76

80 There are 2 approaches to starting a session: it can be run on the whole codebase or in a context of a single chosen method. The first approach is invoked by clicking the New Session... button. The scope can be further refined, however it should be noted, that starting the session on most of the project code is most often a very time-consuming operation. If you do not need to measure the quality of the whole test suite at once, a more ad-hoc approach is recommended. A limited-scope mutation session can be started by right-clicking inside the body of a non-test method in the Visual Studio code editor and choosing the Mutate and test... action. This is a fast and recommended way to check the quality of the tests that call the selected method - the scope of mutation testing is automatically narrowed. Regardless of the initial approach you choose, the mutation testing session creation window appears (fig. A.2): A3.1.1 Running the session on the whole codebase The type tree on the left, labelled Mutate, presents the types and methods detected in the solution s assemblies. The checkboxes can be used to deselect any nodes in order to be excluded from the the mutation process. Note: All types containing tests should be deselected in the left pane to avoid mutating them. A mutated test would, if run, cause the mutation session results to be inacurate. The middle pane Run tests presents the tests detected in the codebase. They are to be executed on every created mutant. By default, all tests are run; deselect any tests to turn off their execution (i.e. the test that are not related to the code selected in the left pane). In the right pane, available mutation operators can be deselected to exclude them from the mutant creation process. Overall, the tuning process performed by the user should include the following steps: 1. In the left pane, deselect any code elements that are not desired to be mutated in the particular use case (for example the codebase that had been mutation-tested before, or a project that is to be mutated at a later time, due to its size); 77

81 Fig. A.1. VisualMutator - mutated code view 2. In the left pane, deselect any code that is strictly test-related (unit tests, functional tests, test helper classes); 3. In the middle pane, deselect any tests that are not desired to be run (e.g. longrunning functional tests, or test that are unrelated to the code selected in the left pane); 4. In the right pane, deselect any mutation operators that are not to be used (due to a large number of equivalent mutants generated, for example); A3.1.2 Running the session on a single method When the session creation window is shown as a result of using the Mutate and test... action on a method, it makes more choices automatic and not involving the user: 78

82 Fig. A.2. VisualMutator - session creation window In the left pane, only the specific method is selected; In the middle pane, only the tests invoking the method are selected. However, the selection may require further tuning; Regardless of the approach, you can tune the selections to suite your needs. It is possible to manually tune the first approach to have its scope narrowed to the one similar to the second approach. A3.2 Starting the session Clicking on the Start session button closes the window and starts the session process. The session consists of the following steps: 1. Pre-check (testing unmodified assemblies); 2. Creating mutant list; 3. Instantiating and testing the mutants (possibly in parallel); 79

83 Current status of the session is visible in the upper-left corner of the VisualMutator window. The first step of a session is the pre-check, aiming to test unmodified assemblies. During the pre-check, the test cases are executed on the unmodified program to ensure that all pass. In case of a test failure, a warning window is displayed. After the pre-check, operators selected before are used to generate information about all mutants, grouped by location in the codebase. The next, main, step is the testing of each mutant. For each mutant, all enabled test cases are executed. During and after this process, you can view the details of a selected mutant - the mutated code fragment and testing results (if the mutant has been already tested) - fig. A.3. Fig. A.3. VisualMutator - test tree view The session s big picture Current overview of the running session is shown in the upper part of the VisualMutator window: 80

Testowanie mutacyjne

Testowanie mutacyjne Testowanie mutacyjne Czyli jak dobre w rzeczywistości są Twoje testy? Marcin Zajączkowski m.zajaczkowski@gmail.com Warszawa, 2013-07-06 Ja technicznie Java architect TDD practitioner Team mentor Clean

Bardziej szczegółowo

Programowanie Strukturalne i Obiektowe Słownik podstawowych pojęć 1 z 5 Opracował Jan T. Biernat

Programowanie Strukturalne i Obiektowe Słownik podstawowych pojęć 1 z 5 Opracował Jan T. Biernat Programowanie Strukturalne i Obiektowe Słownik podstawowych pojęć 1 z 5 Program, to lista poleceń zapisana w jednym języku programowania zgodnie z obowiązującymi w nim zasadami. Celem programu jest przetwarzanie

Bardziej szczegółowo

Michał Olejnik. 22 grudnia 2009

Michał Olejnik. 22 grudnia 2009 Continuous TDD Politechnika Wrocławska Informatyka 22 grudnia 2009 Agenda Wprowadzenie 1 Wprowadzenie 2 3 4 5 Agenda Wprowadzenie 1 Wprowadzenie 2 3 4 5 Agenda Wprowadzenie 1 Wprowadzenie 2 3 4 5 Agenda

Bardziej szczegółowo

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki Dariusz Brzeziński Politechnika Poznańska, Instytut Informatyki Język programowania prosty bezpieczny zorientowany obiektowo wielowątkowy rozproszony przenaszalny interpretowany dynamiczny wydajny Platforma

Bardziej szczegółowo

Wykład Ćwiczenia Laboratorium Projekt Seminarium

Wykład Ćwiczenia Laboratorium Projekt Seminarium WYDZIAŁ ELEKTRONIKI KARTA PRZEDMIOTU Nazwa w języku polskim Języki programowania Nazwa w języku angielskim Programming languages Kierunek studiów (jeśli dotyczy): Informatyka - INF Specjalność (jeśli dotyczy):

Bardziej szczegółowo

Etapy życia oprogramowania

Etapy życia oprogramowania Modele cyklu życia projektu informatycznego Organizacja i Zarządzanie Projektem Informatycznym Jarosław Francik marzec 23 w prezentacji wykorzystano również materiały przygotowane przez Michała Kolano

Bardziej szczegółowo

Programowanie komputerów

Programowanie komputerów Programowanie komputerów Wykład 1-2. Podstawowe pojęcia Plan wykładu Omówienie programu wykładów, laboratoriów oraz egzaminu Etapy rozwiązywania problemów dr Helena Dudycz Katedra Technologii Informacyjnych

Bardziej szczegółowo

INFORMATYKA, TECHNOLOGIA INFORMACYJNA ORAZ INFORMATYKA W LOGISTYCE

INFORMATYKA, TECHNOLOGIA INFORMACYJNA ORAZ INFORMATYKA W LOGISTYCE Studia podyplomowe dla nauczycieli INFORMATYKA, TECHNOLOGIA INFORMACYJNA ORAZ INFORMATYKA W LOGISTYCE Przedmiot JĘZYKI PROGRAMOWANIA DEFINICJE I PODSTAWOWE POJĘCIA Autor mgr Sławomir Ciernicki 1/7 Aby

Bardziej szczegółowo

Programowanie w języku C++ Grażyna Koba

Programowanie w języku C++ Grażyna Koba Programowanie w języku C++ Grażyna Koba Kilka definicji: Program komputerowy to ciąg instrukcji języka programowania, realizujący dany algorytm. Język programowania to zbiór określonych instrukcji i zasad

Bardziej szczegółowo

Algorytm. a programowanie -

Algorytm. a programowanie - Algorytm a programowanie - Program komputerowy: Program komputerowy można rozumieć jako: kod źródłowy - program komputerowy zapisany w pewnym języku programowania, zestaw poszczególnych instrukcji, plik

Bardziej szczegółowo

Technologie informacyjne - wykład 12 -

Technologie informacyjne - wykład 12 - Zakład Fizyki Budowli i Komputerowych Metod Projektowania Instytut Budownictwa Wydział Budownictwa Lądowego i Wodnego Politechnika Wrocławska Technologie informacyjne - wykład 12 - Prowadzący: Dmochowski

Bardziej szczegółowo

Etapy życia oprogramowania. Modele cyklu życia projektu. Etapy życia oprogramowania. Etapy życia oprogramowania

Etapy życia oprogramowania. Modele cyklu życia projektu. Etapy życia oprogramowania. Etapy życia oprogramowania Etapy życia oprogramowania Modele cyklu życia projektu informatycznego Organizacja i Zarządzanie Projektem Informatycznym Jarosław Francik marzec 23 Określenie wymagań Testowanie Pielęgnacja Faza strategiczna

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Programowanie obiektowe Laboratorium 1. Wstęp do programowania w języku Java. Narzędzia 1. Aby móc tworzyć programy w języku Java, potrzebny jest zestaw narzędzi Java Development Kit, który można ściągnąć

Bardziej szczegółowo

Podstawy programowania. Wprowadzenie

Podstawy programowania. Wprowadzenie Podstawy programowania Wprowadzenie Proces tworzenia programu Sformułowanie problemu funkcje programu zakres i postać danych postać i dokładność wyników Wybór / opracowanie metody rozwiązania znaleźć matematyczne

Bardziej szczegółowo

1 Podstawy c++ w pigułce.

1 Podstawy c++ w pigułce. 1 Podstawy c++ w pigułce. 1.1 Struktura dokumentu. Kod programu c++ jest zwykłym tekstem napisanym w dowolnym edytorze. Plikowi takiemu nadaje się zwykle rozszerzenie.cpp i kompiluje za pomocą kompilatora,

Bardziej szczegółowo

Analiza i projektowanie obiektowe 2016/2017. Wykład 10: Tworzenie projektowego diagramu klas

Analiza i projektowanie obiektowe 2016/2017. Wykład 10: Tworzenie projektowego diagramu klas Analiza i projektowanie obiektowe 2016/2017 Wykład 10: Tworzenie projektowego diagramu klas Jacek Marciniak Wydział Matematyki i Informatyki Uniwersytet im. Adama Mickiewicza 1 Plan wykładu 1. Projektowy

Bardziej szczegółowo

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018 Informatyka I Klasy i obiekty. Podstawy programowania obiektowego dr inż. Andrzej Czerepicki Politechnika Warszawska Wydział Transportu 2018 Plan wykładu Pojęcie klasy Deklaracja klasy Pola i metody klasy

Bardziej szczegółowo

Programowanie obiektowe - 1.

Programowanie obiektowe - 1. Programowanie obiektowe - 1 Mariusz.Masewicz@cs.put.poznan.pl Programowanie obiektowe Programowanie obiektowe (ang. object-oriented programming) to metodologia tworzenia programów komputerowych, która

Bardziej szczegółowo

Paradygmaty programowania

Paradygmaty programowania Paradygmaty programowania Jacek Michałowski, Piotr Latanowicz 15 kwietnia 2014 Jacek Michałowski, Piotr Latanowicz () Paradygmaty programowania 15 kwietnia 2014 1 / 12 Zadanie 1 Zadanie 1 Rachunek predykatów

Bardziej szczegółowo

Jeśli chcesz łatwo i szybko opanować podstawy C++, sięgnij po tę książkę.

Jeśli chcesz łatwo i szybko opanować podstawy C++, sięgnij po tę książkę. Języki C i C++ to bardzo uniwersalne platformy programistyczne o ogromnych możliwościach. Wykorzystywane są do tworzenia systemów operacyjnych i oprogramowania użytkowego. Dzięki niskiemu poziomowi abstrakcji

Bardziej szczegółowo

Program szkolenia: Test Driven Development (TDD) using Spock or JUnit 5

Program szkolenia: Test Driven Development (TDD) using Spock or JUnit 5 Program szkolenia: Test Driven Development (TDD) using Spock or JUnit 5 Informacje: Nazwa: Test Driven Development (TDD) using Spock or JUnit 5 Kod: craft-test-tdd Kategoria: Testowanie automatyczne Grupa

Bardziej szczegółowo

Laboratorium Informatyka (I) AiR Ćwiczenia z debugowania

Laboratorium Informatyka (I) AiR Ćwiczenia z debugowania Laboratorium Informatyka (I) AiR Ćwiczenia z debugowania Krzysztof Kluza, Janusz Miller 1 Debugowanie Debugowanie, czy też po polsku odpluskiwanie, to proces polegający na kontrolowanym wykonaniu programu

Bardziej szczegółowo

Maciej Oleksy Zenon Matuszyk

Maciej Oleksy Zenon Matuszyk Maciej Oleksy Zenon Matuszyk Jest to proces związany z wytwarzaniem oprogramowania. Jest on jednym z procesów kontroli jakości oprogramowania. Weryfikacja oprogramowania - testowanie zgodności systemu

Bardziej szczegółowo

System zarządzający grami programistycznymi Meridius

System zarządzający grami programistycznymi Meridius System zarządzający grami programistycznymi Meridius Instytut Informatyki, Uniwersytet Wrocławski 20 września 2011 Promotor: prof. Krzysztof Loryś Gry komputerowe a programistyczne Gry komputerowe Z punktu

Bardziej szczegółowo

Konstruktory. Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut.

Konstruktory. Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut. Konstruktory Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut. Rozpatrzmy przykład przedstawiający klasę Prostokat: class

Bardziej szczegółowo

Czym jest Java? Rozumiana jako środowisko do uruchamiania programów Platforma software owa

Czym jest Java? Rozumiana jako środowisko do uruchamiania programów Platforma software owa 1 Java Wprowadzenie 2 Czym jest Java? Język programowania prosty zorientowany obiektowo rozproszony interpretowany wydajny Platforma bezpieczny wielowątkowy przenaszalny dynamiczny Rozumiana jako środowisko

Bardziej szczegółowo

Wprowadzenie do metodologii modelowania systemów informacyjnych. Strategia (1) Strategia (2) Etapy Ŝycia systemu informacyjnego

Wprowadzenie do metodologii modelowania systemów informacyjnych. Strategia (1) Strategia (2) Etapy Ŝycia systemu informacyjnego Etapy Ŝycia systemu informacyjnego Wprowadzenie do metodologii modelowania systemów informacyjnych 1. Strategia 2. Analiza 3. Projektowanie 4. Implementowanie, testowanie i dokumentowanie 5. WdroŜenie

Bardziej szczegółowo

Nazwa wariantu modułu (opcjonalnie): Laboratorium programowania w języku C++

Nazwa wariantu modułu (opcjonalnie): Laboratorium programowania w języku C++ Uniwersytet Śląski w Katowicach str. 1 Kierunek i poziom studiów: Chemia, poziom pierwszy Sylabus modułu: Laboratorium programowania (0310-CH-S1-019) Nazwa wariantu modułu (opcjonalnie): Laboratorium programowania

Bardziej szczegółowo

Tester oprogramowania 2014/15 Tematy prac dyplomowych

Tester oprogramowania 2014/15 Tematy prac dyplomowych Tester oprogramowania 2014/15 Tematy prac dyplomowych 1. Projekt i wykonanie automatycznych testów funkcjonalnych wg filozofii BDD za pomocą dowolnego narzędzia Jak w praktyce stosować Behaviour Driven

Bardziej szczegółowo

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1 Podstawy programowania. Wykład Funkcje Krzysztof Banaś Podstawy programowania 1 Programowanie proceduralne Pojęcie procedury (funkcji) programowanie proceduralne realizacja określonego zadania specyfikacja

Bardziej szczegółowo

INŻYNIERIA OPROGRAMOWANIA TESTOWANIE SYSTEMOWE

INŻYNIERIA OPROGRAMOWANIA TESTOWANIE SYSTEMOWE INŻYNIERIA OPROGRAMOWANIA TESTOWANIE SYSTEMOWE Ważne pojęcia (I) Warunek testowy (test condition) to element lub zdarzenie modułu lub systemu, który może być zweryfikowany przez jeden lub więcej przypadków

Bardziej szczegółowo

Wzorce projektowe i refaktoryzacja

Wzorce projektowe i refaktoryzacja Wzorce projektowe i refaktoryzacja Paweł Kozioł p.koziol@students.mimuw.edu.pl 18.01.2005 Moja praca magisterska Narzędzie dla środowiska Eclipse wspierające stosowanie wzorców projektowych J2EE Prowadzący:

Bardziej szczegółowo

Optimizing Programs with Intended Semantics

Optimizing Programs with Intended Semantics Interaktywna optymalizacja programów 26 kwietnia 2010 Spis treści Spis treści Wstęp Omówienie zaproponowanego algorytmu na przykładzie Wewnętrzna reprezentacja reguł dotyczących optymalizacji Wybrane szczegóły

Bardziej szczegółowo

XQTav - reprezentacja diagramów przepływu prac w formacie SCUFL przy pomocy XQuery

XQTav - reprezentacja diagramów przepływu prac w formacie SCUFL przy pomocy XQuery http://xqtav.sourceforge.net XQTav - reprezentacja diagramów przepływu prac w formacie SCUFL przy pomocy XQuery dr hab. Jerzy Tyszkiewicz dr Andrzej Kierzek mgr Jacek Sroka Grzegorz Kaczor praca mgr pod

Bardziej szczegółowo

SZYBKO ZROZUMIEĆ VISUAL BASIC 2012 Artur Niewiarowski -

SZYBKO ZROZUMIEĆ VISUAL BASIC 2012 Artur Niewiarowski - S t r o n a 2 SZYBKO ZROZUMIEĆ VISUAL BASIC 2012 Artur Niewiarowski - Copyright by Artur Niewiarowski 2013 ISBN: 978-83-937802-0-4 - Artur Niewiarowski Self-Publishing - All rights reserved. Wszelkie prawa

Bardziej szczegółowo

Język JAVA podstawy. wykład 1, część 2. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Język JAVA podstawy. wykład 1, część 2. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna Język JAVA podstawy wykład 1, część 2 1 Język JAVA podstawy Plan wykładu: 1. Krótka historia Javy 2. Jak przygotować sobie środowisko programistyczne 3. Opis środowiska JDK 4. Tworzenie programu krok po

Bardziej szczegółowo

Feature Driven Development

Feature Driven Development Feature Driven Development lekka metodyka tworzenia oprogramowania Kasprzyk Andrzej IS II Wstęp Feature Driven Development (FDD) to metodyka tworzenia oprogramowania, która wspomaga zarządzanie fazami

Bardziej szczegółowo

Podstawy Programowania Obiektowego

Podstawy Programowania Obiektowego Podstawy Programowania Obiektowego Wprowadzenie do programowania obiektowego. Pojęcie struktury i klasy. Spotkanie 03 Dr inż. Dariusz JĘDRZEJCZYK Tematyka wykładu Idea programowania obiektowego Definicja

Bardziej szczegółowo

Java jako język programowania

Java jako język programowania Java jako język programowania Interpretowany programy wykonują się na wirtualnej maszynie (JVM Java Virtual Machine) Składnia oparta o język C++ W pełni zorientowany obiektowo (wszystko jest obiektem)

Bardziej szczegółowo

Tematy seminariów wg Roger S. Pressman, Praktyczne podejście do oprogramowania, WNT, Zofia Kruczkiewicz

Tematy seminariów wg Roger S. Pressman, Praktyczne podejście do oprogramowania, WNT, Zofia Kruczkiewicz Tematy seminariów wg Roger S. Pressman, Praktyczne podejście do oprogramowania, WNT, 2004 Zofia Kruczkiewicz 1. Przedstaw znaczenie oprogramowania we współczesnym świecie x 1 2. Jaki wpływ na ludzi, komunikację

Bardziej szczegółowo

Grzegorz Ruciński. Warszawska Wyższa Szkoła Informatyki 2011. Promotor dr inż. Paweł Figat

Grzegorz Ruciński. Warszawska Wyższa Szkoła Informatyki 2011. Promotor dr inż. Paweł Figat Grzegorz Ruciński Warszawska Wyższa Szkoła Informatyki 2011 Promotor dr inż. Paweł Figat Cel i hipoteza pracy Wprowadzenie do tematu Przedstawienie porównywanych rozwiązań Przedstawienie zalet i wad porównywanych

Bardziej szczegółowo

PLAN WYNIKOWY PROGRAMOWANIE APLIKACJI INTERNETOWYCH. KL IV TI 6 godziny tygodniowo (6x15 tygodni =90 godzin ),

PLAN WYNIKOWY PROGRAMOWANIE APLIKACJI INTERNETOWYCH. KL IV TI 6 godziny tygodniowo (6x15 tygodni =90 godzin ), PLAN WYNIKOWY PROGRAMOWANIE APLIKACJI INTERNETOWYCH KL IV TI 6 godziny tygodniowo (6x15 tygodni =90 godzin ), Program 351203 Opracowanie: Grzegorz Majda Tematyka zajęć 2. Przygotowanie środowiska pracy

Bardziej szczegółowo

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji.

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji. JAVA Java jest wszechstronnym językiem programowania, zorientowanym obiektowo, dostarczającym możliwość uruchamiania apletów oraz samodzielnych aplikacji. Java nie jest typowym kompilatorem. Źródłowy kod

Bardziej szczegółowo

Zapisywanie algorytmów w języku programowania

Zapisywanie algorytmów w języku programowania Temat C5 Zapisywanie algorytmów w języku programowania Cele edukacyjne Zrozumienie, na czym polega programowanie. Poznanie sposobu zapisu algorytmu w postaci programu komputerowego. Zrozumienie, na czym

Bardziej szczegółowo

Wykład V. Rzut okiem na języki programowania. Studia Podyplomowe INFORMATYKA Podstawy Informatyki

Wykład V. Rzut okiem na języki programowania. Studia Podyplomowe INFORMATYKA Podstawy Informatyki Studia Podyplomowe INFORMATYKA Podstawy Informatyki Wykład V Rzut okiem na języki programowania 1 Kompilacja vs. interpretacja KOMPILACJA Proces, który przetwarza program zapisany w języku programowania,

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Laboratorium z przedmiotu Programowanie obiektowe - zestaw 02 Cel zajęć. Celem zajęć jest zapoznanie z praktycznymi aspektami projektowania oraz implementacji klas i obiektów z wykorzystaniem dziedziczenia.

Bardziej szczegółowo

Języki i paradygmaty programowania doc. dr inż. Tadeusz Jeleniewski

Języki i paradygmaty programowania doc. dr inż. Tadeusz Jeleniewski Języki i paradygmaty programowania doc. dr inż. Tadeusz Jeleniewski e-mail: t.jeleniewski@neostrada.pl tadeusz.jeleniewski@pwr.wroc.pl http://www.tjeleniewski.wstt.edu.pl Treści kształcenia: Paradygmaty

Bardziej szczegółowo

SZKOLENIE TWORZENIE SYSTEMÓW

SZKOLENIE TWORZENIE SYSTEMÓW SZKOLENIE TWORZENIE SYSTEMÓW INFORMATYCZNYCH Z UŻYCIEM GROOVY I GRAILS KOD: JGR Strona 1 1 Opis Platforma Java EE to zbiór zaawansowanych narzędzi umożliwiających tworzenie systemów korporacyjnych. Jest

Bardziej szczegółowo

Dodatkowo planowane jest przeprowadzenie oceny algorytmów w praktycznym wykorzystaniu przez kilku niezależnych użytkowników ukończonej aplikacji.

Dodatkowo planowane jest przeprowadzenie oceny algorytmów w praktycznym wykorzystaniu przez kilku niezależnych użytkowników ukończonej aplikacji. Spis Treści 1. Wprowadzenie... 2 1.1 Wstęp... 2 1.2 Cel pracy... 2 1.3 Zakres pracy... 2 1.4 Użyte technologie... 2 1.4.1 Unity 3D... 3 2. Sztuczna inteligencja w grach komputerowych... 4 2.1 Zadanie sztucznej

Bardziej szczegółowo

Techniki (automatyzacji) projektowania testów. Adam Roman WarszawQA, 24 II 2016

Techniki (automatyzacji) projektowania testów. Adam Roman WarszawQA, 24 II 2016 Techniki (automatyzacji) projektowania testów Adam Roman WarszawQA, 24 II 2016 Prelegent Quality Assurance R&D Lead, Rivet Group Adiunkt w Instytucie Informatyki i Matematyki Komputerowej UJ Członek Stowarzyszenia

Bardziej szczegółowo

Czym są właściwości. Poprawne projektowanie klas

Czym są właściwości. Poprawne projektowanie klas Z akcesorów get i set korzysta każdy kto programuje w C#. Stanowią one duże udogodnienie w programowaniu obiektowym. Zapewniają wygodę, bezpieczeństwo i znacząco skracają kod. Akcesory są ściśle związane

Bardziej szczegółowo

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie Część X C++ Typ znakowy służy do reprezentacji pojedynczych znaków ASCII, czyli liter, cyfr, znaków przestankowych i innych specjalnych znaków widocznych na naszej klawiaturze (oraz wielu innych, których

Bardziej szczegółowo

Testowanie oprogramowania. Testowanie oprogramowania 1/34

Testowanie oprogramowania. Testowanie oprogramowania 1/34 Testowanie oprogramowania Testowanie oprogramowania 1/34 Testowanie oprogramowania 2/34 Cele testowania testowanie polega na uruchamianiu oprogramowania w celu wykrycia błędów, dobry test to taki, który

Bardziej szczegółowo

Tematy seminariów wg Roger S. Pressman, Praktyczne podejście do oprogramowania, WNT, Zofia Kruczkiewicz

Tematy seminariów wg Roger S. Pressman, Praktyczne podejście do oprogramowania, WNT, Zofia Kruczkiewicz Tematy seminariów wg Roger S. Pressman, Praktyczne podejście do oprogramowania, WNT, 2004 Zofia Kruczkiewicz 1. Przedstaw znaczenie oprogramowania we współczesnym świecie. x 3 2. Jaki wpływ na ludzi, komunikację

Bardziej szczegółowo

Wprowadzenie. Organizacja pracy i środowisko programistyczne. Mirosław Ochodek

Wprowadzenie. Organizacja pracy i środowisko programistyczne. Mirosław Ochodek Wprowadzenie Organizacja pracy i środowisko programistyczne Mirosław Ochodek Miroslaw.Ochodek@pwsz.pila.pl Miroslaw.Ochodek@cs.put.poznan.pl Dane kontaktowe Mirosław Ochodek E-mail: Miroslaw.Ochodek@pwsz.pila.pl

Bardziej szczegółowo

Historia modeli programowania

Historia modeli programowania Języki Programowania na Platformie.NET http://kaims.eti.pg.edu.pl/ goluch/ goluch@eti.pg.edu.pl Maszyny z wbudowanym oprogramowaniem Maszyny z wbudowanym oprogramowaniem automatyczne rozwiązywanie problemu

Bardziej szczegółowo

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki Dariusz Brzeziński Politechnika Poznańska, Instytut Informatyki Object-oriented programming Najpopularniejszy obecnie styl (paradygmat) programowania Rozwinięcie koncepcji programowania strukturalnego

Bardziej szczegółowo

Podstawy programowania

Podstawy programowania Podstawy programowania Część pierwsza Od języka symbolicznego do języka wysokiego poziomu Autor Roman Simiński Kontakt roman.siminski@us.edu.pl www.us.edu.pl/~siminski Niniejsze opracowanie zawiera skrót

Bardziej szczegółowo

Modelowanie diagramów klas w języku UML. Łukasz Gorzel 244631@stud.umk.pl 7 marca 2014

Modelowanie diagramów klas w języku UML. Łukasz Gorzel 244631@stud.umk.pl 7 marca 2014 Modelowanie diagramów klas w języku UML Łukasz Gorzel 244631@stud.umk.pl 7 marca 2014 Czym jest UML - Unified Modeling Language - Rodzina języków modelowania graficznego - Powstanie na przełomie lat 80

Bardziej szczegółowo

Testowanie i walidacja oprogramowania

Testowanie i walidacja oprogramowania i walidacja oprogramowania Inżynieria oprogramowania, sem.5 cz. 3 Rok akademicki 2010/2011 Dr inż. Wojciech Koziński Zarządzanie testami Cykl życia testów (proces) Planowanie Wykonanie Ocena Dokumentacja

Bardziej szczegółowo

Programowanie w języku Python. Grażyna Koba

Programowanie w języku Python. Grażyna Koba Programowanie w języku Python Grażyna Koba Kilka definicji Program komputerowy to ciąg instrukcji języka programowania, realizujący dany algorytm. Język programowania to zbiór określonych instrukcji i

Bardziej szczegółowo

Błędy procesu tworzenia oprogramowania (Badania firmy Rational Software Corporation)

Błędy procesu tworzenia oprogramowania (Badania firmy Rational Software Corporation) Błędy procesu tworzenia oprogramowania (Badania firmy Rational Software Corporation) Zarządzanie wymaganiami Ad hoc (najczęściej brak zarządzania nimi) Niejednoznaczna, nieprecyzyjna komunikacja Architektura

Bardziej szczegółowo

Definicje. Algorytm to:

Definicje. Algorytm to: Algorytmy Definicje Algorytm to: skończony ciąg operacji na obiektach, ze ściśle ustalonym porządkiem wykonania, dający możliwość realizacji zadania określonej klasy pewien ciąg czynności, który prowadzi

Bardziej szczegółowo

Analiza i projektowanie oprogramowania. Analiza i projektowanie oprogramowania 1/32

Analiza i projektowanie oprogramowania. Analiza i projektowanie oprogramowania 1/32 Analiza i projektowanie oprogramowania Analiza i projektowanie oprogramowania 1/32 Analiza i projektowanie oprogramowania 2/32 Cel analizy Celem fazy określania wymagań jest udzielenie odpowiedzi na pytanie:

Bardziej szczegółowo

PRZEWODNIK PO PRZEDMIOCIE

PRZEWODNIK PO PRZEDMIOCIE Nazwa przedmiotu: Kierunek: Informatyka Rodzaj przedmiotu: obowiązkowy w ramach specjalności: Programowanie aplikacji internetowych Rodzaj zajęć: laboratorium PRZEWODNIK PO PRZEDMIOCIE I KARTA PRZEDMIOTU

Bardziej szczegółowo

Zaawansowane programowanie obiektowe - wykład 5

Zaawansowane programowanie obiektowe - wykład 5 Zaawansowane programowanie obiektowe - wykład 5 dr Piotr Jastrzębski (czynnościowe) opisują zachowanie obiektów, komunikację pomiędzy nimi i ich odpowiedzialność. Interpreter Iterator (kursor) Łańcuch

Bardziej szczegółowo

Zaawansowane programowanie w języku C++

Zaawansowane programowanie w języku C++ Kod szkolenia: Tytuł szkolenia: C/ADV Zaawansowane programowanie w języku C++ Dni: 3 Opis: Uczestnicy szkolenia zapoznają się z metodami wytwarzania oprogramowania z użyciem zaawansowanych mechanizmów

Bardziej szczegółowo

Komputerowe Systemy Przemysłowe: Modelowanie - UML. Arkadiusz Banasik arkadiusz.banasik@polsl.pl

Komputerowe Systemy Przemysłowe: Modelowanie - UML. Arkadiusz Banasik arkadiusz.banasik@polsl.pl Komputerowe Systemy Przemysłowe: Modelowanie - UML Arkadiusz Banasik arkadiusz.banasik@polsl.pl Plan prezentacji Wprowadzenie UML Diagram przypadków użycia Diagram klas Podsumowanie Wprowadzenie Języki

Bardziej szczegółowo

WPROWADZENIE DO JĘZYKA JAVA

WPROWADZENIE DO JĘZYKA JAVA WPROWADZENIE DO JĘZYKA JAVA programowanie obiektowe KRÓTKA HISTORIA JĘZYKA JAVA KRÓTKA HISTORIA JĘZYKA JAVA 1991 - narodziny języka java. Pierwsza nazwa Oak (dąb). KRÓTKA HISTORIA JĘZYKA JAVA 1991 - narodziny

Bardziej szczegółowo

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak Java język programowania obiektowego Programowanie w językach wysokiego poziomu mgr inż. Anna Wawszczak 1 Język Java Język Java powstał w roku 1995 w firmie SUN Microsystems Java jest językiem: wysokiego

Bardziej szczegółowo

Dokument Detaliczny Projektu

Dokument Detaliczny Projektu Dokument Detaliczny Projektu Dla Biblioteki miejskiej Wersja 1.0 Streszczenie Niniejszy dokument detaliczny projektu(ddp) przedstawia szczegóły pracy zespołu projektowego, nad stworzeniem aplikacji bazodanowej

Bardziej szczegółowo

REFERAT PRACY DYPLOMOWEJ

REFERAT PRACY DYPLOMOWEJ REFERAT PRACY DYPLOMOWEJ Temat pracy: Projekt i implementacja środowiska do automatyzacji przeprowadzania testów aplikacji internetowych w oparciu o metodykę Behavior Driven Development. Autor: Stepowany

Bardziej szczegółowo

Zadanie polega na stworzeniu bazy danych w pamięci zapewniającej efektywny dostęp do danych baza osób.

Zadanie polega na stworzeniu bazy danych w pamięci zapewniającej efektywny dostęp do danych baza osób. Zadanie: Zadanie polega na stworzeniu bazy danych w pamięci zapewniającej efektywny dostęp do danych baza osób. Na kolejnych zajęciach projekt będzie rozwijana i uzupełniana o kolejne elementy omawiane

Bardziej szczegółowo

Programowanie obiektowe

Programowanie obiektowe Laboratorium z przedmiotu Programowanie obiektowe - zestaw 03 Cel zajęć. Celem zajęć jest zapoznanie z praktycznymi aspektami projektowania oraz implementacji klas abstrakcyjnych i interfejsów. Wprowadzenie

Bardziej szczegółowo

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej. Polimorfizm jest filarem programowania obiektowego, nie tylko jeżeli chodzi o język C++. Daje on programiście dużą elastyczność podczas pisania programu. Polimorfizm jest ściśle związany z metodami wirtualnymi.

Bardziej szczegółowo

PLAN WYNIKOWY PROGRAMOWANIE APLIKACJI INTERNETOWYCH. KL III TI 4 godziny tygodniowo (4x30 tygodni =120 godzin ),

PLAN WYNIKOWY PROGRAMOWANIE APLIKACJI INTERNETOWYCH. KL III TI 4 godziny tygodniowo (4x30 tygodni =120 godzin ), PLAN WYNIKOWY PROGRAMOWANIE APLIKACJI INTERNETOWYCH KL III TI 4 godziny tygodniowo (4x30 tygodni =120 godzin ), Program 351203 Opracowanie: Grzegorz Majda Tematyka zajęć 1. Wprowadzenie do aplikacji internetowych

Bardziej szczegółowo

PYTANIA PRÓBNE DO EGZAMINU NA CERTYFIKAT ZAAWANSOWANY REQB KLUCZ ODPOWIEDZI. Część DODATEK

PYTANIA PRÓBNE DO EGZAMINU NA CERTYFIKAT ZAAWANSOWANY REQB KLUCZ ODPOWIEDZI. Część DODATEK KLUCZ ODPOWIEDZI Część DODATEK 8.1 9.4 PYTANIA PRÓBNE DO EGZAMINU NA CERTYFIKAT ZAAWANSOWANY REQB Na podstawie: Syllabus REQB Certified Professional for Requirements Engineering, Advanced Level, Requirements

Bardziej szczegółowo

Rok akademicki: 2012/2013 Kod: ZIE-1-306-s Punkty ECTS: 3. Poziom studiów: Studia I stopnia Forma i tryb studiów: -

Rok akademicki: 2012/2013 Kod: ZIE-1-306-s Punkty ECTS: 3. Poziom studiów: Studia I stopnia Forma i tryb studiów: - Nazwa modułu: Programowanie obiektowe Rok akademicki: 2012/2013 Kod: ZIE-1-306-s Punkty ECTS: 3 Wydział: Zarządzania Kierunek: Informatyka i Ekonometria Specjalność: - Poziom studiów: Studia I stopnia

Bardziej szczegółowo

Web frameworks do budowy aplikacji zgodnych z J2EE

Web frameworks do budowy aplikacji zgodnych z J2EE Web frameworks do budowy aplikacji zgodnych z J2EE Jacek Panachida promotor: dr Dariusz Król Przypomnienie Celem pracy jest porównanie wybranych szkieletów programistycznych o otwartym kodzie źródłowym

Bardziej szczegółowo

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6 JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM Wykład 6 1 SPECYFIKATOR static Specyfikator static: Specyfikator ten powoduje, że zmienna lokalna definiowana w obrębie danej funkcji nie jest niszczona

Bardziej szczegółowo

Architektury Usług Internetowych. Laboratorium 2. Usługi sieciowe

Architektury Usług Internetowych. Laboratorium 2. Usługi sieciowe Architektury Usług Internetowych Laboratorium 2. Usługi sieciowe Wstęp Celem laboratorium jest zapoznanie się z modelem usług sieciowych na przykładzie prostego serwera Apache Axis2. Apache Axis2 Apache

Bardziej szczegółowo

PRZEWODNIK PO PRZEDMIOCIE

PRZEWODNIK PO PRZEDMIOCIE Nazwa przedmiotu: Kierunek: Informatyka Rodzaj przedmiotu: moduł specjalności obowiązkowy: Inżynieria oprogramowania, Programowanie aplikacji internetowych Rodzaj zajęć: wykład, laboratorium I KARTA PRZEDMIOTU

Bardziej szczegółowo

Analiza i projektowanie aplikacji Java

Analiza i projektowanie aplikacji Java Analiza i projektowanie aplikacji Java Modele analityczne a projektowe Modele analityczne (konceptualne) pokazują dziedzinę problemu. Modele projektowe (fizyczne) pokazują system informatyczny. Utrzymanie

Bardziej szczegółowo

Oceny z prezentacji INKU011S. Zofia Kruczkiewicz

Oceny z prezentacji INKU011S. Zofia Kruczkiewicz Oceny z prezentacji INKU011S Zofia Kruczkiewicz Data Student Oceny Uwagi 22.10.2017 231085 3.0 Przedstaw idealne środowisko do stosowania inżynierii oprogramowania- opisz elementy tego środowiska (sprzęt

Bardziej szczegółowo

PRACA DYPLOMOWA INŻYNIERSKA. Mobilny system wspomagający pracę. terminala kontenerowego

PRACA DYPLOMOWA INŻYNIERSKA. Mobilny system wspomagający pracę. terminala kontenerowego PAŃSTWOWA WYŻSZA SZKOŁA ZAWODOWA W ELBLĄGU INSTYTUT INFORMATYKI STOSOWANEJ PRACA DYPLOMOWA INŻYNIERSKA Mobilny system wspomagający pracę terminala kontenerowego autor: Bartłomiej Urbanowicz opiekun pracy:

Bardziej szczegółowo

Użycie Visual Basic for Applications ("VBA")

Użycie Visual Basic for Applications (VBA) Użycie Visual Basic for Applications ("VBA") Przegląd SEE z modułem VBA Developer SEE używa języka programowania Visual Basic for Applications (VBA) pozwalającego tworzyć krótkie programy zwane "makrami".

Bardziej szczegółowo

użytkownika 1 Jak wybrać temat pracy 2 Spis treści 3 Część pierwsza problematyka 4 Część druga stosowane metody 5 Część trzecia propozycja rozwiązania

użytkownika 1 Jak wybrać temat pracy 2 Spis treści 3 Część pierwsza problematyka 4 Część druga stosowane metody 5 Część trzecia propozycja rozwiązania 1 Jak wybrać temat pracy 2 Spis treści 3 Część pierwsza problematyka 4 Część druga stosowane metody 5 Część trzecia propozycja rozwiązania 6 Część czwarta dokumentacja techniczna i dokumentacja użytkownika

Bardziej szczegółowo

Technologie cyfrowe. Artur Kalinowski. Zakład Cząstek i Oddziaływań Fundamentalnych Pasteura 5, pokój 4.15

Technologie cyfrowe. Artur Kalinowski. Zakład Cząstek i Oddziaływań Fundamentalnych Pasteura 5, pokój 4.15 Technologie cyfrowe Artur Kalinowski Zakład Cząstek i Oddziaływań Fundamentalnych Pasteura 5, pokój 4.15 Artur.Kalinowski@fuw.edu.pl Semestr letni 2014/2015 Zadanie algorytmiczne: wyszukiwanie dane wejściowe:

Bardziej szczegółowo

Dziedziczenie. Streszczenie Celem wykładu jest omówienie tematyki dziedziczenia klas. Czas wykładu 45 minut.

Dziedziczenie. Streszczenie Celem wykładu jest omówienie tematyki dziedziczenia klas. Czas wykładu 45 minut. Dziedziczenie Streszczenie Celem wykładu jest omówienie tematyki dziedziczenia klas. Czas wykładu 45 minut. Rozpatrzmy przykład przedstawiający klasy Student oraz Pracownik: class Student class Pracownik

Bardziej szczegółowo

!!!!!!!!!!! PORTFOLIO: Analiza zachowań użytkowników serwisów internetowych. Autorzy: Marek Zachara

!!!!!!!!!!! PORTFOLIO: Analiza zachowań użytkowników serwisów internetowych. Autorzy: Marek Zachara PORTFOLIO: Analiza zachowań użytkowników serwisów internetowych Autorzy: Marek Zachara Opis merytoryczny Cel naukowy (jaki problem wnioskodawca podejmuje się rozwiązać, co jest jego istotą, co uzasadnia

Bardziej szczegółowo

Tworzenie aplikacji Web Alicja Zwiewka. Page 1

Tworzenie aplikacji Web Alicja Zwiewka. Page 1 Tworzenie aplikacji Web Alicja Zwiewka Page 1 Co to są web-aplikacje? Aplikacja internetowa (ang. web application) program komputerowy, który pracuje na serwerze i komunikuje się poprzez sieć komputerową

Bardziej szczegółowo

Smarty PHP. Leksykon kieszonkowy

Smarty PHP. Leksykon kieszonkowy IDZ DO PRZYK ADOWY ROZDZIA SPIS TREœCI KATALOG KSI EK KATALOG ONLINE ZAMÓW DRUKOWANY KATALOG Smarty PHP. Leksykon kieszonkowy Autor: Daniel Bargie³ ISBN: 83-246-0676-9 Format: B6, stron: 112 TWÓJ KOSZYK

Bardziej szczegółowo

Modelowanie i Programowanie Obiektowe

Modelowanie i Programowanie Obiektowe Modelowanie i Programowanie Obiektowe Wykład I: Wstęp 20 październik 2012 Programowanie obiektowe Metodyka wytwarzania oprogramowania Metodyka Metodyka ustandaryzowane dla wybranego obszaru podejście do

Bardziej szczegółowo

Platformy programistyczne:.net i Java L ABORATORIUM 7,8: HACKATHON - JTTT

Platformy programistyczne:.net i Java L ABORATORIUM 7,8: HACKATHON - JTTT Platformy programistyczne:.net i Java L ABORATORIUM 7,8: HACKATHON - JTTT O co chodzi? - Przypomnienie Hackathon - http://en.wikipedia.org/wiki/hackathon A hackathon is an event in which computer programmers

Bardziej szczegółowo

Procesowa specyfikacja systemów IT

Procesowa specyfikacja systemów IT Procesowa specyfikacja systemów IT BOC Group BOC Information Technologies Consulting Sp. z o.o. e-mail: boc@boc-pl.com Tel.: (+48 22) 628 00 15, 696 69 26 Fax: (+48 22) 621 66 88 BOC Management Office

Bardziej szczegółowo

INŻYNIERIA OPROGRAMOWANIA

INŻYNIERIA OPROGRAMOWANIA INSTYTUT INFORMATYKI STOSOWANEJ 2013 INŻYNIERIA OPROGRAMOWANIA Inżynieria Oprogramowania Proces ukierunkowany na wytworzenie oprogramowania Jak? Kto? Kiedy? Co? W jaki sposób? Metodyka Zespół Narzędzia

Bardziej szczegółowo

MATERIAŁY DO ZAJĘĆ I. Podstawowe pojęcia. Algorytm. Spis treści Przepis

MATERIAŁY DO ZAJĘĆ I. Podstawowe pojęcia. Algorytm. Spis treści Przepis MATERIAŁY DO ZAJĘĆ I Podstawowe pojęcia Spis treści I. Algorytm II. Schemat blokowy III. Struktury danych IV. Program komputerowy V. Opis środowiska programistycznego VI. Obsługa wejścia wyjścia VII. Przykład

Bardziej szczegółowo

Automatyzacja testowania oprogramowania. Automatyzacja testowania oprogramowania 1/36

Automatyzacja testowania oprogramowania. Automatyzacja testowania oprogramowania 1/36 Automatyzacja testowania oprogramowania Automatyzacja testowania oprogramowania 1/36 Automatyzacja testowania oprogramowania 2/36 Potrzeba szybkich rozwiązań Testowanie oprogramowania powinno być: efektywne

Bardziej szczegółowo

PROLOG WSTĘP DO INFORMATYKI. Akademia Górniczo-Hutnicza. Wydział Elektrotechniki, Automatyki, Informatyki i Inżynierii Biomedycznej.

PROLOG WSTĘP DO INFORMATYKI. Akademia Górniczo-Hutnicza. Wydział Elektrotechniki, Automatyki, Informatyki i Inżynierii Biomedycznej. Akademia Górniczo-Hutnicza Wydział Elektrotechniki, Automatyki, Informatyki i Inżynierii Biomedycznej WSTĘP DO INFORMATYKI Adrian Horzyk PROLOG www.agh.edu.pl Pewnego dnia przyszedł na świat komputer Komputery

Bardziej szczegółowo