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ż

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.