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, ki oraz kj, 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.