Home > The Unit Test Framework > Tutorials > Boost.Test driven development
PrevNext

Boost.Test driven development or "getting started" for TDD followers

Today is a momentous day - first day of new year. Today I am going to start a new life. I am going to stop eating a greasy food, start attending a fitness club and today I am going to test programs I am writing. I can start right after the last line of a program is completed or, even better, I can write tests while I am coding. And maybe next time I will write tests before the coding, during the design stage. I have read a lot of literature on how to write the tests, I have the unit test framework in hand and an idea of new class. So let's get started.
今天是重要的一天 - 新年的第一天。今天我要開始一種新的生活。我要停止吃充滿油脂的食物,開始參加健身俱樂部, 並且今天我要開始測試寫的程序。我會在程序最後一次結束後開始測試,更進一步,我可以在編碼時編寫測試。 下次我可以在編碼前的設計階段編寫測試。 我已經閱讀了大量關於如何編寫測試的文獻,我手頭已經有了單元測試框架,並且已經有了新類的想法。 那麼我們開始吧。

Let say I want to encapsulate an unchangeable C character buffer with a length into the simple class const_string. Rationale: a string class that does not allocate a memory and provide a convenient read-only access to the preallocated character buffer. I will probably want const_string to have an interface similar to the class std::string. What will I do first? In my new life I will start with writing a test module for future class const_string. It will look like this:
現在我想要將不可變的 C 字符緩衝區和長度封裝進一個簡單的類 const_string。 原則:不申請內存的字符串類,提供對預分配的字符緩衝區的只讀訪問。 我想要 const_string 有和 std::string 相似的接口。 第一步應該做什麼呢?在新生活中,我應該為將來的 const_string 類編寫測試模塊。 看起來就像這樣:

#define BOOST_TEST_MODULE const_string test
#include <boost/test/unit_test.hpp>

// EOF

Now I can compile it and link with the unit test framework. Done! I have a working test program. It is empty, so when I run the program it produces following output:
現在我可以編譯並鏈接到單元測試框架。完成!現在有一個可以工作的測試程序了。它是空的,所以如果我運行會得到如下的輸出:

*** No errors detected

Well, now it could be a good time to start a work on const_string. First thing I imagine would be good to have is a constructors and trivial access methods. So my class initial version looks like this:
好了,現在是開始 const_string 的時候了。 我認為最開始的事情應該是構造函數和平凡的訪問方法。所以我第一個版本看起來是這樣:

const_string.hpp:

class const_string {
public:
    // Constructors
    const_string();
    const_string( std::string const& s )
    const_string( char const* s );
    const_string( char const* s, size_t length );
    const_string( char const* begin, char const* end );

    // Access methods
    char const* data() const;
    size_t      length() const;
    bool        is_empty() const;

    ...
};

Now I am able to write a first test case - constructors testing - and add it to a test suite. My test program became to look like this:
現在我可以寫第一個測試用例 - 構造測試 - 將它加入測試套件中。我的測試程序變得這樣了:

const_string_test.cpp:

#define BOOST_TEST_MODULE const_string test
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE( constructors_test )
{
     const_string cs0( "" );                                                 // 1 //
     BOOST_CHECK_EQUAL( cs0.length(), (size_t)0 );
     BOOST_CHECK( cs0.is_empty() );

     const_string cs01( NULL );                                              // 2 //
     BOOST_CHECK_EQUAL( cs01.length(), (size_t)0 );
     BOOST_CHECK( cs01.is_empty() );

     const_string cs1( "test_string" );                                      // 3 //
     BOOST_CHECK_EQUAL( std::strcmp( cs1.data(), "test_string" ), 0 );
     BOOST_CHECK_EQUAL( cs1.length(), std::strlen("test_string") );

     std::string s( "test_string" );                                         // 4 //
     const_string cs2( s );
     BOOST_CHECK_EQUAL( std::strcmp( cs2.data(), "test_string" ), 0 );

     const_string cs3( cs1 );                                                // 5 //
     BOOST_CHECK_EQUAL( std::strcmp( cs1.data(), "test_string" ), 0 );

     const_string cs4( "test_string", 4 );                                   // 6 //
     BOOST_CHECK_EQUAL( std::strncmp( cs4.data(), "test", cs4.length() ), 0 );

     const_string cs5( s.data(), s.data() + s.length() );                    // 7 //
     BOOST_CHECK_EQUAL( std::strncmp( cs5.data(), "test_string", cs5.length() ), 0 );

     const_string cs_array[] = { "str1", "str2" };                           // 8 //
     BOOST_CHECK_EQUAL( cs_array[0], "str1" );
     BOOST_CHECK_EQUAL( cs_array[1], "str2" );
}

// EOF

The constructors_test test case is intended to check a simple feature of the class const_string: an ability to construct itself properly based on different arguments. To test this feature I am using such characteristics of constructed object as a data it contains and a length. The specification of the class const_string does not contain any expected failures, so, though the constructor can fail if I would pass a pointer to an invalid memory, error check control is not performed (can't require what was not promised :-)). But for any valid input it should work. So I am trying to check a construction for an empty string (1), a NULL string (2) a regular C string(3), an STL string(4), a copy construction(5) and so on. Well, after fixing all the errors in the implementation (do you write programs without errors from scratch?) I am able to pass this test case and the unit test framework gives me the following report:
測試用例 constructors_test 試圖檢查類 const_string 的簡單特性: 根據不同的參數正確構造的能力。為了測試這個特性我使用有代表性的數據和長度作為參數進行構造。 類 const_string 並不存在可預期的失敗,所以,雖然如果我傳入指向無效內存的指針, 構造將失敗,但錯誤檢查控制將不運行 (並不能要求其保證的 :-))。但是對於有效的輸入它可以工作。 所以我檢查空字符串構造 (1),NULL 字符串 (2),普通 C 字符串 (3),STL 字符串 (4),拷貝構造 (5)和其它。 在修改了所有實現的錯誤 (難道你寫程序沒有因為筆誤發生的錯誤) 之後, 我可以通過這個測試用例了,並且單元測試框架會給出如下輸出:

Running 1 test case 
  
*** No errors detected

Encouraged I am moving on and adding more access methods:
很受鼓舞,我可以繼續添加更多的訪問方法了:

const_string.hpp:

class const_string {
public:
    ...
    char operator[]( size_t index ) const;
    char at( size_t index ) const;
    ...
};

I added the new feature - I need a new test case to check it. As a result my test suite became to look like this:
我添加了新的特性 - 需要新的測試用例來檢驗。這樣我的測試套件就是這樣了:

const_string_test.cpp:

#define BOOST_TEST_MODULE const_string test
#include <boost/test/unit_test.hpp>

BOOST_AUTO_EST_CASE( constructors_test )
{
    ...
}

BOOST_AUTO_EST_CASE( data_access_test )
{
    const_string cs1( "test_string" );                                 // 1 //
    BOOST_CHECK_EQUAL( cs1[(size_t)0], 't' );
    BOOST_CHECK_EQUAL( cs1[(size_t)4], '_' );
    BOOST_CHECK_EQUAL( cs1[cs1.length()-1], 'g' );

    BOOST_CHECK_EQUAL( cs1[(size_t)0], cs1.at( 0 ) );                  // 2 //
    BOOST_CHECK_EQUAL( cs1[(size_t)2], cs1.at( 5 ) );
    BOOST_CHECK_EQUAL( cs1.at( cs1.length() - 1 ), 'g' );

    BOOST_CHECK_THROW( cs1.at( cs1.length() ), std::out_of_range );    // 3 //
}

// EOF

In the data_access_test test case I am trying to check the class const_string character access correctness. While tests (1) checks valid access using const_string::operator[] and test (2) checks valid access using method const_string::at(), there is one more thing to test. The specification of the method const_string::at() contains validation for the out of bound access. That was test (3) is intended to do: check that the validation is working. A testing of a validation and error handling code is an important part of a unit testing and should not be left for a production stage. The data_access_test test case passed and I am ready for the next step.
在測試用例 data_access_test 中,我驗證類 const_string 的字符訪問的正確性。 測試 1 檢查 const_string::operator[] 的正確性, 測試 2 檢查 const_string::at() 的正確性,還有一樣要測試。 const_string::at() 的聲明中包含越界訪問的驗證。那就是測試 3 做的: 檢查驗證是否工作。驗證測試和錯誤處理代碼是單元測試是非常重要的部分,並且不應該留到產品級階段。 data_access_test 測試用例通過,我準備好進行下一步了。

Continuing my effort I am able to complete class const_string (see Listing 1) and testing module for it (see Listing 2) that is checking all features that are presented in the class const_string specification.
繼續努力,我已經完全類 const_string (見 Listing 1) 和相應的檢查類 const_string 所有特性的測試模塊 (見 Listing 2)。

Well, I am step closer to fulfilling my new year resolution (we should see about this fitness club sometime next ). What about you? Your testing habits could be a little different. You could start with a class/library development and then at some point start writing test cases on feature basis. Or you can, given a detailed specification for the future product, including expected interfaces, immediately start with writing all test cases (or it could be a different person, while you working on implementation at the same time). In any case you should not have any problems to use facilities provided by the Boost.Test unit test framework and, let me hope, be able to write a stable, bulletproof code. And what is even more important is your confidence in an ability to make changes of any complexity without involving a lengthy regression testing of your whole product. Your test module and the unit test framework will stay behind your back to help you with any occasional errors.
好了,我已經接近滿足新年的改革 (下次我們會看到健身俱樂部) 了。你呢?你的測試習慣可能會有所不同。 你可能會從開發類 / 庫開始,然後在某個點上開始基於基本特性寫編寫測試用例。 或者你可以為將來的產品,包括期望的接口,給出詳細的定義,立刻開始編寫所有的測試用例 (或者是另外一個跟你同時工作的人)。 在任何情況下,你都可以不用對使用 Boost.Test 單元測試框架提供的工具提出問題,並且,我希望,可以寫出穩定、健壯的代碼 (???)。 更重要的是你可以放心地對任意複雜的代碼進行修改而不用擔心陷於一遍又一遍的產品整體回歸測試中。 測試模塊和單元測試框架堅定地站在你的背後,幫助你避免偶然的錯誤。


PrevUpHomeNext