Testy losowe
Testowanie losowe to czarnoskrzynkowa technika testowania oprogramowania, w której programy są testowane poprzez generowanie losowych, niezależnych danych wejściowych. Wyniki danych wyjściowych są porównywane ze specyfikacjami oprogramowania, aby zweryfikować, czy dane wyjściowe testu są pozytywne, czy negatywne. W przypadku braku specyfikacji używane są wyjątki języka, co oznacza, że jeśli wyjątek pojawi się podczas wykonywania testu, oznacza to, że w programie jest błąd, jest to również używane jako sposób na uniknięcie stronniczego testowania.
Historia losowych testów
Losowe testy sprzętu zostały po raz pierwszy zbadane przez Melvina Breuera w 1971 r., A wstępną próbę oceny jego skuteczności wykonali Pratima i Vishwani Agrawal w 1975 r.
W oprogramowaniu Duran i Ntafos zbadali losowe testy w 1984 roku.
Wykorzystanie testowania hipotez jako podstawy teoretycznej do testowania losowego zostało opisane przez Howdena w Functional Testing and Analysis . Książka zawierała również opracowanie prostej formuły szacowania liczby testów n , które są potrzebne, aby mieć pewność co najmniej 1-1/ n przy wskaźniku awaryjności nie większym niż 1/n. Formuła to dolna granica n log n , która wskazuje dużą liczbę bezawaryjnych testów potrzebnych do uzyskania nawet skromnej pewności co do skromnej granicy wskaźnika awaryjności.
Przegląd
Rozważ następującą funkcję C++:
0
int moje Abs ( int x ) { if ( x > ) { return x ; } else { zwróćx ; _ // błąd: powinno być '-x' } }
Teraz losowymi testami dla tej funkcji mogą być {123, 36, -35, 48, 0}. Tylko wartość „-35” powoduje błąd. Jeśli nie ma implementacji referencyjnej do sprawdzenia wyniku, błąd nadal może pozostać niezauważony. jednak asercję , aby sprawdzić wyniki, na przykład:
0
0
void testAbs ( int n ) { for ( int i = ; ja < n ; i ++ ) { int x = getRandomInput (); int wynik = myAbs ( x ); potwierdzić ( wynik >= ); } }
Implementacja referencyjna jest czasami dostępna, np. przy implementacji prostego algorytmu w znacznie bardziej złożony sposób dla lepszej wydajności. Na przykład, aby przetestować implementację algorytmu Schönhage – Strassen , można zastosować standardową operację „*” na liczbach całkowitych:
0
int getRandomInput () { // … } void testFastMultiplication ( int n ) { for ( int i = ; i < n ; i ++ ) { long x = getRandomInput (); długie y = getRandomInput (); długi wynik = szybkie mnożenie ( x , y
); potwierdzić ( x * y == wynik ); } }
Chociaż ten przykład ogranicza się do prostych typów (dla których można użyć prostego generatora losowego), narzędzia ukierunkowane na języki zorientowane obiektowo zazwyczaj eksplorują program w celu przetestowania i znalezienia generatorów (konstruktorów lub metod zwracających obiekty tego typu) i wywoływania ich przy użyciu losowych dane wejściowe (same generowane w ten sam sposób lub generowane przy użyciu generatora pseudolosowego, jeśli to możliwe). Takie podejście następnie utrzymuje pulę losowo generowanych obiektów i wykorzystuje prawdopodobieństwo ponownego wykorzystania wygenerowanego obiektu lub stworzenia nowego.
O przypadkowości
Zgodnie z przełomowym artykułem D. Hamleta na temat losowych testów
[…] techniczne, matematyczne znaczenie „testowania losowego” odnosi się do wyraźnego braku „systemu” w wyborze danych testowych, tak że nie ma korelacji między różnymi testami.
Mocne i słabe strony
Losowe testy są chwalone za następujące mocne strony:
- Jest tani w użyciu: nie musi być mądry w testowanym programie.
- Nie ma stronniczości: w przeciwieństwie do testów ręcznych, nie pomija błędów, ponieważ istnieje niewłaściwa wiara w jakiś kod.
- Kandydatów na błędy można szybko znaleźć: przeprowadzenie sesji testowej zajmuje zazwyczaj kilka minut.
- Jeśli oprogramowanie jest poprawnie określone: znajduje prawdziwe błędy.
Opisano następujące słabości:
- Wyszukuje tylko podstawowe błędy (np. dereferencja pustego wskaźnika ).
- Jest tylko tak precyzyjny, jak specyfikacja, a specyfikacje są zazwyczaj nieprecyzyjne.
- Słabo wypada w porównaniu z innymi technikami wyszukiwania błędów (np. statyczna analiza programu ).
- Jeśli różne dane wejściowe są wybierane losowo w każdym przebiegu testu, może to powodować problemy z ciągłą integracją , ponieważ te same testy losowo kończą się powodzeniem lub niepowodzeniem.
- Niektórzy twierdzą, że lepiej byłoby starannie objąć wszystkie istotne przypadki ręcznie skonstruowanymi testami w sposób białoskrzynkowy, niż polegać na przypadkowości.
- Może to wymagać bardzo dużej liczby testów dla skromnego poziomu ufności w skromnych wskaźnikach awaryjności. Na przykład będzie wymagać 459 bezawaryjnych testów, aby mieć co najmniej 99% pewności, że prawdopodobieństwo niepowodzenia jest mniejsze niż 1/100.
Rodzaje testów losowych
W odniesieniu do wkładu
- Generowanie losowej sekwencji wejściowej (tj. sekwencji wywołań metod)
- Losowa sekwencja wprowadzania danych (czasami nazywana testowaniem stochastycznym) - np. losowa sekwencja wywołań metod
- Losowy wybór danych z istniejącej bazy danych
Z przewodnikiem vs. niekierowany
- nieukierunkowane losowe generowanie testów - bez heurystyki kierującej jego wyszukiwaniem
- ukierunkowane generowanie losowych testów - np. „generowanie losowych testów kierowanych przez sprzężenie zwrotne” i „adaptacyjne testowanie losowe”
Implementacje
Niektóre narzędzia realizujące testy losowe:
- QuickCheck - znane narzędzie testowe, pierwotnie opracowane dla Haskella , ale przeniesione do wielu innych języków, które generuje losowe sekwencje wywołań API na podstawie modelu i weryfikuje właściwości systemu, które powinny być prawdziwe po każdym uruchomieniu.
- Randoop - generuje sekwencje metod i wywołań konstruktorów dla testowanych klas i tworzy z nich testy JUnit
- Symulant – narzędzie Clojure , które przeprowadza symulacje różnych agentów (np. użytkowników o różnych profilach behawioralnych) w oparciu o statystyczny model ich zachowania, rejestrując wszystkie działania i wyniki w bazie danych w celu późniejszej eksploracji i weryfikacji
- AutoTest – narzędzie zintegrowane z EiffelStudio do automatycznego testowania kodu Eiffla z kontraktami opartymi na tytułowym prototypie badawczym.·
- York Extensible Testing Infrastructure (YETI) - niezależne od języka narzędzie, które jest ukierunkowane na różne języki programowania (Java, JML, CoFoJa, .NET, C, Kermeta).
- GramTest - narzędzie do losowego testowania oparte na gramatyce, napisane w Javie, używa notacji BNF do określenia gramatyk wejściowych.
Krytyka
Testowanie losowe ma w praktyce tylko specjalistyczną niszę, głównie dlatego, że skuteczna wyrocznia jest rzadko dostępna, ale także z powodu trudności z profilem operacyjnym i generowaniem pseudolosowych wartości wejściowych.
Wyrocznia testowa jest narzędziem służącym do weryfikacji, czy wyniki są zgodne ze specyfikacją programu, czy też nie. Profil działania to wiedza o wzorcach użytkowania programu, a więc które części są ważniejsze.
Dla języków programowania i platform posiadających kontrakty (np. Eiffel. .NET lub różne rozszerzenia Javy jak JML, CoFoJa...) kontrakty działają jak naturalne wyrocznie i podejście to zostało z powodzeniem zastosowane. W szczególności losowe testy wykrywają więcej błędów niż kontrole ręczne lub raporty użytkowników (choć różne).
Zobacz też
- Testowanie rozmyte - rodzaj losowego testowania, które dostarcza błędne dane wejściowe do testowanego programu
- Leniwe systematyczne testowanie jednostkowe # Testowanie systematyczne - systematyczny sposób eksploracji „wszystkich” wywołań metod, zaimplementowany np. przez Java Path Finder NASA (który łączy testowanie ze sprawdzaniem modelu poprzez ograniczenie przestrzeni stanów do rozsądnego rozmiaru za pomocą różnych środków)
- Ograniczone losowe generowanie w SystemVerilog
- Obudowa narożna
- Obudowa krawędzi
- Testy konkolkowe
Linki zewnętrzne
- Losowe testy przeprowadzone przez Andreę Arcuri.
- Losowe testy przeprowadzone przez Richarda Hamleta, emerytowanego profesora na Uniwersytecie Stanowym w Portland; cenny wykaz zasobów na końcu artykułu
- Random Testing wiki w Cunningham & Cunningham, Inc.