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
true
i 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, ±∞, NaN
przypadku 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ą assert
instrukcji.
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.
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.