Dzięki testom pokrycia dowiesz się, które linie Twojego kodu zostały wykonane i ile razy były wykonywane. Możesz w ten sposób wykryć  martwe fragmenty kodu, dowiedzieć się, gdzie warto pomyśleć nad optymalizacją, oraz sprawdzić, w jakim stopniu Twoje testy jednostkowe pokrywają kod.

Testy pokrycia w 3 krokach

Załóżmy, że mamy program program.cpp.

Żeby zrobić testy pokrycia kodu, musimy wykonać trzy kroki:

> g++ -fprofile-arcs -ftest-coverage -o program program.cpp
> ./program
> gcov program.cpp
  1. Najpierw kompilujemy program z flagami -fprofile-arcs oraz -ftest-coverage, dzięki czemu program zostaje wzbogacony o instrukcje diagnozujące jego działanie.
  2. Następnie uruchamiamy program – wtedy zostają wygenerowane jego statystyki.
  3. Na koniec, odpalamy narzędzie gcov, które wygeneruje dla nas dwa raporty:
  • podstawowy (podsumowanie testu) – na standardowe wyjście
  • szczegółowy (które linie ile razy wykonano) – do pliku gcov

Dla każdego pliku źródłowego automatycznie generowany jest osobny raport (osobny plik). My mamy tylko jeden plik, wiec powstanie jeden raport – program.cpp.gcov. Zobaczmy, jak to wszystko działa na konkretnym przykładzie.

Szybki przykład

Mamy prosty programik liczący silnię:

// program.cpp

long int factorial(unsigned short n)
{
	if (n == 0)
		return 0;

	long int fact = 1;
	for (int i = 2; i <= n; i++)
		fact *= i;

	return fact;
}

int main()
{
  	return factorial(10);
}

Wykonujemy 3 kroki do testów pokrycia:

> g++ -fprofile-arcs -ftest-coverage -o program program.cpp
> ./program
> gcov program.cpp

W wyniku tego, na standardowe wyjście dostajemy krótkie podsumowanie:

File 'program.cpp'
Lines executed:88.89% of 9
program.cpp:creating 'program.cpp.gcov'

Mówi ono, że moduł program.cpp liczy 9 linii kodu, z czego 88.89% zostało wykonane. Mówi też, że raport został zapisany do pliku program.cpp.gcov. Zobaczmy więc, co on zawiera:

wywołania:linia:kod programu

        1:    1:long int factorial(unsigned short n)
        -:    2:{
        1:    3:	if (n == 0)
    #####:    4:		return 0;
        -:    5:
        1:    6:	long int fact = 1;
       10:    7:	for (int i = 2; i <= n; i++)
        9:    8:		fact *= i;
        -:    9:
        1:   10:	return fact;
        -:   11:}
        -:   12:
        1:   13:int main()
        -:   14:{
        1:   15:  	return factorial(10);
        -:   16:}

Pierwsza kolumna przedstawia liczbę wywołań, druga – nr linii.

Co nam mówi ten raport? Ano mówi na przykład, że funkcja factorial została wywołana 1 raz. Widać też, że pętla for wykonana została 9 razy, oraz że warunek if (n == 0) nigdy nie został spełniony. W ten sposób możemy sprawdzić, czy program wykonuje się zgodnie z naszymi założeniami!

Oznaczenia w kolumnie wywołania:

  • ##### – linia nigdy nie wykonana
  • “-” – linia nie zawiera kodu wykonywalnego
  • liczba – liczba wykonań linii kodu

Przydatne flagi gcov:

  • -help – bardzo krótka lista flag narzędzia gcov. Przydatne!
  • -n – nie generuje plików gcov. Przydatne do sprawdzania pokrycia kodu testami, kiedy interesuje nas tylko ogólny procent pokrycia, a nie szczegóły która linia ile razy została wykonana
  • -b – procentowe statystyki rozgałęzień kodu – do każdej instrukcji warunkowej dodana zostanie informacja, jaki procent testów wyszedł na true, jaki na false
  • -b -c – liczbowe zamiast procentowe statystyki rozgałęzień kodu (ile razy wyszło true, ile false)
  • -f – podsumowanie dla każdej funkcji – ile procent linii zostało wykonane w danej funkcji. Na standardowe wyjście, nie do pliku

Podsumowanie

Korzystając z narzędzia gcov jesteś w stanie dokładnie prześledzić wykonanie swojego programu. Możesz odnaleść martwe bloki kodu oraz zidentyfikować fragmenty intensywnie przetwarzane, którym optymalizacja przyniesie największą korzyść. Możesz też sprawdzić, w jakim stopniu testy jednostkowe pokrywają Twój kod.

Następnym razem pokażę na prościutkim przykładzie, jak taki unit test coverage można zrobić.

Related Posts: