LABORATORIUM NR 6 Kryteria przepływu danych
Transkrypt
LABORATORIUM NR 6 Kryteria przepływu danych
TESTOWANIE I JAKOŚĆ OPROGRAMOWANIA – LABORATORIUM NR 6 Kryteria przepływu danych Kryteria przepływu danych oparte są na założeniu, że aby przetestowad dobrze program, należy skupid się na przepływie danych w tym programie. Powinniśmy upewnid się, że wartości zmiennych tworzone w jednym miejscu programu są tworzone i używane w sposób poprawny. Definicja jest miejscem, gdzie wartośd zmiennej jest zachowywana w pamięci (poprzez instrukcję przypisania, wejście danych itp.). Użycie jest miejscem, w którym następuje dostęp do wartości zmiennej. Kryteria testowania przepływu danych wykorzystują fakt, że wartości są przenoszone od definicji do użyd. Nazywamy je du-parami. Po pierwsze, musimy zintegrowad przepływ danych z naszym modelem grafowym. Niech V będzie zbiorem zmiennym związanych z artefaktem oprogramowania, który jest modelowany danym grafem. Każdy wierzchołek n (krawędź e) może definiowad jakiś podzbiór V; podzbiór ten oznaczamy przez def(n) lub def(e). Chociaż grafy tworzone na podstawie programów nie mogą mied definicji na krawędziach, to w innych artefaktach oprogramowania już jest to dozwolone (np. w maszynach skooczenie stanowych). Każdy wierzchołek n i krawędź e może także używad jakiegoś podzbioru V; podzbiór ten oznaczamy przez use(n) lub use(e). Na Rysunku 1. mamy przykład grafu z annotacją definicji i użyd. Zakładamy, że wszystkie zmienne używane są na krawędziach, więc zarówno a jak i b są używane na trzech krawędziach, (n0,n1), (n0,n3) i (n0,n2). Rysunek 1 Ważnym zagadnieniem w analizie kryteriów przepływu danych jest kwestia osiągalności: definicja zmiennej może, ale nie musi osiągnąd danego użycia. Najbardziej oczywistym powodem sytuacji, w której zmienna v w lokacji li (lokacja to wierzchołek lub krawędź) nie osiągnie użycia w lokacji lj jest fakt, iż nie ma ścieżki prowadzącej od li do lj. Bardziej subtelnym powodem może byd to, że wartośd zmiennej może się zmienid w wyniku dojścia do innego miejsca definicji tej samej zmiennej, przed osiągnięciem miejsca użycia. Powiemy, że ścieżka prowadząca od li do lj jest def-wolna ze względu na zmienną v, jeśli dla każdego wierzchołka nk i każdej krawędzi ek na ścieżce, ki oraz kj, v nie występuje w def(nk) ani w def(ek). Jeśli def-wolna ścieżka ze względu na v prowadzi z li do lj, powiemy, że definicja v w li osiąga użycie w lj. W dalszej części będziemy zakładad, że początek i koniec du-ścieżki znajduje się w wierzchołkach. Niech du(ni, v) będzie zbiorem wszystkich du-ścieżek ze względu na zmienną v, rozpoczynających się w wierzchołku ni. Zbiór ten nazwiemy zbiorem def-ścieżek. Ze względu na dużą liczbę wierzchołków oraz zmiennych w programie, liczba oraz moce zbiorów du mogą byd duże. Kryterium wymagające, by przynajmniej jedna ścieżka z każdego takiego zbioru była pokryta jest mimo wszystko kryterium słabym. Co ciekawe, okazuje się, że podobne podejście, ale z grupowaniem nie definicji, a użyd, nie jest zbyt pomocne. Innym możliwym grupowaniem du-par jest grupowanie ze względu na pary definicji i użycia. Takie zbiory nazwiemy zbiorami def-par. Idea takiego grupowania pochodzi stąd, że zasadą testowania przepływu danych jest przepływ od definicji do użyd. Formalnie, zbiór def-par du(ni,nj,v) jest zbiorem du-ścieżek ze względu na zmienną v, które rozpoczynają się w wierzchołku ni, a kooczą w nj. Nieformalnie, jest to zbiór wszystkich możliwych (prostych) ścieżek od zadanej definicji do zadanego użycia. Kryterium oparte o ten podział będzie wymagało, by przynajmniej jedna ze ścieżek z każdego zbioru była pokryta przez jakąś ścieżkę z TR. Ponieważ zwykle jedna definicja posiada wiele użyd, zbiorów defpar będzie na ogół bardzo dużo. Rozszerzamy teraz definicję przechodzenia, by móc ją zastosowad w odniesieniu do du-ścieżek. Powiemy, że ścieżka testowa p du-przechodzi przez podścieżkę d ze względu na v, jeśli p przechodzi przez podścieżkę d i fragment p, któremu odpowiada d, jest def-wolny ze względu na v. W definicji można uwzględnid użycie def-wolnych ścieżek pobocznych. Możemy teraz formalnie zdefiniowad trzy podstawowe kryteria pokrycia. Kryterium 1 (pokrycie wszystkich definicji) – ang. All-Defs Coverage, ADC. Dla każdego zbioru def-ścieżek S=du(n,v), TR zawiera co najmniej jedną ścieżkę d ze zbioru S. Kryterium 2 (pokrycie wszystkich użyć) – ang. All-Uses Coverage, AUC. Dla każdego zbioru def-par S=du(ni, nj,v), TR zawiera co najmniej jedną ścieżkę d ze zbioru S. Kryterium 3 (pokrycie wszystkich du-ścieżek) – ang. All-du-Paths Coverage, ADUPC. Dla każdego zbioru def-par S=du(ni,nj,v), TR zawiera każdą ścieżkę d ze zbioru S. Zauważmy, że kryteria nie precyzują postaci ścieżek w TR. Zaleca się stosowanie ścieżek o największej efektywności (best effort touring, patrz Laboratorium 3). Zadanie 1. Dane są dwa grafy, miejsca definicji i użyd, oraz zestawy ścieżek testowych. Dla każdego z nich odpowiedz na poniższe pytania. Graf I. N={0,1,2,3,4,5,6,7}, N0={0}, Nf={7}, E={(0,1),(1,2),(1,7),(2,3),(2,4),(3,2),(4,5),(4,6),(5,6),(6,1)}, def(0)=def(3)=use(5)=use(7)={x} t1=[0,1,7], t2=[0,1,2,4,6,1,7], t3=[0,1,2,4,5,6,1,7], t4=[0,1,2,3,2,4,6,1,7], t5=[0,1,2,3,2,3,2,4,5,6,1,7], t6=[0,1,2,3,2,4,6,1,2,4,5,6,1,7] Graf II. N=[1,2,3,4,5,6}, N0={1}, Nf={6}, E={(1,2),(2,3),(2,6),(3,4),(3,5),(4,5),(5,2)}, def(x)={1,3}, use(x)={3,6} – zakładamy, że użycie x w wierzchołku 3 poprzedza definicję x w tym wierzchołku t1=[1,2,6], t2=[1,2,3,4,5,2,3,5,2,6], t3=[1,2,3,5,2,3,4,5,2,6], t4=[1,2,3,5,2,6] a) narysuj graf b) wypisz wszystkie du-ścieżki ze względu na x. Uwaga: uwzględnij wszystkie du-ścieżki, nawet te, które są podścieżkami innych du-ścieżek. c) dla każdej ścieżki testowej znajdź te spośród du-ścieżek, które są pokryte przez tą ścieżkę testową. Rozważ użycie ścieżek prostych, a także ścieżek pobocznych. d) znajdź minimalny zbiór testowy, który spełnia pokrycie wszystkich definicji (ADC) ze względu na x, wykorzystując tylko ścieżki proste. Użyj zadanych ścieżek testowych. e) wypisz minimalny zbiór testowy, który spełnia pokrycie wszystkich użyd (AUC) ze względu na x, wykorzystując tylko ścieżki proste. Użyj zadanych ścieżek testowych. f) to samo co w e) tylko dla pokrycia wszystkich du-ścieżek. Strukturalne pokrycie grafowe dla kodu źródłowego Rysunek 2 Kryteria pokrycia grafowego najczęściej używane są dla kodu źródłowego. Aby zastosowad dane kryterium, należy najpierw zdefiniowad graf. Dla kodu źródłowego zwykle jest to tzw. graf przepływu sterowania (ang. control flow graph, CFG). Krawędzie utożsamiane są z rozgałęzieniami w programie, a wierzchołki z sekwencjami instrukcji. Formalnie, blok bazowy to maksymalny ciąg instrukcji programu taki, że jeśli jedna z instrukcji bloku jest wykonana, to wszystkie sekwencje w bloku muszą byd wykonane. Blok bazowy ma jeden punkt wejścia i jeden punkt wyjścia. Rysunek 2. przedstawia CFG dla prostej instrukcji ifelse. Blokami bazowymi są wierzchołki n1 i n2. Wierzchołek n0 ma dwa wyjścia i nazywany jest wierzchołkiem decyzyjnym. Wierzchołek n3 ma dwa wejścia. Takie wierzchołki nazywamy łącznikami. Na Rysunku 3. przedstawiony jest graf przepływu sterowania dla instrukcji if bez else. CFG ma tylko 3 wierzchołki. Zauważmy, że test spełniający x<y przechodzi przez wszystkie wierzchołki tego grafu. Reprezentacja pętli w CFG jest nieco trickowa, gdyż wymusza tworzenie wierzchołków, które nie pochodzą bezpośrednio od instrukcji programu. Najprostszym Rysunek 4 Rysunek 3 przykładem jest pętla while, której CFG przedstawiony jest na Rysunku 3. Zauważmy, że konieczne jest wprowadzenie wierzchołka n1, który jest „dummy node”, gdyż nie reprezentuje żadnej instrukcji, ale pozwala na stworzenie krawędzi wychodzącej z n2 na początek pętli. Zadanie 2. Narysuj CFG dla kodu: for (x=0; x<y; x++) { y=f(x,y); } Zdefiniowane powyżej kryteria pokrycia mogą byd zastosowane do CFG. Pokrycie wierzchołkowe często nazywa się pokryciem kodu lub pokryciem bloków bazowych. Pokrycie krawędziowe określa się mianem pokrycia rozgałęzieo. Pokrycie grafowe przepływu danych dla kodu źródłowego Definicja to miejsce w programie, w którym wartośd zmiennej jest zachowywana w pamięci (inicjalizacja, przypisanie, pobranie danych z wejścia itp.). Użycie to miejsce, w którym następuje dostęp do wartości danej zmiennej. Definicja dla zmiennej x może nastąpid w poniższych sytuacjach: x pojawia się po lewej stronie instrukcji przypisania x jest parametrem aktualnym w miejscu wywołania i jej wartośd zmienia się podczas działania metody/funkcji x jest parametrem formalnym metody (definicja następuje implicite, gdy następuje wywołanie metody) x jest wejściem programu Jeśli zmienna ma kilka definicji w jednym bloku, w analizie bierzemy pod uwagę tylko ostatnią z nich. Użycie zmiennej x może nastąpid w następujących sytuacjach: x pojawia się po prawej stronie instrukcji przypisania x pojawia się w teście warunkowym (taki test zawsze związany jest z co najmniej dwiema krawędziami) x jest aktualnym parametrem metody x jest wyjściem programu x jest wyjściem metody w instrukcji zwracania wartości (return) lub zwracane jest jako parametr Nie wszystkie użycia są odpowiednie do analizy przepływu danych: Zadanie 3. Rozważmy kod: y=z; x=y+2; Użycie zmiennej y w drugiej instrukcji nazywane jest użyciem lokalnym. Dlaczego? Z których miejsc definicji użycie to może byd osiągnięte? Jaki jest tego powód? Użycie zmiennej z nazywane jest globalnym. Dlaczego? Gdzie musi rozpoczynad się definicja zmiennej z, która jest użyta w tym bloku bazowym? Analiza przepływu danych zajmuje się tylko użyciami globalnymi. Zadanie 4. Dany jest kod programu TestPat (patrz plik testPat.java). Zapoznaj się z komentarzami w kodzie, aby dokładnie zrozumied działanie programu. a) narysuj CFG dla tego programu (dla funkcji pat). b) porównaj twój CFG z CFG wygenerowanym przez program automatyzujący tę czynnośd, np. program Visus lub inny znaleziony przez Ciebie w Internecie. c) przeformułuj opisy wierzchołków i krawędzi tego grafu tak, aby wskazywały explicite definicje i użycia poszczególnych zmiennych. Dane przedstaw w tabelach: wierzchołek krawędź zmienne definiowane zmienne używane zmienne używane d) znajdź (ręcznie) wszystkie du-ścieżki dla zmiennej iSub dla wszystkich możliwych du-par (wierzchołek, iSub). Wynik zapisz w tabeli (poniżej: przykład dla zmiennej trnIndex; w kolumnie prefiks należy wpisad „tak”, jeśli ścieżka jest podścieżką innej ścieżki występującej w kolumnie „du-ścieżki”): zmienna trnIndex zbiór du-ścieżek du(2,rtnIndex) du(5,rtnIndex) du(8,rtnIndex) du-ścieżki [2,3,11] [5,6,10,3,11] [8,10,3,11] prefiks? uzupełnienie całej tabeli (dla wszystkich zmiennych) jest pracochłonne i ręczne wykonywanie tego zadania może prowadzid do wielu błędów, dlatego proces ten dobrze jest poddad automatyzacji. e) użyj automatycznego narzędzia (gotowego lub napisanego przez Ciebie) do znalezienia wszystkich du-ścieżek dla wszystkich zmiennych występujących w programie. Przy każdej z nich dodaj informację, jakiej zmiennej dotyczy. Wyeliminuj ścieżki, które są prefiksami innych ścieżek. Tak opracowany zbiór zapisz w postaci pliku tekstowego o nazwie du_paths.txt, w którym każda linia zawiera numer kolejny, nazwę zmiennej oraz pewną du-ścieżkę. Zadanie 5. Dokonaj instrumentacji kodu TestPat tak, by był on w stanie wypisywad ścieżki występujące przy zadanym wykonaniu programu dla pewnych parametrów wejściowych. Innymi słowy, program ma wypisywad na wyjście jedynie ciąg etykiet wierzchołków, przez które przechodzi sterowanie w danym wykonaniu programu. Zadanie 6. Spróbuj znaleźd zbiór T przypadków testowych (w postaci trójek (subject, pattern, expected output)), który spełni pokrycie wszystkich du-ścieżek (Kryterium 3.) ze zbioru tekstowego stworzonego w poprzednim zadaniu. To zadanie może byd trudne, dlatego zautomatyzuj proces w następujący sposób: a) napisz program (skrypt), który na wejściu otrzyma plik tekstowy testcases.txt. Każda linijka ma reprezentowad pojedynczy przypadek testowy, czyli musi byd trójką (subject, pattern, expected output). b) program (skrypt), po uruchomieniu, ma wykonad program TestPat na wszystkich przypadkach testowych z pliku testcases.txt podanego jako wejście. c) dla każdego uruchomienia TestPat z pojedynczym testem, program ma sprawdzid, jaką ścieżką przeszło sterowanie programu (to będzie możliwe, jeśli dokonałeś uprzednio instrumentacji kodu TestPat z Zadania 5. Możesz użyd przekierowania strumienia wyjściowego ze zinstrumentowanego kodu TestPat na wejście odpowiedniej procedury Twojego programu (skryptu)). d) program (skrypt) ma mied zaszyty w sobie zbiór przypadków testowych znalezionych w zadaniu 4. pkt. e) i zapisanych w pliku du_paths.txt. Po wykonaniu każdego przypadku testowego Twój program ma sprawdzad, które ścieżki z du_paths.txt zostały pokryte. (Uwaga – zanim wykorzystasz plik du_paths.txt usuo z niego ścieżki będące prefixami innych ścieżek (czyli wiersze ze słowem YES w ostatniej kolumnie)). Jeśli jakaś ścieżka została pokryta przez pewną ścieżkę testową z testcases.txt, nie uwzględniaj już pokrycia tej ścieżki przy kolejnych ścieżkach testowych. e) na koocu program powinien przedstawid raport w następującej postaci: przypadek testowy ścieżka testowa pokryła ścieżki (a, bc, -1) [1,2,3,11] 9 28 31 [numery kolejne z odpowiednich wierszy pliku du_paths.txt] .... .... Podsumowanie: Liczba przypadków testowych: xxx Liczba pokrytych du-ścieżek: yyy Liczba wszystkich du-ścieżek: zzz Stopieo pokrycia: yyy/zzz % Uwaga – jedna du-ścieżka jest nieosiągalna, więc stopieo pokrycia nigdy nie osiągnie 100%, chyba, że wyeliminujesz tę ścieżkę ze zbioru du_paths.txt. Spróbuj znaleźd tę nieosiągalną ścieżkę. f) poeksperymentuj z zestawem testów starając się uzyskad jak najlepszy stopieo pokrycia przy użyciu jak najmniejszej liczby testów. g) przedyskutuj w grupie możliwośd napisania programu do automatycznej generacji testów wraz z poprawnymi odpowiedziami h) na koniec prowadzący udostępni Ci plik all_du_paths.txt. Wykonaj swój skrypt na tym pliku. Wszystkie du-ścieżki powinny zostad pokryte przypadkami testowymi z pliku all_du_paths.txt. Porównaj wyniki ze swoim zbiorem przypadków testowych.