2. Testy jednostkowe¶
Biblioteka Moss posiada w swoim zbiorze modułów plik, zawierający funkcje, ułatwiające tworzenie testów jednostkowych. Testowanie kodu jest stosunkowo ważną rzeczą w trakcie tworzenia oprogramowania. Jak to niektórzy mówią - lepiej zapobiegać niż leczyć. Testy jednostkowe wydłużają czas tworzenia oprogramowania, jednak mogą zaoowocować w przyszłości, wykrywając błędy w zmianach wprowadzonych w kodzie modułu, do którego test został napisany. Niejednokrotnie zdarza się nawet że niektóre błędy wykrywane są już podczas jego tworzenia.
Moduł MSTest odpowiedzialny za tworzenie testów jednostkowych jest stosunkowo prosty w użytkowaniu. Wiele przykładów jego użycia można znaleźć w folderze z testami jednostkowymi dla biblioteki. Do rozpoczęcia, oczywiście będzie potrzebny kod, który ma być testowany. Poniżej podany zostanie pełny przykład wykorzytania modułu do tworzenia testów jednostkowym.
Testowany plik:
int f_add( int a, int b )
{
return a + b;
}
int f_sub( int a, int b )
{
return b - a;
}
int f_mul( int a, int b )
{
return a * b;
}
Aby przetestować powyższy kod, należy najpierw utworzyć funkcje testujące dla niego. Nie ma żadnych reguł dotyczących tego, jak dobry test ma wyglądać, jednak tworzenie testu dla każdej funkcji jest dobrym rozwiązaniem. Dobry test, to test, który próbuje wywołać funkcję na wszystkie możliwe sposoby. Oczywiście nie zawsze jest to możliwe.
Przykład funkcji testujących:
/* test dla dodawania */
int f_test_add( MST_FUNCTION *info )
{
struct EXTRADATA *data = (struct EXTRADATA*)info->Data;
mst_prepare( info );
data->result = f_add( data->a, data->b );
mst_assert_sint( data->result, ==, 5 );
return MSET_OK;
}
/* test dla odejmowania */
int f_test_sub( MST_FUNCTION *info )
{
struct EXTRADATA *data = (struct EXTRADATA*)info->Data;
mst_prepare( info );
data->result = f_sub( data->a, data->b );
mst_assert_sint( data->result, ==, 1 );
return MSET_OK;
}
/* test dla mnożenia */
int f_test_mul( MST_FUNCTION *info )
{
struct EXTRADATA *data = (struct EXTRADATA*)info->Data;
mst_prepare( info );
data->result = f_mul( data->a, data->b );
mst_assert_sint( data->result, ==, 6 );
return MSET_OK;
}
Utworzenie funkcji testujących napisany kod, jest już większą połową sukcesu.
Teraz wystarczy zainicjalizować struktury, odpowiedzialne za testy.
W tym momencie można wybrać jedno z dwóch rozwiązań - albo wywoływać każdy test pojedynczo,
albo wszystkie na raz po przypisaniu ich do struktury zestawu.
Poniżej zaprezentowana została druga opcja.
Warto się zastanowić, czy funkcje dostępne w strukturze zbioru, przekazywane do pól MST_SUITE.Setup
oraz MST_SUITE.TearDown
nie ułatwią wykonywania testów i zarządzania pamięcią.
Pozostała część kodu:
struct EXTRADATA {
int a, b, result;
};
int f_test_add( MST_FUNCTION *info );
int f_test_sub( MST_FUNCTION *info );
int f_test_mul( MST_FUNCTION *info );
/* funkcja przygotowująca dane */
int setup_op_function( MST_SUITE *info )
{
struct EXTRADATA *data;
if( info->Data )
return MSEC_INVALID_ARGUMENT;
info->Data = malloc( sizeof(struct EXTRADATA) );
data = (struct EXTRADATA*)info->Data;
data->result = 0;
data->a = 3;
data->b = 2;
return MSEC_OK;
}
/* funkcja zwalniająca pamięć po danych */
void teardown_op_function( MST_SUITE *info )
{
free( info->Data );
}
/* funkcja główna, uruchamiająca test */
int main( int argc, char **argv )
{
MST_FUNCTION operation_funcs[] = {
{ MST_STRINGIFY(f_test_add), "Add two numbers", NULL },
{ MST_STRINGIFY(f_test_sub), "Substract two numbers", NULL },
{ MST_STRINGIFY(f_test_mul), "Multiply two numbers", NULL },
{ MST_LASTRECORD }
};
MST_SUITE operation_suite = {
"Test add, sub and mul functions",
FALSE, /* BreakOnError */
setup_op_function, /* Setup */
teardown_op_function, /* TearDown */
NULL,
operation_funcs
};
/* uruchom testy w zbiorze */
return mst_run_suite( &operation_suite );
}
Przykładowe wyjście:
===============================================================================
Test add, sub and mul functions
--------------------------------------------------------------------- [001/003]
[TEST] f_test_add
[DESC] Add two numbers
[STAT] SUCCESS! > Passed asserts: 1
--------------------------------------------------------------------- [002/003]
[TEST] f_test_sub
[DESC] Substract two numbers
[STAT] FAILED! > Passed asserts: 0
------
# Error in test.c on line 90
# data->result == 1
# Where: L = -1 and R = 1
--------------------------------------------------------------------- [003/003]
[TEST] f_test_mul
[DESC] Multiply two numbers
[STAT] SUCCESS! > Passed asserts: 1
===============================================================================
Jak można zauważyć, powyższy kod wygenerował błąd w trakcie działania funkcji f_test_sub
.
Przyglądając się bliżej funkcji f_sub
, można zauważyć, że podczas odejmowania argumenty podane zostały na odwrót.
Testy umieszczone w zestawie nie zostały przerwane podczas wystąpienia błędu, ponieważ pole MST_SUITE.BreakOnError
zostało ustawione na wartość FALSE
.
Testy nie są zależne od siebie, więc ustawianie tego pola na wartość TRUE
jest niepotrzebne.
Oczywiście nie ulega wątpliwości to, że aby błąd został wykryty, test jednostkowy musi być poprawnie napisany.
2.1. Struktury¶
-
MST_FUNCTION
¶ Struktura odpowiedzialna za przechowywanie informacji o funkcji testującej daną część kodu. Większość pól struktury w głównej mierze wykorzystywana jest w funkcji
mst_run_test()
, gdzie używane są podczas wyświetlania w konsoli informacji o uruchamianym teście. Można je również wykorzystać bezpośrednio wewnątrz uruchomionego testu, gdzie cała struktura przekazywana jest do argumentu funkcji testującej. PoleMST_FUNCTION.PassedAsserts
zwiększane jest automatycznie za każdy razem, gdy wywoływane jest makromst_assert
.Strukturę można inicjalizować w następujący sposób:
int additional_data = 5; MST_FUNCTION test = { module_part_test_func, /* Function */ "testing_function", /* Name */ "Some description of this test.", /* Desc */ &additional_data, /* Data */ 0 /* PassedAsserts */ };
Inicjalizację ostatniego pola można jednak pominąć, ponieważ ustawiane jest ono automatycznie podczas uruchamiania testu. W przypadku gdy struktura nie będzie zawierać żadnych danych, warto dopisać do niej wartość NULL, zamiast zostawiać inicjalizacje na pastwę losu kompilatora. Jest to ważna rada w przypadku gdy test uruchamia się z zestawu i to właśnie z niego argument ma być podesłany do funkcji.
-
char *
Function
(MST_FUNCTION *info)¶ Funkcja testująca uruchamiana przez funkcję
mst_run_test()
. Powinna udowodnić poprawność testowanego kodu poprzez stosowanie makramst_assert
dla każdego testowanego wyrażenia. Dzięki temu błąd, który wystąpi podczas działania funkcji zostanie odpowiednio odnotowany w konsoli. Funkcja jako argument pobiera strukturę informacji o teście, dzięki której można pobrać zapisane w niej dane. Wystąpienie błędu w asercji podczas działania funkcji natychmiast przerywa jej działanie, zwracając treść błędu. Nazwa parametru funkcji nie może być zmieniana, zawsze musi to byćinfo
.Przykład prostej funkcji testującej:
char *test_function( MST_FUNCTION *info ) { int ercode; mst_assert( info->Data != NULL ); ercode = do_something_with_data( info->Data ); mst_assert( !ercode ); return MST_SUCCESS; }
Parametry: - info – Struktura zawierająca informacje o przetwarzanym teście.
Zwraca: Wartość NULL lub treść błędu który wystąpił w przetwarzanej asercji.
-
char *
Name
¶ Nazwa testu używana przy wyświetlaniu informacji o uruchomionym teście w kosnoli. Nazwa reprezentuje test podczas wyświetlania w konsoli, dzięki czemu zamiast numerków oznaczających indeksy testów wyświetlane są przypisane do nich nazwy. To pole struktury jest wymagane i nie może być oznaczone wartością NULL.
-
char *
Desc
¶ Opis testu używany przy wyświetlaniu informacji o uruchomionym teście w konsoli. Opis powinien być krótki i przejrzysty, dzięki czemu może pomóc w zrozumieniu na czym tak właściwie polega napisany test, bez zaglądania w kod i odczytywania komentarzy. Wartość ta nie jest wymagana i w przypadku braku opisu powinna być oznaczona wartością NULL, co spowoduje, że opis nie zostanie w ogóle wyświetlony w konsoli.
-
void *
Data
¶ Dodatkowe dane przekazywane do funkcji testującej wraz ze strukturą testu. Zmienna nie jest wymagana i w przypadku braku danych powinna być oznaczona wartością NULL. Podczas wykonywania zestawu testów, wartość ta w przypadku ustawienia wartości NULL jest zastępowana wartością zmiennej globalnej dla całego zestawu, ustawioną w jego strukturze.
-
size_t
PassedAsserts
¶ Ilość poprawnie wykonanych asercji w podpiętej do struktury funkcji testu. Zmienna resetowana jest do wartości 0 podczas startu testu i zwiększana automatycznie w trakcie wywoływania makra pozwalającego na sprawdzenie danego wyrażenia. Można dzięki temu wypisać ilość wszystkich asercji w funkcji lub tylko tych, które zostały wywołane do wystąpienia błędu. Wartość tej zmiennej nie powinna być zmieniana manualnie.
Pole to można pominąć podczas inicjalizacji struktury:
MST_FUNCTION test = { test_func, "func_name", "desc", NULL };
-
char *
ErrorMessage
¶ Treść błędu generowanego podczas działania testu. W trakcie błędu, dla zmiennej przydzielana jest pamięć, potrzebna do przechowania treści wiadomości. Zmienna ta jest wypełniana automatycznie przez funkcje raportujące błędy, wywoływane przez asercje. Pamięć przydzielona dla tej zmiennej nie jest zwalniana automatycznie.
-
char *
-
MST_SUITE
¶ Struktura odpowiedzialna za przechowywanie informacji o zestawie, zawierającym funkcje testujące. W głównej mierze struktura wykorzystywana jest w funkcji
mst_run_suite()
. Jednym z ważniejszych pól jest poleMST_SUITE.BreakOnError
, gdzie wartość decyduje o tym, czy zbiór podczas działania zostanie przerwany po wykryciu błędu. Struktura jest przekazywana do funkcji wywoływanych przed rozpoczęciem i zaraz po zakończeniu wszystkich dostępnych w tablicy testów. Ostatni test w tablicy powinien zawierać wszystkie pola równe wartości NULL lub 0. Najlepszym sposobem jest inicjalizacja ostatniego rekordu makremMST_LASTRECORD
.Przykład inicjalizacji struktury:
/* lista funkcji testujących */ MST_FUNCTION suite_functions[] = { { MST_STRINGIFY(mst_test_01), "Desc_01", NULL }, { MST_STRINGIFY(mst_test_02), "Desc_02", NULL }, { MST_STRINGIFY(mst_test_02), "Desc_03", NULL }, { MST_LASTRECORD } }; int sample_data = 5; /* inicjalizacja struktury dla zestawu */ MST_SUITE suite_tests = { "Suite description", /* Desc */ TRUE, /* BreakOnError */ setup_test_function, /* Setup */ teardown_test_function, /* TearDown */ &sample_data, /* Data */ suite_functions /* Tests */ };
Powyższy kod utworzy zestaw testów, przerywanych w przypadku wystąpienia błędu. Każda struktura testu otrzyma wartość
sample_data
w polu odpowiedzialnym za dane. Każdy test może być zależny od danych, zmodyfikowanych w poprzednim teście, dlatego struktura posiada poleMST_SUITE.BreakOnError
ustawione na wartośćTRUE
.-
char *
Desc
¶ Opis zestawu wyświetlany przed uruchomieniem jakiegokolwiek testu. Pole to powinno opisywać w skrócie całość zestawu, choć może być jednocześnie jego nazwą. Opis wykorzystywany jest w funkcji
mst_run_suite()
. Pole jest wymagane, więc powinno zawierać przynajmniej informację o tym, do czego zestaw się odnosi.
-
bool *
BreakOnError
¶ Informacja o tym, czy zestaw po wykryciu błędu w jednej z funkcji testujących ma przerwać działanie pozostałych. Pole to jest bardzo ważne w przypadku, gdy dane wychodzące z jednej funkcji testowej, modyfikowane są w niej i przekazywane do drugiej, która operuje na modyfikacjach i nie może bez nich wykonać się prawidłowo. Gdy pole zawiera wartość
TRUE
i w jednej z funkcji testujących wystąpi bład w asercji, cały zestaw uruchamianych testów natychmiast zostaje przerwany, zwracając błąd. W przeciwnym wypadku funkcja nadal zwróci błąd, ale dopiero po zakończeniu wykonywania wszystkich znajdujących się w zestawie testów.
-
int
Setup
(MST_SUITE *info)¶ Funkcja wywoływana przed wykonaniem jakiekogolwiek testu. Może być potraktowana jako funkcja pozwalająca na przygotowanie danych do testowania. Jako argument przyjmuje strukturę z informacjami o zestawie w której znajdują się również przekazane dane globalne dla każdego testu. W przypadku gdy funkcja zwróci wartość inną niż 0, funkcje testowe nie są wywoływane. Pole nie jest wymagane i w przypadku braku funkcji należy podać wartość NULL.
Przykład prostej funkcji:
int setup_test( MST_SUITE *info ) { if( info->Data ) return MSEC_INVALID_ARGUMENT; /* przygotuj tablicę do działania */ info->Data = ms_array_alloc( sizeof(int), 100 ); return info->Data ? MSEC_OK : MSEC_MEMORY_ALLOCATION; }
Parametry: - info – Wskaźnik na strukturę zawierającą informacje o uruchomionym zestawie.
Zwraca: Kod błędu który wystąpił podczas działania funkcji lub wartość 0.
-
void
TearDown
(MST_SUITE *info)¶ Funkcja wywoływana po wykonaniu testów zawartych w zestawie. Wywołanie tej funkcji występuje nawet gdy w trakcie testów wyktyty zostanie błąd a struktura będzie miała ustawione pole
MST_SUITE.BreakOnError
na wartośćTRUE
. Główne zastosowanie tej funkcji to zwalnianie pamięci pozostałej po wykonanych testach. Pole nie jest wymagane i w przypadku braku funkcji należy podać wartość NULL.Przykład prostej funkcji:
struct ARRAYSET { MS_ARRAY a1, a2, a3, a4; }; void teardown_test( MST_SUITE *info ) { struct ARRAYSET *aset = (struct ARRAYSET*)info->Data; if( !aset ) return; ms_array_free( &aset->a1 ); ms_array_free( &aset->a2 ); ms_array_free( &aset->a3 ); ms_array_free( &aset->a4 ); }
Parametry: - info – Wskaźnik na strukturę zawierającą informacje o uruchomionym zestawie.
-
void *
Data
¶ Dodatkowe dane przekazywane do struktury testu. W przypadku gdy struktura testu zawiera swoje własne dane, pole to nie jest wykorzystywane. W przeciwnym wypadku wartość pola kopiowana jest do pola
MST_FUNCTION.Data
, gdzie przekazywana jest wraz ze strukturą do funkcji testującej. Pole to nie jest wymagane i powinno być ustawiane na wartość NULL w przypadku gdy do funkcji nie mają być przekazywane żadne dane.
-
MST_FUNCTION *
Tests
¶ Tablica zawierająca testy do wykonania podczas uruchomienia zestawu. Wszystkie testy znajdujące się na liście w tym polu, zczytywane są i wykonywane przez funkcję
mst_run_suite()
. Testy wykonywane są w takiej kolejności w jakiej zostały podane w tablicy, aż do napotkania ostatniego rekordu, który musi być zainicjalizowany wartościami NULL lub 0 dla każdego pola strukturyMST_FUNCTION
. Do ułatwienia tego zadania stworzono makro o nazwieMST_LASTRECORD
.Przykład tablicy z testami przekazywanymi do zestawu:
MST_FUNCTION TestFunctions[] = { { MST_STRINGIFY(mst_test_01), "Desc_01", NULL }, { MST_STRINGIFY(mst_test_02), "Desc_02", NULL }, { MST_STRINGIFY(mst_test_03), "Desc_03", NULL }, { MST_STRINGIFY(mst_test_04), "Desc_04", NULL }, { MST_STRINGIFY(mst_test_05), "Desc_05", NULL }, { MST_STRINGIFY(mst_test_06), "Desc_06", NULL }, { MST_STRINGIFY(mst_test_07), "Desc_07", NULL }, { MST_STRINGIFY(mst_test_08), "Desc_08", NULL }, { MST_STRINGIFY(mst_test_09), "Desc_09", NULL }, /* ostatni rekord można inicjalizować w ten sposób */ { MST_LASTRECORD }, /* lub w ten, choć ostatnie 0 jest zbędne */ { NULL, NULL, NULL, NULL, 0 } };
-
char *
2.2. Funkcje¶
-
int
mst_run_suite
(MST_SUITE *suite)¶ Funkcja uruchamia zestaw testów jednostkowych, przypisanych do podanej struktury w postaci tablicy. Przed uruchomieniem wyświetlana jest informacja o aktualnie działającym zestawie i wywoływana jest funkcja
MST_SUITE.Setup
, pozwalająca na przygotowanie danych do testów. Następnie wywoływane są po koleji wszystkie testy z tablicy przypisanej do polaMST_SUITE.Tests
. W przypadku gdy któryś z nich zwróci błąd a poleMST_SUITE.BreakOnError
będzie ustawione na wartośćTRUE
, pętla wywołująca funkcje testowe zostanie przerwana. Po zakończeniu wszystkich dostępnych w tablicy testów lub w przypadku ich przerwania, wywoływana jest funkcja zapisana w poluMST_SUITE.TearDown
, pozwalająca na zwolnienie pamięci przydzielonej na dane testowe. Kod błędu zwracany przez funkcję nie jest ustalony z racji tego, iż jest on zależny w głównej mierze od funkcjimst_run_test()
.Przykład użycia funkcji:
... MST_FUNCTION tests[] = { { test_func1, "func_name1", "desc1", NULL }, { test_func2, "func_name2", "desc2", NULL }, { test_func3, "func_name3", "desc3", NULL } }; MST_SUITE suite = { "Suite description", TRUE, NULL, NULL, NULL, tests }; ... if( mst_run_suite(&suite) ) return -1; ...
Parametry: - func – Struktura zawierająca informacje o zestawie funkcji testujących.
Zwraca: Wartość różna od 0 w przypadku błędu lub 0.
-
int
mst_run_test
(MST_FUNCTION *info, size_t current, size_t count)¶ Funkcja uruchamia test jednostkowy przypisany do podanej struktury. Przed jego uruchomieniem wyświetla informacje o teście podane w strukturze. Funkcja wywoływana jest w głównej mierze wprost z funkcji
mst_run_suite()
, jednak może być wywoływana samodzielnie. Na uwagę zasługują ostatnie dwa parametry, reprezentujące numer aktualnego testu i ilość wszystkich testów. Liczby te wyświetlane są nad informacjami wypisywanymi ze struktury. Można je pominąć, wpisując w ich miejsce wartości 0, co spowoduje pominięcie ich w trakcie wypisywania informacji w konsoli. Funkcja w przypadku wystąpienia błędu zwróci wartość różną od 0, która będzie reprezentowała ilość znaków w zwróconej przez test wiadomości.Przykład użycia funkcji:
... MST_FUNCTION test1 = { test_func1, "func_name1", "desc1", NULL }; MST_FUNCTION test2 = { test_func2, "func_name2", "desc2", NULL }; ... if( mst_run_test(&test1, 0, 0) ) return -1; if( mst_run_test(&test2, 3, 50) ) return -1; ...
Parametry: - info – Struktura zawierająca informacje o funkcji testującej.
- current – Aktualny numer testu lub 0.
- count – Ilość wszystkich testów lub 0.
Zwraca: Wartość 0 lub w przypadku błędu ilość wypisanych znaków.
-
int
mst_report_sint
(MST_FUNCTION *info, const char *exp, const char *file, int line, llong a, llong b)¶ Tworzenie raportu o błędzie, spowodowanym przez asercje sprawdzanego wyrażenia, składającego się z dwóch liczb całkowitych. Funkcja w głównej mierze wywoływana jest automatycznie przez makro
mst_assert_sint
. Uruchomienie funkcji powoduje przydzielenie pamięci dla polaMST_FUNCTION.ErrorMessage
i zapisanie w nim treści błędu, tworzonego z przekazanych parametrów. Przydzieloną pamięć należy zwolnić samodzielnie po wykorzystaniu danych, zapisanych w zmiennej.Parametry: - info – Struktura zawierająca informacje o funkcji testującej.
- exp – Wyrażenie w którym wystąpił błąd.
- file – Plik w którym wystąpił błąd.
- line – Linia w której wystąpił błąd w pliku.
- a – Lewa strona wyrażenia, znajdująca się przed operatorem głównym.
- b – Prawa strona wyrażenia, znajdująca się po operatorze głównym.
Zwraca: Kod błędu lub wartość 0.
-
int
mst_report_uint
(MST_FUNCTION *info, const char *exp, const char *file, int line, ullong a, ullong b)¶ Tworzenie raportu o błędzie, spowodowanym przez asercje sprawdzanego wyrażenia, składającego się z dwóch liczb naturalnych. Funkcja w głównej mierze wywoływana jest automatycznie przez makro
mst_assert_uint
. Uruchomienie funkcji powoduje przydzielenie pamięci dla polaMST_FUNCTION.ErrorMessage
i zapisanie w nim treści błędu, tworzonego z przekazanych parametrów. Przydzieloną pamięć należy zwolnić samodzielnie po wykorzystaniu danych, zapisanych w zmiennej.Parametry: - info – Struktura zawierająca informacje o funkcji testującej.
- exp – Wyrażenie w którym wystąpił błąd.
- file – Plik w którym wystąpił błąd.
- line – Linia w której wystąpił błąd w pliku.
- a – Lewa strona wyrażenia, znajdująca się przed operatorem głównym.
- b – Prawa strona wyrażenia, znajdująca się po operatorze głównym.
Zwraca: Kod błędu lub wartość 0.
-
int
mst_report_float
(MST_FUNCTION *info, const char *exp, const char *file, int line, ldouble a, ldouble b)¶ Tworzenie raportu o błędzie, spowodowanym przez asercje sprawdzanego wyrażenia, składającego się z dwóch liczb rzeczywistych reprezentowanych przez zapis zmiennoprzecinkowy. Funkcja w głównej mierze wywoływana jest automatycznie przez makro
mst_assert_float
. Uruchomienie funkcji powoduje przydzielenie pamięci dla polaMST_FUNCTION.ErrorMessage
i zapisanie w nim treści błędu, tworzonego z przekazanych parametrów. Przydzieloną pamięć należy zwolnić samodzielnie po wykorzystaniu danych, zapisanych w zmiennej. Należy pamiętać, że porównywanie liczb zmiennoprzecinkowych nie wygląda tak samo jak porównywanie liczb całkowitych.Parametry: - info – Struktura zawierająca informacje o funkcji testującej.
- exp – Wyrażenie w którym wystąpił błąd.
- file – Plik w którym wystąpił błąd.
- line – Linia w której wystąpił błąd w pliku.
- a – Lewa strona wyrażenia, znajdująca się przed operatorem głównym.
- b – Prawa strona wyrażenia, znajdująca się po operatorze głównym.
Zwraca: Kod błędu lub wartość 0.
-
int
mst_report
(MST_FUNCTION *info, const char *exp, const char *file, int line)¶ Tworzenie raportu o błędzie, spowodowanym przez asercje na wyrażeniu, które posiada z prawej i lewej strony typ logiczny. Dlatego w raporcie wyświetlane jest tylko podane wyrażenie, bez informacji o wartościach stojących po prawej i lewej stronie. Funkcja w głównej mierze wywoływana jest automatycznie przez makro
mst_assert
. Uruchomienie funkcji powoduje przydzielenie pamięci dla polaMST_FUNCTION.ErrorMessage
i zapisanie w nim treści błędu, tworzonego z przekazanych parametrów. Przydzieloną pamięć należy zwolnić samodzielnie po wykorzystaniu danych, zapisanych w zmiennej.Parametry: - info – Struktura zawierająca informacje o funkcji testującej.
- exp – Wyrażenie w którym wystąpił błąd.
- file – Plik w którym wystąpił błąd.
- line – Linia w której wystąpił błąd w pliku.
Zwraca: Kod błędu lub wartość 0.
-
int
mst_report_cs
(MST_FUNCTION *info, const char *file, int line, const char *a, const char *b)¶ Tworzenie raportu o błędzie, spowodowanym przez porównanie dwóch przekazanych ciągów znaków, zawierających znaki składające się z jednego lub wielu bajtów. Funkcja w głównej mierze wywoływana jest automatycznie przez makra
mst_assert_cs
orazmst_asser_mbs
. Uruchomienie funkcji powoduje przydzielenie pamięci dla polaMST_FUNCTION.ErrorMessage
i zapisanie w nim treści błędu, tworzonego z przekazanych parametrów. Przydzieloną pamięć należy zwolnić samodzielnie po wykorzystaniu danych, zapisanych w zmiennej.Parametry: - info – Struktura zawierająca informacje o funkcji testującej.
- exp – Wyrażenie w którym wystąpił błąd.
- file – Plik w którym wystąpił błąd.
- line – Linia w której wystąpił błąd w pliku.
- a – Ciąg znaków znajdujący się po lewej stronie.
- b – Ciąg znaków znajdujący się po prawej stronie.
Zwraca: Kod błędu lub wartość 0.
-
int
mst_report_wcs
(MST_FUNCTION *info, const char *file, int line, const wchar_t *a, const wchar_t *b)¶ Tworzenie raportu o błędzie, spowodowanym przez porównanie dwóch przekazanych ciągów znaków, zawierających znaki o typie
wchar_t
o rozmiarze 2 lub 4 bajtów. Funkcja w głównej mierze wywoływana jest automatycznie przez makromst_assert_wcs
. Uruchomienie funkcji powoduje przydzielenie pamięci dla polaMST_FUNCTION.ErrorMessage
i zapisanie w nim treści błędu, tworzonego z przekazanych parametrów. Przydzieloną pamięć należy zwolnić samodzielnie po wykorzystaniu danych, zapisanych w zmiennej.Parametry: - info – Struktura zawierająca informacje o funkcji testującej.
- exp – Wyrażenie w którym wystąpił błąd.
- file – Plik w którym wystąpił błąd.
- line – Linia w której wystąpił błąd w pliku.
- a – Ciąg znaków znajdujący się po lewej stronie.
- b – Ciąg znaków znajdujący się po prawej stronie.
Zwraca: Kod błędu lub wartość 0.
2.3. Makra¶
-
void
mst_prepare
(MST_FUNCTION *info)¶ Przygotowuje strukturę danych w funkcji do użycia w makrach. Makro to należy wywołać w funkcji testującej bezpośrednio po deklaracjach wykorzystywanych zmiennych. Przy okazji sprawdza, czy struktura przekazana w parametrze jest poprawna.
Przykład użycia makra:
int test_function( MST_FUNCTION *info ) { int x, y, z; /* makro wywoływane zaraz po deklaracjach */ mst_prepare( info ); for( x = 5; y = 1, z = 0; z < 10; ++z ) mst_assert( x + y == x++ + ++y - 1 ); return MSEC_OK; }
Parametry: - info – Struktura zawierająca informacje o funkcji testującej.
-
int
mst_assert
(expression exp)¶ Makro sprawdzające wartość wyrażenia podanego w argumencie. Makro zwiększa wartość pola
MST_FUNCTION.PassedAsserts
w przypadku gdy wyrażenie podane w argumencie okaże się prawdziwe. Gdy wyrażenie okaże się fałszywe, makro natychmiast zakończy funkcję w której zostało wywołane. Wywoływana funkcja raportowania powinna zwracać ilość znaków zapisanych w zmiennejMST_FUNCTION.ErrorMessage
, dlatego funkcja w której makro jest wywoływane, musi zwracać typint
. Asercja ta działa dobrze w przypadku sprawdzania poprawności zmiennych o typie logicznym czy przyrównywania wartość do NULL. W pozostałych przypadkach informacje mogą się okazać zbyt mało szczegółowe. Makro wywołuje funkcje raportowaniamst_report()
, automatycznie uzupełniając potrzebne argumenty.Pełny przykład użycia makra:
int test_function( MST_FUNCTION *info ) { bool istrue = TRUE; mst_prepare( info ); /* ta asercja będzie w porządku */ mst_assert( istrue == TRUE ); /* ta już nie, funkcja powinna zwrócić błąd */ mst_assert( istrue == FALSE ); return MSEC_OK; } int main( int argc, char **argv ) { MST_FUNCTION tfunc = { MST_LASTRECORD }; int ercode = test_function( &tfunc ); printf( "Error code: 0x%X\nMessage:\n-----------------\n%s\n", ercode, tfunc.ErrorMessage ); return 0; }
Przykładowe wyjście:
Error code: 0x2A Message: ----------------- # Error in test.c on line 14 # istrue == 0
Parametry: - exp – Wyrażenie do sprawdzenia.
Zwraca: Kod błędu zwracany przez funkcję raportu lub nic.
-
int
mst_assert_sint
(expression left, operator compare, expression right)¶ Makro sprawdzające wartość wyrażenia formułowanego z podanych argumentów. Wersja dla liczb całkowitych, pozwalająca na wyświetlenie wartości lewej i prawej strony wyrażenia. Makro zwiększa wartość pola
MST_FUNCTION.PassedAsserts
w przypadku gdy wyrażenie okaże się prawdziwe. W przeciwnym wypadku makro natychmiast zakończy funkcję w której zostało wywołane. Wywoływana funkcja raportowania powinna zwracać ilość znaków zapisanych w zmiennejMST_FUNCTION.ErrorMessage
, dlatego funkcja w której makro jest wywoływane, musi zwracać typint
. Makro wywołuje funkcje raportowaniamst_report_sint()
, automatycznie uzupełniając potrzebne argumenty.Pełny przykład użycia makra:
int test_function( MST_FUNCTION *info ) { mst_prepare( info ); /* ta asercja będzie w porządku */ mst_assert_sint( 2 + 2, ==, 4 ); /* ta już nie, funkcja powinna zwrócić błąd */ mst_assert_sint( 2 + 2, ==, 5 ); return MSEC_OK; } int main( int argc, char **argv ) { MST_FUNCTION tfunc = { MST_LASTRECORD }; int ercode = test_function( &tfunc ); printf( "Error code: 0x%X\nMessage:\n-----------------\n%s\n", ercode, tfunc.ErrorMessage ); return 0; }
Przykładowe wyjście:
Error code: 0x42 Message: ----------------- # Error in test.c on line 11 # 2 + 2 == 5 # Where: L = 4 and R = 5
Parametry: - left – Lewa strona wyrażenia.
- compare – Operator, stojący pomiędzy prawą a lewą stroną.
- right – Prawa strona wyrażenia.
Zwraca: Kod błędu zwracany przez funkcję raportu lub nic.
-
int
mst_assert_uint
(expression left, operator compare, expression right)¶ Makro sprawdzające wartość wyrażenia formułowanego z podanych argumentów. Wersja dla liczb naturalnych, pozwalająca na wyświetlenie wartości lewej i prawej strony wyrażenia. Makro zwiększa wartość pola
MST_FUNCTION.PassedAsserts
w przypadku gdy wyrażenie okaże się prawdziwe. W przeciwnym wypadku makro natychmiast zakończy funkcję w której zostało wywołane. Wywoływana funkcja raportowania powinna zwracać ilość znaków zapisanych w zmiennejMST_FUNCTION.ErrorMessage
, dlatego funkcja w której makro jest wywoływane, musi zwracać typint
. Makro wywołuje funkcje raportowaniamst_report_uint()
, automatycznie uzupełniając potrzebne argumenty.Pełny przykład użycia makra:
int test_function( MST_FUNCTION *info ) { ullong max = ULLONG_MAX; mst_prepare( info ); /* ta asercja będzie w porządku */ mst_assert_uint( max - 3, ==, max - 3 ); /* ta już nie, funkcja powinna zwrócić błąd */ mst_assert_uint( max - 3, ==, max - 4 ); return MSEC_OK; } int main( int argc, char **argv ) { MST_FUNCTION tfunc = { MST_LASTRECORD }; int ercode = test_function( &tfunc ); printf( "Error code: 0x%X\nMessage:\n-----------------\n%s\n", ercode, tfunc.ErrorMessage ); return 0; }
Przykładowe wyjście:
Error code: 0x70 Message: ----------------- # Error in test.c on line 14 # max - 3 == max - 4 # Where: L = 18446744073709551612 and R = 18446744073709551611
Parametry: - left – Lewa strona wyrażenia.
- compare – Operator, stojący pomiędzy prawą a lewą stroną.
- right – Prawa strona wyrażenia.
Zwraca: Kod błędu zwracany przez funkcję raportu lub nic.
-
int
mst_assert_float
(expression left, operator compare, expression right)¶ Makro sprawdzające wartość wyrażenia formułowanego z podanych argumentów. Wersja dla liczb zmiennoprzecinkowych, pozwalająca na wyświetlenie wartości lewej i prawej strony wyrażenia. Makro zwiększa wartość pola
MST_FUNCTION.PassedAsserts
w przypadku gdy wyrażenie okaże się prawdziwe. W przeciwnym wypadku makro natychmiast zakończy funkcję w której zostało wywołane. Wywoływana funkcja raportowania powinna zwracać ilość znaków zapisanych w zmiennejMST_FUNCTION.ErrorMessage
, dlatego funkcja w której makro jest wywoływane, musi zwracać typint
. Makro wywołuje funkcje raportowaniamst_report_float()
, automatycznie uzupełniając potrzebne argumenty.Pełny przykład użycia makra:
int test_function( MST_FUNCTION *info ) { float fnum = 0.2f; mst_prepare( info ); /* ta asercja będzie w porządku */ mst_assert_float( fnum, ==, 0.2f ); /* ta już nie, funkcja powinna zwrócić błąd */ mst_assert_float( fnum - 0.1f, ==, 0.2f ); return MSEC_OK; } int main( int argc, char **argv ) { MST_FUNCTION tfunc = { MST_LASTRECORD }; int ercode = test_function( &tfunc ); printf( "Error code: 0x%X\nMessage:\n-----------------\n%s\n", ercode, tfunc.ErrorMessage ); return 0; }
Przykładowe wyjście:
Error code: 0x59 Message: ----------------- # Error in main.c on line 14 # fnum - 0.1f == 0.2f # Where: L = 0.100000 and R = 0.200000
Parametry: - left – Lewa strona wyrażenia.
- compare – Operator, stojący pomiędzy prawą a lewą stroną.
- right – Prawa strona wyrażenia.
Zwraca: Kod błędu zwracany przez funkcję raportu lub nic.
-
int
mst_assert_cs
(const char *left, const char *right)¶ Makro pozwalające na porównanie dwóch ciągów znaków, zawierających znaki jednobajtowe. Ciągi porównywane są funkcją
strcmp
. Makro zwiększa wartość polaMST_FUNCTION.PassedAsserts
w przypadku gdy wyrażenie okaże się prawdziwe. W przeciwnym wypadku makro natychmiast zakończy funkcję w której zostało wywołane. Wywoływana funkcja raportowania powinna zwracać ilość znaków zapisanych w zmiennejMST_FUNCTION.ErrorMessage
, dlatego funkcja w której makro jest wywoływane, musi zwracać typint
. Makro wywołuje funkcje raportowaniamst_report_cs()
, automatycznie uzupełniając potrzebne argumenty.Pełny przykład użycia makra:
int test_function( MST_FUNCTION *info ) { const char *str = "The quick brown fox jumps over the lazy dog."; mst_prepare( info ); /* ta asercja będzie w porządku */ mst_assert_cs( str, "The quick brown fox jumps over the lazy dog." ); /* ta już nie, funkcja powinna zwrócić błąd */ mst_assert_cs( str, "The quick brown fox jumps over the lazy cat." ); return MSEC_OK; } int main( int argc, char **argv ) { MST_FUNCTION tfunc = { MST_LASTRECORD }; int ercode = test_function( &tfunc ); printf( "Error code: 0x%X\nMessage:\n-----------------\n%s\n", ercode, tfunc.ErrorMessage ); return 0; }
Przykładowe wyjście:
Error code: 0xA7 Message: ----------------- # Error in main.c on line 19 # Function strcmp( L, R ) failed... # L = The quick brown fox jumps over the lazy dog. # R = The quick brown fox jumps over the lazy cat.
Parametry: - left – Ciąg znaków po lewej stronie.
- right – Ciąg znaków po prawej stronie.
Zwraca: Kod błędu zwracany przez funkcję raportu lub nic.
-
int
mst_assert_mbs
(const char *left, const char *right)¶ Makro pozwalające na porównanie dwóch ciągów znaków, zawierających znaki jedno lub kilku bajtowe. Na chwilę obecną makro to jest tylko aliasem makra o nazwie
mst_assert_cs
. Warto jednak używać go do sprawdzania ciągów wielobajtowych znaków.Parametry: - left – Ciąg znaków po lewej stronie.
- right – Ciąg znaków po prawej stronie.
Zwraca: Kod błędu zwracany przez funkcję raportu lub nic.
-
int
mst_assert_wcs
(const wchar_t *left, const wchar_t *right)¶ Makro pozwalające na porównanie dwóch ciągów znaków, zawierających znaki o typie
wchar_t
. Ciągi porównywane są funkcjąwcscmp
. Makro zwiększa wartość polaMST_FUNCTION.PassedAsserts
w przypadku gdy wyrażenie okaże się prawdziwe. W przeciwnym wypadku makro natychmiast zakończy funkcję w której zostało wywołane. Wywoływana funkcja raportowania powinna zwracać ilość znaków zapisanych w zmiennejMST_FUNCTION.ErrorMessage
, dlatego funkcja w której makro jest wywoływane, musi zwracać typint
. Makro wywołuje funkcje raportowaniamst_report_wcs()
, automatycznie uzupełniając potrzebne argumenty.Pełny przykład użycia makra:
int test_function( MST_FUNCTION *info ) { const wchar_t *str = L"Чушь: гид вёз кэб цапф, юный жмот съел хрящ."; mst_prepare( info ); /* ta asercja będzie w porządku */ mst_assert_wcs( str, L"Чушь: гид вёз кэб цапф, юный жмот съел хрящ." ); setlocale( LC_ALL, "" ); /* ta już nie, funkcja powinna zwrócić błąd */ mst_assert_wcs( str, L"Чушь: гид вёз кэб цапф, жмот съел хрящ." ); return MSEC_OK; } int main( int argc, char **argv ) { MST_FUNCTION tfunc = { MST_LASTRECORD }; int ercode = test_function( &tfunc ); printf( "Error code: 0x%X\nMessage:\n-----------------\n%s\n", ercode, tfunc.ErrorMessage ); return 0; }
Przykładowe wyjście:
Error code: 0xE0 Message: ----------------- # Error in main.c on line 23 # Function wcscmp( L, R ) failed... # L = Чушь: гид вёз кэб цапф, юный жмот съел хрящ. # R = Чушь: гид вёз кэб цапф, жмот съел хрящ.
Parametry: - left – Ciąg znaków po lewej stronie.
- right – Ciąg znaków po prawej stronie.
Zwraca: Kod błędu zwracany przez funkcję raportu lub nic.
-
part
MST_STRINGIFY
(literal func)¶ Makro wstawiające w miejsce wystąpienia wskazanie na funkcję oraz jej nazwę w postaci ciągu znaków. Makro jest częścią inicjalizacji struktury
MST_FUNCTION
i tylko wtedy powinno być stosowane. Jego użycie upraszcza nieco kod w przypadku funkcji o długich nazwach.Przykład użycia makra:
/* ta inicjalizacja */ MST_FUNCTION test1 = { MST_STRINGIFY(some_test_function), "desc", NULL }; /* jest równoznaczna z tą */ MST_FUNCTION test2 = { some_test_function, "some_test_function", "desc", NULL };
Parametry: - func – Nazwa funkcji zamieniana na ciąg znaków.
Zwraca: Dwa pierwsze pola podczas inicjalizacji struktury.
-
MST_LASTRECORD
¶ Makro pozwalające na szybką inicjalizację ostatniego rekordu testu dla struktury zestawu. Wystarczy użyć tego makra, zamiast wpisywać dla każdego pola wartość NULL. Makro przeznaczone tylko do inicjalizacji struktury
MST_FUNCTION
wewnątrzMST_SUITE
.Przykład użycia makra:
/* ta inicjalizacja */ MST_FUNCTION test1 = { MST_LASTRECORD }; /* jest równoznaczna z tą */ MST_FUNCTION test2 = { NULL, NULL, NULL, NULL, 0, NULL };
2.4. Pokrycie kodu¶
Pokrycie kodu z punktu widzenia tworzenia testów jednostkowych jest jedną z ważniejszych kwestii, które należy poruszyć. Tworzenie statystyk pokrycia kodu, pozwala na wykrycie miejsc, do których program nie zdołał dojść w trakcie działania. Metoda ta jest więc stosowana do wykrywania zarówno starego kodu, który od dawna już za nic nie odpowiada, zajmując niepotrzebnie miejsce w plikach, jak i również miejsc, do których napisany test jednostkowy nie zdołał dojść. Dzięki temu można dopisać kod uwzlgędniający przypadek, który przechodzi przez daną część kodu testowanego. Do tworzenia informacji o pokryciu kodu potrzebne jest specjalne narzędzie, przeznaczone do tego celu. Różne systemy oferują różne narzędzia, które są uzależnione od użytego kompilatora.
2.4.1. GCOV¶
Systemy z rodziny Unix (którym jest de facto Linux) udostępniają narzędzie gcov
, działające wraz z kompilatorem gcc
.
Aby całość mogła współgrać ze sobą, program należy skompilować wraz z odpowiednimi parametrami, które umożliwią dokładne
wykrycie linii, które są aktywowane podczas działania programu.
Poniższy wycinek kodu przedstawia przykład kompilacje testu w gcc
:
gcc "source.c" "test_source.c" -o "program.out"
-g # włącza debugowanie
-O0 # wyłącza optymalizację kodu przez kompilator
-Wall # włącza raportowanie wszystkich błędów
-fprofile-arcs # dodaje możliwość analizy kodu
-ftest-coverage # tworzy pliki potrzebne przez gcov
Jak można zauważyć, kompilator, otrzymując odpowiednie argumenty, pozwala przygotować kompilowany program do użycia gcov
.
Po poprawnej kompilacji dla każdego pliku źródłowego utworzone zostaną pliki o rozszerzeniu .gcda, które będą potrzebne
podczas uruchamiania narzędzia gcov
:
./program.out
gcov "source.c" "test_source.c"
Przed uruchomieniem narzędzia gcov
, należy uruchomić skompilowany przed chwilą program.
Spowoduje to wygenerowanie danych, które gcov
będzie w stanie przetworzyć i wygenerować odpowiedni raport.
Uruchomienie polecenia gcov
wymaga podania plików dla których statystyki będą utworzone.
Po uruchomieniu narzędzia w konsoli wyświetlą się informacje o pokryciu kodu:
Checking code coverage...
File 'source.c'
Lines executed:95.14% of 247
Creating 'array.c.gcov'
File 'test_source.c'
Lines executed:99.80% of 494
Creating 'array_test.c.gcov'
Dane przedstawiają procent wykonania kodu w pliku. Plik wygenerowany przez narzędzie o rozszerzeniu .gcov zawiera informacje o przebiegu programu. Precyzując, można tam znaleźć statystyki na temat tego, ile razy dana linia programu została wykonana, oraz które z linii nie zostały wykonane w uruchomionej instancji aplikacji. Przykład zawartości pliku prosto ze źródeł testowych modułu tablicy dynamicznej:
-: 312: /* sprawdź czy nowy element się zmieści */
9: 313: if( array->Length + size > array->Capacity )
-: 314: {
-: 315: int ercode;
3: 316: if( (ercode = ms_array_realloc_min(array, array->Length + size)) )
#####: 317: return ercode;
-: 318: }
6: 319: else if( !size )
1: 320: SETERRNOANDRETURN( MSEC_INVALID_ARGUMENT );
Jak można zauważyć, linie które nie zostały wykonane, oznaczone są kratkami. Linie zawierające znak minusa to linie nieistotne dla przebiegu programu. Pozostałe zawierają liczbę, która przedstawia ile razy program przeszedł przed dane miejsce.
2.4.2. VSPerf¶
Program Microsoft Visual Studio zawiera narzędzia do generowania statystyk pokrycia kodu. Tutaj jest jednak haczyk, gdyż choć każda wersja potrafi je generować, to już nie każda potrafi je otworzyć. Odczytanie wygenerowanych statystyk wiąże się z zainstalowaniem odpowiedniej wersji produktu, posiadającego w swym zbiorze bibliotekę Microsoft.VisualStudio.Coverage.Analysis. Aktualnie, wersje zawierające ten moduł, oznaczone są nazwą Enterprise. Oczywiście w przypadku systemu Windows zawsze można użyć metody, opisanej w rozdziale GCOV po wcześniejszym zainstalowaniu środowiska MinGW lub dużo prostrzej w użyciu metody z rozdziału OpenCppCoverage.
Zestaw narzędzi VSPerf
został utworzony na potrzeby zbierania danych o wydajności aplikacji. Można dzięki
nim wygenerować również dane dotyczące pokrycia kodu przez program. Aby to zrobić, należy oczywiście wcześniej
odpowiednio skompilować program. Wszystkie funkcje zamieszczone poniżej należy wywoływać w konsoli zawierającej
ustawione ścieżki w zmiennej środowiskowej. Skrypt Visual Studio Command Prompt dołączony do menu podczas
instalacji Visual Studio ustawia je automatycznie.
Poniżej przedstawiony został przykład kompilacji testu w cl
:
cl "source.c" "test_source.c" /Fe"program.exe"
/Zi # włącza debugowanie
/Od # wyłącza optymalizację kodu przez kompilator
/Wall # włącza raportowanie wszystkich błędów
/link
/Profile # dodaje możliwość analizy kodu
/OUT:NOREF # pozostawia funkcje, które nie są w programie
Tak skompilowany program powinien dać się uruchomić. Wcześniej jednak, należy uruchomić narzędzie do instrumentacji
plików binarnych, VSInstr
, po czym użyć narzędzia VSPerfMon
.
Kolejność wywoływanych poleceń jest następująca:
VSInstr /coverage "program.exe"
start VSPerfMon /coverage /output:"program.coverage"
program.exe
VSPerfCmd /shutdown
Ta sekwencja instrukcji otwiera nowe okno linii poleceń.
Sytuacja ta jest uciążliwa z punktu widzenia przetwarzania instrukcji sekwencyjnie w plikach skryptowych, gdyż
program wykona się zanim narzędzie zostanie przygotowane do działania.
Aby temu zapobiec, zamiast VSPerfMon
można użyć VSPerfCMD
:
VSPerfCmd /start:coverage /output:"program.coverage"
Uruchomienie powyższej sekwencji z użyciem narzędzia VSPerfMon
lub VSPerfCMD
utworzy plik o rozszerzeniu .coverage,
który należy uruchomić w programie Visual Studio.
To jest właśnie ten moment w którym do uruchomienia pliku i wyświetlenia wyników należy posiadać odpowiednią wersję aplikacji.
2.4.3. OpenCppCoverage¶
Ta darmowa aplikacja konsolowa potrafi w pełni zastąpić narzędzie VSPerfMon
, generując statystyki pokrycia
kodu do plików o rozszerzeniu .html. Program może również eksportować dane do plików .xml w takim układzie
w jakim tworzy je aplikacja Cobertura.
Możliwy jest również eksport do plików binarnych, które mogą być później łączone przez program w jeden plik wynikowy
zawierający wszystkie dostępne w nich statystyki.
Tworzenie i łączenie plików binarnych używane jest przez bibliotekę Moss, dzięki czemu wszystkie testowane
osobno moduły zostają połączone w jeden plik, zawierający statystyki pokrycia kodu dla całej biblioteki.
Przed użyciem programu z linii poleceń, należy dodać ścieżkę aplikacji do zmiennej środowiskowej PATH.
Można to oczywiście ominąć i zamiast samej nazwy programu podawać jego pełną ścieżkę podczas wywoływania.
Program wymaga, aby aplikacja, do której generowane będą statystyki pokrycia kodu, była już skompilowana.
Przykład kompilacji z użyciem kompilatora cl
opisany został w rozdziale VSPerf.
Aplikacja w trakcie działania wykorzystuje plików .pdb, generowane przez kompilator cl
ustawiony na kompilację w trybie odpluskiwania.
Jest to naprawdę dobry odpowiednik narzędzia gcov
dla kompilatora cl
.
Program wywołać można w następujący sposób:
OpenCppCoverage --modules "directory" --sources "directory"
--export_type html:program # typ eksportu, tutaj HTML
-- "program.exe" # nazwa programu do uruchomienia
Powyższa komenda wpisana w linii poleceń zapisze do folderu o nazwie program statystyki pokrycia kodu w formacie .html
dla aplikacji o nazwie program.exe. Bardzo ważne są tutaj parametry --modules
oraz --sources
, które pozwalają
na przetwarzanie tylko plików należących do programu. Dotyczy to zarówno plików .pdb jak i plików źródłowych programu.
Przykład łączenia statystyk pokrycia kodu generowanych przez aplikację:
# pierwszy moduł
OpenCppCoverage --modules "directory" --sources "directory"
--export_type binary:module1.bin
-- "module1.exe"
# drugi moduł
OpenCppCoverage --modules "directory" --sources "directory"
--export_type binary:module2.bin
-- "module2.exe"
# łączenie plików binarnych
OpenCppCoverage
--input_coverage "module1.bin"
--input_coverage "module2.bin"
--export_type html:module
Dzięki temu zabiegowi, program wygeneruje jedną stronę zawierającą statystyki pokrycia kodu dla wszystkich modułów. Prezentacja pokrycia kodu zapisana w plikach .html, przedstawia odpowiednio pokolorowane linie. Te, które nie zostały wykorzystane w trakcie działania programu oznaczone są na czerwono, pozostałe zaś na zielono.
2.5. Wycieki pamięci¶
Jedną z najważniejszych kwestii w trakcie tworzenia oprogramowania jest wykrycie wszelkich wycieków pamięci. Tworzą się one w przypadku, gdy ilość zwalnianej pamięci jest mniejsza niż ilość pamięci przydzielanej. Język C jest jednym z języków, w których zwalnianie pamięci nie jest wykonywane automatycznie. Każda funkcja przydzielająca zasoby musi posiadać odpowiednik, który te zasoby zwalnia. Wycieki pamięci są jednymi z najgorszych błędów do wykrycia przez programistę, dlatego do ich wyszukiwania powstały odpowiednie programy.
2.5.1. Valgrind¶
Podstawowym narzędziem do sprawdzania wycieków pamięci w systemach z rodziny Linux, jest program o nazwie Valgrind, choć oczywiście jest możliwość uruchomienia go na systemie Windows. Przed użyciem narzędzia Valgrind, należy skompilować w trybie odpluskiwania program, który będzie sprawdzany. Kompilację można wykonać zgodnie ze wskazówkami podanymi w rozdziałach GCOV oraz VSPerf.
Uruchomienie narzędzia sprowadza się do podania jego nazwy wraz z nazwą programu:
valgrind --leak-check=yes ./test
Po nazwie programu, w tym wypadku ./test
, można podać dodatkowo argumenty przekazywane bezpośrednio do programu.
W zasadzie to wszystko, jeżeli chodzi o sprawdzanie wycieków pamięci narzędziem Valgrind.
Szczegóły dotyczące uruchamiania programu można znaleźć na stronie producenta.
Zakończenie programu powinien wieńczyć następujący komunikat:
==3168== HEAP SUMMARY:
==3168== in use at exit: 0 bytes in 0 blocks
==3168== total heap usage: 41 allocs, 41 frees, 10,772 bytes allocated
==3168==
==3168== All heap blocks were freed -- no leaks are possible
==3168==
==3168== For counts of detected and suppressed errors, rerun with: -v
==3168== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Można z niego odczytać, że aplikacja nie ma żadnych wycieków pamięci, więc wszystko jest w porządku. W przypadku gdyby wycieki pamięci się pojawiły, to w powyższym komunikacie wypisane będą wszystkie błędy, które Valgrind zdołał wychwycić. Oczywiście możliwe są też fałszywe alarmy (false positives), ale lepiej dmuchać na zimne.
2.5.2. Dr. Memory¶
Bardzo rozbudowanym i zarazem prostym w obsłudze narzędziem do wykrywania wycieków pamięci jest Dr. Memory, działający zarówno na systemie Microsoft Windows jak i na systemach z rodziny Linux. Aby zaprzęgnąć narzędzie do działania, należy skompilować program w trybie odpluskiwania zgodnie z instrukcjami podanymi w rozdziałach GCOV oraz VSPerf. Dzięki temu Dr. Memory będzie mógł wykryć potencjalne błędy związane z zarządzaniem pamięcią.
Uruchomienie narzędzie jest banalnie proste:
drmemory -- test.exe
Gdzie test.exe
to nazwa testowanego programu.
Oczywiście po jego nazwie można podać dodatkowe argumenty, które zostaną przekazane bezpośrednio do niego.
W zasadzie to tyle, jeżeli chodzi o wykrywanie wycieków pamięci narzędziem Dr. Memory.
Szczegóły dotyczące uruchamiania programu można znaleźć na stronie producenta.
Zakończenie programu powinno wyglądać mniej więcej tak:
NO ERRORS FOUND:
0 unique, 0 total unaddressable access(es)
0 unique, 0 total uninitialized access(es)
0 unique, 0 total invalid heap argument(s)
0 unique, 0 total GDI usage error(s)
0 unique, 0 total handle leak(s)
0 unique, 0 total warning(s)
0 unique, 0 total, 0 byte(s) of leak(s)
0 unique, 0 total, 0 byte(s) of possible leak(s)
ERRORS IGNORED:
21 potential error(s) (suspected false positives)
(details: %APPDATA%\Dr. Memory\DrMemory-array.exe.6304.000\potential_errors.txt)
119 unique, 239 total, 39196 byte(s) of still-reachable allocation(s)
(re-run with "-show_reachable" for details)
Details: %APPDATA%\Dr. Memory\DrMemory-array.exe.6304.000\results.txt
Jak można zauważyć powyżej, program został wykonany poprawnie i nie posiada żadnych wycieków pamięci. W przypadku gdy podczas działania, program wychwyci jiekolwiek błędy, to zostaną one wypisane w powyższym komunikacie w konsoli. Narzędzie Dr. Memory wykrywa również fałszywe alarmy, te, należące do bibliotek systemowych lub używanych w programie bibliotek wstawiane są od razu w sekcję ERRORS IGNORED.
2.6. Testy biblioteki Moss¶
Biblioteka Moss posiada zaprogramowane testy, oparte na module MSTest.
Każdy moduł posiada swój test, dzięki czemu można je testować osobno lub wszystkie na raz.
Uruchomienie któregokolwiek z utworzonych testów jest banalnie proste.
W folderze tst/run
znajdują się skrypty odpowiadające za kompilacje testu i uruchomienie narzędzia
pozwalającego na wyświetlenie statystyk pokrycia kodu.
Skrypty utworzone zsotały z myślą o dwóch kompilatorach, gcc
oraz cl
.
Dla systemu Windows są to pliki o rozszerzeniu .bat zaś dla systemu Linux, pliki o rozszerzeniu .sh.
Aby uruchomić test dla wybranego modułu (tutaj Array), wystarczy wpisać:
./array.sh # dla systemu Linux
array.bat # dla systemu Windows
Można również uruchomić wszystkie testy po wpisaniu:
./all.sh # dla systemu Linux
all.bat # dla systemu Windows
Po uruchomieniu testu lub wszystkich testów na raz, wyświetlone zostanie pokrycie kodu. Błąd w jednym teście przerywa wykonywanie pozostałych, zarówno w wybranym zestawie dla modułu jak i również podczas uruchamiania testów dla wszystkich modułów. Tak więc w przypadku uruchomienia wszystkich testów, podczas wystąpienia błędu w pierwszym module, pozostałe nie będą testowane do momentu naprawienia wykrytego błędu. Testy nie sprawdzają programu pod kątem wycieków pamięci.
Dane generowane przez skrypty zapisywane są do plików w folderze tst/gen
.
Po uruchomieniu skryptu to właśnie tam znajdą się pliki zawierające informacje o pokryciu kodu
oraz skompilowane pliki zawierające testy, które można uruchomić samodzielnie.
Dzięki temu każda zmiana w module może być przetestowana pod kątem poprawności z zapisanym
w dokumentacji standardem.