2015-04-27 16 views
6

W ramach testów jednostkowych chcę zapewnić pokrycie kodu testów. Celem jest umieszczenie gdzieś w kodzie makr, takich jak REQUIRE_TEST, i sprawdzenie, czy wszystkie zostały wywołane.Znajdź niezrealizowane wiersze kodu C++

void foo(bool b) { 
    if (b) { 
    REQUIRE_TEST 
    ... 
    } else { 
    REQUIRE_TEST 
    ... 
    } 
} 

void main() { 
    foo(true); 

    output_all_missed_REQUIRE_macros(); 
} 

Najlepiej, gdyby dane wyjściowe zawierały plik źródłowy i linię makra.

Mój początkowy pomysł był aby mieć makra tworzyć obiekty statyczne, które będą rejestrować się w pewnym mapie, a następnie sprawdzić, czy wszystkie z nich były nazywane

#define REQUIRE_TEST \ 
     do { \ 
      static ___RequiredTest requiredTest(__func__, __FILE__, __LINE__);\ 
      (void)requiredTest;\ 
      ___RequiredTest::increaseCounter(__func__, __FILE__, __LINE__);\ 
     } while(false) 

ale obiekt statyczne są tylko tworzone, gdy kod jest nazywany pierwszy raz. Mapa zawiera tylko funkcje, które są również zliczane w następnej linii - brakuje makr REQUIRE_TEST. __attribute__((used)) jest w tym przypadku ignorowany.

gcc ma ładny atrybut __attribute__((constructor)), ale widocznie zdecyduje się go ignorować, gdy umieszczone tutaj (po kodzie zamiast obiektu statycznego)

struct teststruct { \ 
    __attribute__((constructor)) static void bla() {\ 
    ___RequiredTest::register(__func__, __FILE__, __LINE__); \ 
    } \ 
};\ 

jak również dla

[]() __attribute__((constructor)) { \ 
    ___RequiredTest::register(__func__, __FILE__, __LINE__); \ 
};\ 

Jedynym obecnie mogę myśleć teraz: a) ręcznie (lub za pomocą skryptu) analizować kod poza zwykłą kompilacją (uargh) lub b) używać makra __COUNTER__ do liczenia makr - ale wtedy nie wiedziałbym, które konkretne makra REQUIRE_TEST były nie calle d ... (i wszystko zepsuje się, jeśli ktoś inny zdecyduje się również użyć makra __COUNTER__ ...)

Czy są jakieś przyzwoite rozwiązania tego problemu? czego mi brakuje? To byłoby miło mieć makro, które dołącza bieżącą linię i złożyć więc niektóre zmienna preprocesor kiedy to nazywa - ale to nie jest możliwe, prawda? Czy istnieją inne sposoby zarejestrowania czegoś, co ma być wykonane przed , wykonanym przed main(), które można wykonać w treści funkcji?

+0

Badanie ostrzeżeń i opcji wiersza poleceń dla kompilatora. Wiele kompilatorów ma możliwość identyfikowania "martwego kodu". –

+8

TL; DR; Dlaczego nie używasz wtyczki kodującej profilowanie zasięgu np. ['Gcov'] (https://gc.gnu.org/onlinedocs/gcc/Gcov.html) i analizujesz wyniki, np. przy użyciu ['lcov'] (http://ltp.sourceforge.net/coverage/lcov.php)? To niekoniecznie musi być zatwierdzone dla _dead code_, jeśli nie zostanie wykonane z twoich scenariuszy testowych. Narzędzie do statycznej analizy kodu może lepiej służyć do wyszukiwania takich martwych rzeczy. –

+0

@ πάνταῥεῖ 'gcov' jest bardzo szczegółowy - ale także w częściach kodu, w których tak naprawdę mnie to nie obchodzi. Umieszczenie znaczników takich jak "REQUIRE_TEST" pozwoli mi określić, które części mnie interesują. Byłaby to także inna zależność dla każdego, do kogo wysyłam moją bibliotekę - i wydaje mi się, że C++ jest w stanie wykonywać to, co chcę ... Gdyby któraś z moich prób zadziałała, miałbym wszystko, czego chciałem. Może coś takiego jak 'gcov' będzie jedynym rozwiązaniem w końcu - ale chciałbym wiedzieć, że to, co próbuję, nie jest możliwe przed" rezygnacją "i dodaniem innej zależności ... – example

Odpowiedz

1

Jest brzydki, ale proste podejście jest dla REQUIRE_TEST używać __LINE__ lub __COUNTER__ do skonstruowania nazwy plików wyjątkowy zakres statycznego obiektu, który go dotyczy, a co spowoduje błąd kompilatora, jeśli nie została uznana . Następnie musisz ręcznie zadeklarować wszystkie takie obiekty z góry, po jednym dla każdego REQUIRE_TEST - ale przynajmniej dostaniesz błąd kompilacji, jeśli tego nie zrobiłeś.

Hej, powiedziałem, że to było brzydkie!

2

Jak o tym:

#include <iostream> 

static size_t cover() { return 1; } 

#define COV() do { static size_t cov[2] __attribute__((section("cov"))) = { __LINE__, cover() }; } while(0) 

static void dump_cov() 
{ 
     extern size_t __start_cov, __stop_cov; 

     for (size_t* p = &__start_cov; p < &__stop_cov; p += 2) 
     { 
       std::cout << p[0] << ": " << p[1] << "\n"; 
     } 
} 

int main(int argc, char* argv[]) 
{ 
     COV(); 

     if (argc > 1) 
       COV(); 

     if (argc > 2) 
       COV(); 

     dump_cov(); 
     return 0; 
} 

Wyniki:

$ ./cov_test 
19: 1 
22: 0 
25: 0 

a:

$ ./cov_test x 
19: 1 
22: 1 
25: 0 

a:

$ ./cov_test x y 
19: 1 
22: 1 
25: 1 

Zasadniczo ustawiliśmy macierz pokrycia w sekcji o nazwie pamięć (oczywiście używaliśmy w tym celu mechanizmów GCC), którą zrzucamy po wykonaniu.

Polegamy na ciągłej inicjalizacji lokalnej statystyki wykonywanej przy starcie - która pobiera numery linii do tablicy pokrycia z flagą zasięgu ustawioną na zero - i przy inicjalizacji poprzez wywołanie funkcji do cover() wykonywane przy pierwszym wykonaniu instrukcji , który ustawia flagi zasięgu na 1 dla wykonanych linii. Nie jestem w 100% pewny, że wszystko to jest gwarantowane przez standard, ani przez które wersje standardu (skompilowałem z --std=c++11).

Wreszcie, budynek z '-O3' produkuje również poprawnych wyników (choć przyznane w innej kolejności):

$ ./a 
25: 0 
22: 0 
19: 1 

oraz:

$ ./a x 
25: 0 
22: 1 
19: 1 

i

$ ./a x y 
25: 1 
22: 1 
19: 1 
+0

Zgodnie ze standardem, kompilator jest może inicjować lokalne statystyki przed głównym - jeśli tego chce. Inicjowanie go za pomocą 0 i dodawanie kolejnej linii kodu, która zwiększa tę liczbę w makrze, powinno działać dobrze.Dzięki za pomysł iteracji nad tą sekcją niestandardową. Dodam również bieżącą nazwę pliku, ale to powinno być dokładnie to, co chciałem =) – example

+0

czy masz pojęcie, dlaczego daje to konflikt typu sekcji, gdy makro 'COV' jest używane w funkcji inline? – example

1

Jeremy dał mi dobry pomysł, aby przyjrzeć się bliżej sekcjom. Jego odpowiedź działa - ale tylko bez funkcji inline. Z kilkoma innymi badaniami udało mi się znaleźć następujące rozwiązanie, które jest zależne od GCC (ze względu na nazwę sekcji), ale bardziej elastyczne (i działa w funkcjach wbudowanych). Makro przedstawia się następująco:

#define REQUIRE_TEST \ 
    do { \ 
     struct ___a{ static void rt() {\ 
      ___RequiredTest::register_test(__FILE__, __LINE__);\ 
     } };\ 
     static auto ___rtp __attribute__((section(".init_array"))) = &___a::rt; \ 
     (void) ___rtp; \ 
     ___RequiredTest::increase_counter(__FILE__, __LINE__); \ 
    } while(false) 

Umieszczenie wskaźnika funkcji w sekcji .init_array faktycznie umieszcza go na liście funkcji inicjujących, które są wywołane przed głównym. W ten sposób można zapewnić, że lokalnie określona funkcja jest wywoływana przed główną.