Czy są jakieś sformalizowane / matematyczne teorie testowania oprogramowania?

12

Wydaje się, że „teoria testowania oprogramowania” daje teorie w miękkim tego słowa znaczeniu; Nie byłem w stanie znaleźć niczego, co klasyfikowałoby jako teorię w sensie matematycznym, teoretycznym informacji lub w jakiejś innej dziedzinie naukowej.

To, czego szukam, to coś, co formalizuje, czym jest testowanie, użyte pojęcia, czym jest przypadek testowy, wykonalność testowania czegoś, praktyczność testowania czegoś, zakres, w jakim coś powinno być testowane, formalna definicja / wyjaśnienie zasięg kodu itp.

AKTUALIZACJA: Ponadto, nie jestem pewien, intuicyjnie, co do związku między formalną weryfikacją a tym, o co prosiłem, ale najwyraźniej istnieje jakiś związek.

Erik Kaplun
źródło
1
Testowanie oprogramowania jest bardzo cenne (np. Testowanie jednostkowe w praktyce), ale jego teoria zawsze będzie miała pewne dziury. Rozważmy ten klasyczny przykład: double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }którego nauczyłem się od mojego nauczyciela matematyki . Ten kod ma dokładnie jedną dziurę , której nie można wykryć automatycznie na podstawie samych testów czarnej skrzynki. W matematyce nie ma takiej dziury. W rachunku różniczkowym możesz zamknąć otwór, jeśli jednostronne limity są równe.
rwong
4
To może być część tego, czego szukasz - en.wikipedia.org/wiki/Formal_verification
enderland
1
Zastrzegam sobie sugestię @ enderland. Nie ma znaczenia, jak rygorystyczne jest twoje podejście do testowania; niektóre błędy nadal będą się przesuwać przez pęknięcia, a gdy pokryjesz więcej kodu swoimi testami, koszt znalezienia nowych błędów wzrasta. Prawdopodobnie dlatego nikt nie przeszedł trudu sformalizowania pojęcia testowania - podejście „heurystyczne” działa równie dobrze przy mniejszym szkoleniu.
Doval
Od tego czasu zapoznałem się z krajem formalnej weryfikacji za pośrednictwem typów zależnych i mogę całkowicie zgodzić się z @Doval i krajem krańca.
Erik Kaplun,
1
@rwong Przypuszczam, że odwołujesz się do możliwości, że zarówno licznik, jak i mianownik będą równe zero. Częściowo problem wynika z niewłaściwego zaprojektowania tej funkcji. Mówiąc o matematycznej weryfikacji formalnej, musisz komponować swoje funkcje nie arbitralnie, ale zgodnie z formalnymi regułami, opartymi na prawidłowych typach danych. W tym przykładzie musiałbyś użyć funkcji dzielenia (a,b)=>a/b, która musi zostać powiększona o wartość przepełnienia, aby mogła być poprawnie skomponowana.
Dmitri Zaitsev,

Odpowiedzi:

5

Nie mogę wskazać na dobry zasób online (artykuły w angielskiej Wikipedii na te tematy są zazwyczaj poprawialne), ale mogę streścić wykład, który słyszałem, który obejmował także podstawową teorię testowania.

Tryby testowania

Istnieją różne klasy testów, takie jak testy jednostkowe lub testy integracyjne . Test jednostkowy stwierdza, że ​​spójny fragment kodu (funkcja, klasa, moduł) wzięty z własnych prac zgodnie z oczekiwaniami, podczas gdy test integracyjny potwierdza, że ​​wiele takich elementów działa poprawnie.

Przypadek testowy jest znanym środowiskiem, w którym wykonywany jest fragment kodu, np. Przy użyciu określonego wejścia testowego lub przez wyśmiewanie innych klas. Zachowanie kodu jest następnie porównywane z oczekiwanym zachowaniem, np. Określoną wartością zwracaną.

Test może jedynie udowodnić obecność błędu, nigdy braku wszystkich błędów. Testy ustalają górną granicę poprawności programu.

Pokrycie kodu

Aby zdefiniować metryki pokrycia kodu, kod źródłowy można przetłumaczyć na wykres przepływu sterowania, gdzie każdy węzeł zawiera liniowy segment kodu. Kontrola przepływa między tymi węzłami tylko na końcu każdego bloku i zawsze jest warunkowa (jeśli warunek, to jest goto węzeł A, w przeciwnym razie goto węzeł B). Wykres ma jeden węzeł początkowy i jeden węzeł końcowy.

  • Na tym wykresie pokrycie instrukcji jest stosunkiem wszystkich odwiedzonych węzłów do wszystkich węzłów. Pełny zakres wyciągów nie jest wystarczający do dokładnych testów.
  • Zasięg gałęzi to stosunek wszystkich odwiedzanych krawędzi między węzłami w CFG do wszystkich krawędzi. To niewystarczająco testuje pętle.
  • Pokrycie ścieżki to stosunek wszystkich odwiedzonych ścieżek do wszystkich ścieżek, gdzie ścieżka jest dowolną sekwencją krawędzi od początku do końca węzła. Problem polega na tym, że w przypadku pętli może istnieć nieskończona liczba ścieżek, więc pełnego pokrycia ścieżek nie można praktycznie przetestować.

Dlatego często przydatne jest sprawdzenie pokrycia warunków .

  • W prostym pokryciu warunków każdy warunek atomowy jest raz prawdziwy, a raz fałszywy - ale to nie gwarantuje pełnego pokrycia instrukcji.
  • W pokryciu wielu warunków warunki atomowe przyjęły wszystkie kombinacje truei false. Oznacza to pełne pokrycie oddziałów, ale jest raczej drogie. Program może mieć dodatkowe ograniczenia, które wykluczają pewne kombinacje. Ta technika jest dobra do uzyskania zasięgu gałęzi, może znaleźć martwy kod, ale nie może znaleźć błędów wynikających ze złego stanu.
  • W minimalnym pokryciu wielu warunków każdy warunek atomowy i złożony jest raz prawdziwy i fałszywy. Nadal oznacza pełne pokrycie oddziału. Jest to podzbiór obejmujący wiele warunków, ale wymaga mniejszej liczby przypadków testowych.

Podczas konstruowania danych wejściowych testowych z wykorzystaniem pokrycia warunków należy wziąć pod uwagę zwarcie. Na przykład,

function foo(A, B) {
  if (A && B) x()
  else        y()
}

musi zostać przetestowany z foo(false, whatever), foo(true, false)i foo(true, true)dla pełnego minimalnego pokrycia wielu warunków.

Jeśli masz obiekty, które mogą znajdować się w wielu stanach, sensowne wydaje się testowanie wszystkich przejść stanu analogicznych do przepływów sterowania.

Istnieje kilka bardziej złożonych wskaźników zasięgu, ale są one zasadniczo podobne do wskaźników przedstawionych tutaj.

Są to metody testowania białych skrzynek i mogą być częściowo zautomatyzowane. Należy pamiętać, że pakiet testów jednostkowych powinien dążyć do uzyskania wysokiego zasięgu kodu według dowolnej wybranej metryki, ale 100% nie zawsze jest możliwe. Szczególnie trudno jest przetestować obsługę wyjątków, w których błędy muszą być wstrzykiwane w określone miejsca.

Testy funkcjonalne

Następnie są testy funkcjonalne, które potwierdzają, że kod jest zgodny ze specyfikacją, widząc implementację jako czarną skrzynkę. Takie testy są przydatne zarówno w testach jednostkowych, jak i testach integracyjnych. Ponieważ niemożliwe jest testowanie przy użyciu wszystkich możliwych danych wejściowych (np. Testowanie długości łańcucha ze wszystkimi możliwymi łańcuchami), przydatne jest grupowanie danych wejściowych (i danych wyjściowych) w równoważne klasy - jeśli length("foo")jest poprawne, foo("bar")prawdopodobnie również zadziała. Dla każdej możliwej kombinacji klas równoważności danych wejściowych i wyjściowych wybiera się i testuje co najmniej jedno reprezentatywne dane wejściowe.

Należy dodatkowo przetestować

  • w przypadkach skrajnych length(""), foo("x"), length(longer_than_INT_MAX),
  • wartości, które są dozwolone przez język, ale nie przez kontrakt funkcji length(null), oraz
  • możliwe niepotrzebne dane length("null byte in \x00 the middle")

W 0, ±1, ±x, MAX, MIN, ±∞, NaNprzypadku liczb oznacza to testowanie , aw przypadku porównań zmiennoprzecinkowych testowanie dwóch sąsiednich liczb zmiennoprzecinkowych. Jako kolejne uzupełnienie losowe wartości testowe można wybrać z klas równoważności. Aby ułatwić debugowanie, warto zapisać użyte ziarno…

Testy niefunkcjonalne: testy obciążenia, testy warunków skrajnych

Oprogramowanie ma niefunkcjonalne wymagania, które również muszą zostać przetestowane. Obejmują one testowanie na określonych granicach (testy obciążenia) i poza nimi (testy warunków skrajnych). W przypadku gry komputerowej może to oznaczać minimalną liczbę klatek na sekundę w teście obciążenia. Witryna może być poddana testom warunków skrajnych w celu zaobserwowania czasów reakcji, gdy dwukrotnie więcej odwiedzających niż przewidywano obija serwery. Testy te dotyczą nie tylko całych systemów, ale także pojedynczych jednostek - jak degraduje się tablica skrótów z milionem wpisów?

Innymi rodzajami testów są testy całego systemu, w których symulowane są scenariusze, lub testy akceptacyjne potwierdzające spełnienie umowy deweloperskiej.

Metody niezwiązane z testowaniem

Opinie

Istnieją techniki niezwiązane z testowaniem, które można wykorzystać do zapewnienia jakości. Przykładami są instrukcje, formalne przeglądy kodu lub programowanie w parach. Chociaż niektóre części można zautomatyzować (np. Za pomocą włókien), są one na ogół czasochłonne. Jednak przeglądy kodu przez doświadczonych programistów mają wysoki wskaźnik wykrywanych błędów i są szczególnie cenne podczas projektowania, w którym nie jest możliwe automatyczne testowanie.

Kiedy recenzje kodu są tak świetne, dlaczego wciąż piszemy testy? Dużą zaletą pakietów testowych jest to, że mogą one uruchamiać się (głównie) automatycznie i jako takie są bardzo przydatne w testach regresyjnych .

Formalna weryfikacja

Formalna weryfikacja przebiega i potwierdza pewne właściwości kodu. Weryfikacja ręczna jest przeważnie możliwa w przypadku części krytycznych, a mniej w przypadku całych programów. Dowody stawiają dolną granicę poprawności programu. Dowody mogą być do pewnego stopnia zautomatyzowane, np. Za pomocą sprawdzania typu statycznego.

Niektóre niezmienniki można jawnie sprawdzić za pomocą assertinstrukcji.


Wszystkie te techniki mają swoje miejsce i się uzupełniają. TDD zapisuje testy funkcjonalne z góry, ale testy można ocenić na podstawie wskaźników zasięgu po wdrożeniu kodu.

Pisanie testowalnego kodu oznacza pisanie małych jednostek kodu, które można testować osobno (funkcje pomocnicze o odpowiedniej szczegółowości, zasada pojedynczej odpowiedzialności). Im mniej argumentów pobiera każda funkcja, tym lepiej. Taki kod nadaje się również do wstawiania fałszywych obiektów, np. Poprzez wstrzykiwanie zależności.

amon
źródło
2
Doceniam misterną odpowiedź, ale obawiam się, że nie ma to prawie nic wspólnego z tym, o co prosiłem :) Ale na szczęście programmers.stackexchange.com/questions/78675/… wydaje się być tak blisko, jak to możliwe celując w.
Erik Kaplun,
To świetne rzeczy. Czy możesz polecić jakieś książki lub rzeczy?
Marcin
4

Może „testowanie oparte na specyfikacji” również stanowi odpowiedź na twoje pytanie. Sprawdź te moduły testowe (których jeszcze nie używałem). Wymagają one napisania wyrażenia matematycznego w celu określenia zestawów wartości testowych zamiast pisania testu jednostkowego przy użyciu wybranych pojedynczych wartości danych.

Test :: Lectrotest

Jak mówi autor, ten moduł Perla został zainspirowany modułem szybkiego sprawdzania Haskella . Na tej stronie znajduje się więcej linków, z których niektóre nie żyją.

knb
źródło
2

Jednym podejściem matematycznym jest testowanie wszystkich par . Chodzi o to, że większość błędów jest aktywowana przez wybór jednej opcji konfiguracji, a większość pozostałych jest aktywowana przez pewną parę opcji wziętych jednocześnie. Dlatego większość można złapać, testując „wszystkie pary”. Wyjaśnienie matematyczne (z uogólnieniami) znajduje się tutaj:

System AETG: podejście do testowania oparte na konstrukcji kombinatorycznej

(istnieje wiele innych takich referencji)

David Ketcheson
źródło
2

Istnieje kilka równań matematycznych, ale zależy to od rodzaju testowanego oprogramowania. Na przykład założenie krytycznego uszkodzenia zakłada, że ​​awarie nie są wynikiem dwóch lub więcej jednoczesnych awarii. Następujące równanie to: f = 4n + 1. f = funkcja, która oblicza liczbę przypadków testowych dla danej liczby zmiennych ( n) + 1 jest dodatkiem stałej, w której wszystkie zmienne przyjmują wartość nominalną.

Innym rodzajem testowania, który wymaga równań matematycznych, jest Testowanie odporności Testowanie odporności lub poprawności przypadków testowych w procesie testowym. W tym teście wprowadzałeś zmienne z legalnego zakresu wejściowego (czyste przypadki testowe) i zmienne wejściowe poza zakresem wejściowym (brudne przypadki testowe). Użyłbyś następującego równania matematycznego: f = 6n + 1 . 6n oznacza, że ​​każda zmienna musi przyjmować 6 różnych wartości, podczas gdy inne wartości przyjmują wartość nominalną. * + 1 * oznacza dodanie stałej 1.

Charles Lucas Shabazz
źródło