9.2 — Pokrycie kodu

W poprzednim lekcja 9.1 -- Wprowadzenie do testowania kodu, omówiliśmy, jak pisać i zachowywać proste testy. Na tej lekcji porozmawiamy o tym, jakie testy warto napisać, aby upewnić się, że kod jest poprawny.

Pokrycie kodu

Termin Pokrycie kodu służy do opisania, jaka część kodu źródłowego programu jest wykonywana podczas testowania. Istnieje wiele różnych metryk używanych do pokrycia kodu. W poniższych sekcjach omówimy kilka bardziej przydatnych i popularnych.

Pokrycie instrukcji

Termin Pokrycie instrukcji odnosi się do procentu instrukcji w kodzie, które zostały przetestowane przez procedury testowe.

Rozważ następującą funkcję:

int foo(int x, int y)
{
    int z{ y };
    if (x > y)
    {
        z = x;
    }
    return z;
}

Wywołanie tej funkcji jako foo(1, 0) zapewni pełne pokrycie instrukcji dla tej funkcji, ponieważ każda instrukcja w tej funkcji będzie wykonaj.

Dla naszej isLowerVowel() funkcji:

bool isLowerVowel(char c)
{
    switch (c) // statement 1
    {
    case 'a':
    case 'e':
    case 'i':
    case 'o':
    case 'u':
        return true; // statement 2
    default:
        return false; // statement 3
    }
}

Ta funkcja będzie wymagała dwóch wywołań, aby przetestować wszystkie instrukcje, ponieważ nie ma możliwości dotarcia do instrukcji 2 i 3 w tym samym wywołaniu funkcji.

Chociaż cel 100% pokrycia instrukcji jest dobry, często nie wystarcza do zapewnienia poprawności.

Pokrycie gałęzi

Pokrycie gałęzi odnosi się do procentu rozgałęzień które zostały wykonane, każdą możliwą gałąź liczy się osobno. An if statement ma dwie gałęzie - gałąź, która jest wykonywana, gdy warunek jest true, i gałąź, która jest wykonywana, gdy warunek jest false (nawet jeśli nie ma odpowiadającego else statement do wykonania). Instrukcja switch może mieć wiele rozgałęzień.

int foo(int x, int y)
{
    int z{ y };
    if (x > y)
    {
        z = x;
    }
    return z;
}

Poprzednie wywołanie foo(1, 0) dało nam 100% pokrycia instrukcji i sprawdziło przypadek użycia gdzie x > y, ale to dało nam tylko 50% pokrycia rozgałęzień. Potrzebujemy jeszcze jednego wywołania, aby foo(0, 1) przetestować przypadek użycia, w którym if statement nie zostanie wykonany.

bool isLowerVowel(char c)
{
    switch (c)
    {
    case 'a':
    case 'e':
    case 'i':
    case 'o':
    case 'u':
        return true;
    default:
        return false;
    }
}

W funkcji isLowerVowel() potrzebne będą dwa wywołania, aby zapewnić 100% pokrycie gałęzi: jedno (np. isLowerVowel('a')) do przetestowania pierwszych przypadków i drugie (np. isLowerVowel('q')) do przetestowania przypadku domyślnego. Wiele przypadków wchodzących w skład tego samego ciała nie musi być testowanych osobno — jeśli jeden zadziała, wszystkie powinny.

Rozważmy teraz następującą funkcję:

void compare(int x, int y)
{
	if (x > y)
		std::cout << x << " is greater than " << y << '\n'; // case 1
	else if (x < y)
		std::cout << x << " is less than " << y << '\n'; // case 2
	else
		std::cout << x << " is equal to " << y << '\n'; // case 3
}

potrzebne są 3 wywołania, aby uzyskać 100% pokrycia gałęzi: compare(1, 0) testuje pozytywny przypadek użycia dla pierwszego if statement. compare(0, 1) testuje negatywny przypadek użycia dla pierwszego if statement i pozytywny przypadek użycia dla pierwszego<<<M33>>>i pozytywny przypadek użycia dla pierwszego second if statement. compare(0, 0) testuje negatywny przypadek użycia dla pierwszego i drugiego if statement i wykonuje else statement. Zatem możemy powiedzieć, że ta funkcja jest niezawodnie testowana za pomocą 3 wywołań (co jest nieco lepsze niż 18 kwintylionów).

Najlepsza praktyka

Dąż do 100% pokrycia gałęzi swojego kodu.

Pokrycie pętli

Pokrycie pętli (nieformalnie nazywany testem 0, 1, 2) mówi, że jeśli w kodzie znajduje się pętla, należy upewnić się, że działa on poprawnie podczas iteracji 0 razy, 1 raz i 2 razy. Jeśli działa poprawnie w przypadku 2 iteracji, powinno działać poprawnie dla wszystkich iteracji większych niż 2. Te trzy testy obejmują zatem wszystkie możliwości (ponieważ pętla nie może wykonać ujemnej liczby razy).

Rozważ:

#include <iostream>

void spam(int timesToPrint)
{
    for (int count{ 0 }; count < timesToPrint; ++count)
         std::cout << "Spam! ";
}

Aby poprawnie przetestować pętlę w ramach tej funkcji, powinieneś wywołać ją trzy razy: spam(0) aby przetestować przypadek iteracji zerowej, spam(1) aby przetestować przypadek jednej iteracji i spam(2) przetestowanie przypadku dwóch iteracji. Jeśli spam(2) działa, to spam(n) powinno działać, gdzie n > 2.

Najlepsza praktyka

Użyj 0, 1, 2 test aby upewnić się, że pętle działają poprawnie przy różnej liczbie iteracji.

Testowanie różnych kategorii danych wejściowych

Podczas pisania funkcji akceptujących parametry lub akceptowania danych wejściowych użytkownika należy rozważyć, co stanie się z różnymi kategoriami danych wejściowych. W tym kontekście używamy terminu „kategoria” do określenia zestawu danych wejściowych o podobnych cechach.

Na przykład, gdybym napisał funkcję obliczającą pierwiastek kwadratowy z liczby całkowitej, z jakimi wartościami miałoby sens jej testowanie? Prawdopodobnie zacząłbyś od jakiejś normalnej wartości, np. 4. Ale dobrym pomysłem byłoby również przetestowanie z 0 i liczbą ujemną.

Oto kilka podstawowych wskazówek dotyczących testowania kategorii:

W przypadku liczb całkowitych upewnij się, że rozważyłeś, w jaki sposób funkcja obsługuje wartości ujemne, zero i wartości dodatnie. Powinieneś także sprawdzić, czy nie ma przepełnienia, jeśli ma to znaczenie.

W przypadku liczb zmiennoprzecinkowych upewnij się, że zastanawiałeś się, w jaki sposób funkcja radzi sobie z wartościami, które mają problemy z precyzją (wartości, które są nieco większe lub mniejsze niż oczekiwano). Dobre double wpisz wartości do przetestowania to 0.1 i -0.1 (aby przetestować liczby nieco większe niż oczekiwano) i 0.7 i -0.7 (aby przetestować liczby nieco mniejsze niż oczekiwano).

W przypadku ciągów upewnij się, że rozważyłeś, jak funkcja obsługuje pusty ciąg, ciąg alfanumeryczny, ciągi znaków ze spacjami (początkowy, końcowy i wewnętrzny) oraz ciągi, które są wszystkie spacje.

Jeśli Twoja funkcja przyjmuje wskaźnik, nie zapomnij również o przetestowaniu nullptr (nie martw się, jeśli to nie ma sensu, jeszcze tego nie omawialiśmy).

Najlepsza praktyka

Przetestuj różne kategorie wartości wejściowych, aby upewnić się, że Twoje urządzenie obsługuje je prawidłowo.

Czas quizu

Pytanie nr 1

Co to jest pokrycie gałęzi?

Pokaż rozwiązanie

Pytanie nr 2

Ile testów będzie wymagać poniższa funkcja, aby minimalnie potwierdzić, że działa?

bool isLowerVowel(char c, bool yIsVowel)
{
    switch (c)
    {
    case 'a':
    case 'e':
    case 'i':
    case 'o':
    case 'u':
        return true;
    case 'y':
        return yIsVowel;
    default:
        return false;
    }
}

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
75 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze