2013-07-01 26 views
15

Tôi đang viết một bộ kiểm thử đơn vị cho thư viện mã nguồn có chứa static_assert s. Tôi muốn đảm bảo rằng các số static_assert không làm được nhiều hơn và không ít hơn chúng được mong muốn thực hiện theo các điều khoản thiết kế. Vì vậy, tôi muốn có thể kiểm tra chúng.Làm thế nào để viết các thử nghiệm runnable của static_assert?

tôi dĩ nhiên có thể thêm các xét nghiệm đơn vị uncompilable của giao diện mà gây ra s static assert bị vi phạm bởi một loạt đầy đủ các phương tiện, và bình luận hoặc #if 0 tất cả chúng ra, với cá nhân đảm bảo của tôi để cho người dùng biết nếu có trong số họ không được nhận xét, họ sẽ quan sát rằng thư viện không biên dịch.

Nhưng điều đó sẽ khá ngớ ngẩn. Thay vào đó, tôi muốn có một số thiết bị, trong bối cảnh của bộ kiểm tra đơn vị, thay thế a static_assert với ngoại lệ thời gian chạy tương đương, rằng khung kiểm tra có thể bắt và báo cáo có hiệu lực: Mã này sẽ có static_assert ed trong bản dựng thực.

Tôi có thấy một số lý do rõ ràng tại sao điều này sẽ là ý tưởng không?

Nếu không, làm thế nào nó có thể được thực hiện? Thiết bị macro là một cách tiếp cận rõ ràng và tôi không loại trừ nó. Nhưng cũng có thể, và tốt hơn là, với một chuyên môn về mẫu hoặc cách tiếp cận SFINAE?

+3

cảm giác ruột cá nhân của tôi là tĩnh khẳng định nên bắt lỗi lập trình * trước * bạn thậm chí chạy bất kỳ thử nghiệm nào. Tôi không thấy ngay lập tức một lý do bức xúc tại sao họ nên tự kiểm tra ... –

+2

Những gì Kerrek nói. Nếu môi trường xây dựng của bạn không có "kiểm tra không xây dựng" như là một thất bại, đó là một vấn đề. – Xeo

+1

Tôi không thấy tốt hơn là tạo macro xuất ra static_assert hoặc ngoại lệ thời gian chạy trong chế độ thử nghiệm. Trong trường hợp lập trình meta, nó có thể tạo cảm giác để kiểm tra các xác nhận biên dịch của bạn. –

Trả lời

10

Như tôi dường như là một tay quay duy nhất trong quan tâm của tôi trong câu hỏi này, tôi đã cranked ra một câu trả lời cho bản thân mình, với một tập tin header cơ bản như thế này:

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 

Tiêu đề này chỉ để bao gồm trong một bộ thử nghiệm, và sau đó nó sẽ làm cho hiển thị định nghĩa lại vĩ mô của static_assert chỉ hiển thị khi bộ kiểm tra được xây dựng với

`-DTEST__EXCEPTIONALIZE_STATIC_ASSERT=1`  

Việc sử dụng các thiết bị này có thể được phác thảo với một món đồ chơi mẫu thư viện:

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 

Hãy thử tưởng tượng rằng mã là đủ lớn và phức tạp mà bạn không thể ở một chiếc mũ chỉ khảo sát và chọn ra số static_assert s và thỏa mãn bản thân rằng bạn biết lý do tại sao họ ở đó và họ đáp ứng mục đích thiết kế của họ. Bạn đặt niềm tin vào thử nghiệm hồi quy.

đây sau đó là một món đồ chơi hồi quy kiểm tra bộ cho 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 

Bạn có thể biên dịch test.cpp với ít nhất gcc 6.1, kêu vang 3.8 và tùy chọn -std=c++14 hoặc VC++ 19.10.24631.0 và tùy chọn /std:c++latest. Làm như vậy trước tiên mà không cần xác định TEST__EXCEPTIONALIZE_STATIC_ASSERT (hoặc xác định nó = 0). Sau đó chạy và đầu ra nên là:

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

Nếu bạn sau đó lặp lại, nhưng biên dịch với -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 :) 

Rõ ràng mã hóa lặp đi lặp lại của try/catch khối trong tĩnh khẳng định trường hợp thử nghiệm là tẻ nhạt , nhưng trong bối cảnh của một khung công tác kiểm tra đơn vị thực sự và đáng kính, sẽ mong đợi nó đóng gói bộ máy kiểm tra ngoại lệ để tạo ra những thứ như vậy trong tầm nhìn của bạn. Trong googletest, ví dụ, bạn có thể viết những thứ tương tự của:

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

Bây giờ tôi có thể lấy lại tính toán của tôi về ngày Armageddon :)

+0

'// Định nghĩa lại macro của 'static_assert'' ... ya, đừng làm vậy. Tạo một mã thông báo mới 'my_static_assert' để ánh xạ tới' static_assert' dưới tiêu chuẩn và 'my_strange_thing' bên dưới công cụ kiểm tra của bạn. Không cần phải lạm dụng ngôn ngữ khi bạn không phải ... – Yakk

+3

@Yakk Định nghĩa lại vĩ mô sẽ chỉ hiển thị trong bộ thử nghiệm, bởi vì tiêu đề xác định nó chỉ được đưa vào trong đó và chỉ hiển thị khi thử nghiệm bộ phần mềm được tạo bằng '-DTEST__EXCEPTIONALIZE_STATIC_ASSERT = 1'. Đã cập nhật câu trả lời để thực hiện rõ ràng. –

+0

@MikeKinghan Định nghĩa lại macro của bạn không hoạt động (với phiên bản STL của tôi), thật không may, do các hạn chế về macro khi sử dụng đối số mẫu, ví dụ: 'static_assert (std :: is_same < T1, T2 >," blabla ")'. Mã này cũng sẽ xuất hiện trong thư viện chuẩn. Đã không nghĩ về điều đó trước đó, nhưng nó là một máy cắt đối phó cho các giải pháp mà bạn đề nghị. Như Yakk đã đề xuất, xác định một mã thông báo mới là cách để đi trong trường hợp này. Btw, vẫn là một giải pháp tuyệt vời. –

Các vấn đề liên quan