2013-07-01 18 views
15

Piszę pakiet testów jednostkowych dla biblioteki kodu źródłowego, która zawiera static_assert s. Chcę zapewnić te te static_assert s nie więcej i nie mniej niż one są pożądane, w zakresie projektowania. Chciałbym móc je przetestować.Jak napisać testowanie testów static_assert?

Mógłbym oczywiście dodać uncompilable testy jednostkowe interfejsu, które powodują static assert s zostać naruszone przez kompleksowej gamy środków i skomentować lub #if 0 je wszystkie, z mojego osobistego zapewniania użytkownikowi, że jeśli w ogóle z nich nie są komentowane, wtedy będą obserwować, że biblioteka nie kompiluje się.

Ale to byłoby raczej śmieszne. Zamiast tego chciałbym mieć pewną aparaturę, która w kontekście pakietu testów jednostkowych zastąpi a static_assert z ekscytującym prowokacją wyjątku środowiska wykonawczego, że struktura testowa może przechwytywać i generować raporty: ten kod miałby static_assert ed w prawdziwym kompilacji.

Czyżbym przeoczył jakiś rażący powód, dla którego byłby to głupi pomysł?

Jeśli nie, w jaki sposób można to zrobić? Makro aparatura to oczywiste podejście i nie wykluczam tego. Ale może również, i najlepiej, ze specjalizacją szablonu lub podejściem SFINAE?

+3

Moja osobista przeczucie, że statyczny twierdzi powinien złapać błędy w programowaniu * przed * przeprowadzasz nawet testy. Nie widzę natychmiast pilnego powodu, dla którego powinni oni zostać przetestowani ... –

+2

Co mówi Kerrek. Jeśli twoje środowisko kompilacji nie bierze "testów nie zbudowało" jako niepowodzenia, to jest problem. – Xeo

+1

Nie widzę lepiej niż tworzenie makra, które wyprowadza albo static_assert, albo wyjątek środowiska wykonawczego w trybie testowym. W przypadku metaprogramowania, może to sprawić, że przetestujesz swoje asercje kompilacji. –

Odpowiedz

10

Jak wydaje się być samotny korbowego w moim interesie w tej kwestii mam wygiętą się odpowiedź dla siebie, z plikiem nagłówkowym zasadzie jak ten:

exceptionalized_static_assert.h

#ifndef TEST__EXCEPTIONALIZE_STATIC_ASSERT_H 
#define TEST__EXCEPTIONALIZE_STATIC_ASSERT_H 

/* Conditionally compilable apparatus for replacing `static_assert` 
    with a runtime exception of type `exceptionalized_static_assert` 
    within (portions of) a test suite. 
*/ 
#if TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1 

#include <string> 
#include <stdexcept> 

namespace test { 

struct exceptionalized_static_assert : std::logic_error 
{ 
    exceptionalized_static_assert(char const *what) 
    : std::logic_error(what){}; 
    virtual ~exceptionalized_static_assert() noexcept {} 
}; 

template<bool Cond> 
struct exceptionalize_static_assert; 

template<> 
struct exceptionalize_static_assert<true> 
{ 
    explicit exceptionalize_static_assert(char const * reason) { 
     (void)reason; 
    } 
}; 


template<> 
struct exceptionalize_static_assert<false> 
{ 
    explicit exceptionalize_static_assert(char const * reason) { 
     std::string s("static_assert would fail with reason: "); 
     s += reason; 
     throw exceptionalized_static_assert(s.c_str()); 
    } 
}; 

} // namespace test 

// A macro redefinition of `static_assert` 
#define static_assert(cond,gripe) \ 
    struct _1_test \ 
    : test::exceptionalize_static_assert<cond> \ 
    { _1_test() : \ 
     test::exceptionalize_static_assert<cond>(gripe){}; \ 
    }; \ 
    _1_test _2_test 

#endif // TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1 

#endif // EOF 

Ten nagłówek jest uwzględniany tylko w zestawie testowym, a następnie spowoduje, że będzie widoczny jako definicja redefinicji static_assert widoczna tylko wtedy, gdy zestaw testów zostanie zbudowany z

`-DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1`  

Zastosowanie tego urządzenia może być zarysowane biblioteki zabawka Szablon:

my_template.h

#ifndef MY_TEMPLATE_H 
#define MY_TEMPLATE_H 

#include <type_traits> 

template<typename T> 
struct my_template 
{ 
    static_assert(std::is_pod<T>::value,"T must be POD in my_template<T>"); 

    explicit my_template(T const & t = T()) 
    : _t(t){} 
    // ... 
    template<int U> 
    static int increase(int i) { 
     static_assert(U != 0,"I cannot be 0 in my_template<T>::increase<I>"); 
     return i + U; 
    } 
    template<int U> 
    static constexpr int decrease(int i) { 
     static_assert(U != 0,"I cannot be 0 in my_template<T>::decrease<I>"); 
     return i - U; 
    } 
    // ... 
    T _t; 
    // ... 
}; 

#endif // EOF 

Spróbuj sobie wyobrazić, że kod jest wystarczająco duże i skomplikowane, że cię Nie możesz tego porzucić, po prostu wypytaj i wybierz static_assert s oraz , aby się upewnić, że wiesz, dlaczego one są i czy spełniają one ich celów projektowych. Zaufałeś testom regresyjnym.

Tutaj następnie jest zabawką regresji Zestaw testów dla my_template.h:

test.cpp

#include "exceptionalized_static_assert.h" 
#include "my_template.h" 
#include <iostream> 

template<typename T, int I> 
struct a_test_template 
{ 
    a_test_template(){}; 
    my_template<T> _specimen; 
    //... 
    bool pass = true; 
}; 

template<typename T, int I> 
struct another_test_template 
{ 
    another_test_template(int i) { 
     my_template<T> specimen; 
     auto j = specimen.template increase<I>(i); 
     //... 
     (void)j; 
    } 
    bool pass = true; 
}; 

template<typename T, int I> 
struct yet_another_test_template 
{ 
    yet_another_test_template(int i) { 
     my_template<T> specimen; 
     auto j = specimen.template decrease<I>(i); 
     //... 
     (void)j; 
    } 
    bool pass = true; 
}; 

using namespace std; 

int main() 
{ 
    unsigned tests = 0; 
    unsigned passes = 0; 

    cout << "Test: " << ++tests << endl;  
    a_test_template<int,0> t0; 
    passes += t0.pass; 
    cout << "Test: " << ++tests << endl;  
    another_test_template<int,1> t1(1); 
    passes += t1.pass; 
    cout << "Test: " << ++tests << endl;  
    yet_another_test_template<int,1> t2(1); 
    passes += t2.pass; 
#if TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1 
    try { 
     // Cannot instantiate my_template<T> with non-POD T 
     using type = a_test_template<int,0>; 
     cout << "Test: " << ++tests << endl; 
     a_test_template<type,0> specimen; 

    } 
    catch(test::exceptionalized_static_assert const & esa) { 
     ++passes; 
     cout << esa.what() << endl; 
    } 
    try { 
     // Cannot call my_template<T>::increase<I> with I == 0 
     cout << "Test: " << ++tests << endl; 
     another_test_template<int,0>(1); 
    } 
    catch(test::exceptionalized_static_assert const & esa) { 
     ++passes; 
     cout << esa.what() << endl; 
    } 
    try { 
     // Cannot call my_template<T>::decrease<I> with I == 0 
     cout << "Test: " << ++tests << endl; 
     yet_another_test_template<int,0>(1); 
    } 
    catch(test::exceptionalized_static_assert const & esa) { 
     ++passes; 
     cout << esa.what() << endl; 
    } 
#endif // TEST__EXCEPTIONALIZE_STATIC_ASSERT == 1 
    cout << "Passed " << passes << " out of " << tests << " tests" << endl; 
    cout << (passes == tests ? "*** Success :)" : "*** Failure :(") << endl; 
    return 0; 
} 

// EOF 

można skompilować test.cpp przynajmniej gcc 6.1, brzękiem 3.8 i opcja -std=c++14 lub VC++ 19.10.24631.0 i opcja /std:c++latest. Zrób to najpierw bez definiowania TEST__EXCEPTIONALIZE_STATIC_ASSERT (lub definiowania go jako 0). Następnie należy uruchomić i wyjście powinno być:

Test: 1 
Test: 2 
Test: 3 
Passed 3 out of 3 tests 
*** Success :) 

Jeśli następnie powtórzyć, ale skompilować z -DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1,

Test: 1 
Test: 2 
Test: 3 
Test: 4 
static_assert would fail with reason: T must be POD in my_template<T> 
Test: 5 
static_assert would fail with reason: I cannot be 0 in my_template<T>::increase<I> 
Test: 6 
static_assert would fail with reason: I cannot be 0 in my_template<T>::decrease<I> 
Passed 6 out of 6 tests 
*** Success :) 

Wyraźnie powtarzające kodowanie try/catch bloków w statycznej-dochodzić przypadków testowych jest nudny , ale w warunkach prawdziwego i godnego szacunku modelu testu jednostkowego można się spodziewać, że zapakuje on aparat do testowania wyjątków w celu generowania takich rzeczy poza zasięgiem wzroku. W googletest, na przykład, jesteś w stanie napisać jak się z:

TYPED_TEST(t_my_template,insist_non_zero_increase) 
{ 
    ASSERT_THROW(TypeParam::template increase<0>(1), 
     exceptionalized_static_assert); 
} 

teraz mogę wrócić do moich obliczeń daty Armagedonu :)

+0

'// Redefinicja makra 'static_assert'' ... ya, nie rób tego. Utwórz nowy token 'my_static_assert', który jest mapowany do' static_assert' w standardzie i 'my_strange_thing' w twoim testowym silniku. Nie musisz nadużywać języka, kiedy nie musisz ... – Yakk

+3

@Yakk Redefinicja makr będzie widoczna tylko w zestawie testów, ponieważ nagłówek definiujący go będzie tylko w nim zawarty i widoczny tylko wtedy, gdy test pakiet jest zbudowany z '-DTEST__EXCEPTIONALIZE_STATIC_ASSERT = 1'. Zaktualizowałam odpowiedź, aby usunąć wyraźną wartość . –

+0

@MikeKinghan Redefinicja makr nie działa (z moją wersją STL), niestety z powodu ograniczeń w makrach podczas używania argumentów szablonu, np. 'static_assert (std :: is_same < T1, T2 >," blabla ")'. Ten kod również pojawia się w standardowej bibliotece. Nie myślałem o tym wcześniej, ale jest to przełom w rozwiązaniu, które zasugerowałeś. Jak zasugerował Yakk, zdefiniowanie nowego tokena jest drogą do zrobienia w tym przypadku. Btw, wciąż świetne rozwiązanie. –