The acceptance test makes the customer satisfied that the\\ software provides the business value that makes them willing\\ to pay for it. The unit test makes the programmer satisfied\\ that the software does what the programmer thinks it does.\\ ===== Opis biblioteki ===== Biblioteka Boost.Test dostarcza programiście zestaw narzędzi do testowania tworzonego przez niego oprogramowania. Mechanizmy dostarczane przez bibliotekę umożliwiają tworzenie programów testujących, definiowanie przypadków testowych i grupowanie ich w zestawy testów oraz uruchamianie testów w monitorowanym środowisku. ==== Minimal testing facility ==== Minimal testing facility, jak sama nazwa wskazuje, stanowi minimum mechanizmów umożliwiających testowanie oprogramowania. Pierwsza wersja Boost.Test zawierała jedynie prezentowaną w tym podrozdziale funkcjonalność. Minimal testing facility dostarcza własną funkcję main(), która uruchamia funkcję test_main(dostarczaną przez użytkownika) w monitorowanym środowisku. Biblioteka dba o to, aby wszystkie parametry uruchomienia programu zostały również przekazane do funkcji test_main(). #include #include int square_err( int arg ) { return arg*arg-1; //blad gruby:) } int square( int arg ) { return arg*arg; } int cube( int arg ) { return arg*arg*arg; } int cube_err( int arg ) { return square_err( arg )*arg; } int test_main( int /*argc*/, char* /*argv*/[] ) //uwaga - zmieniona nazwa funkcji main { int foo(3); //w monitorowanym srodowisku uzyskujemy dostep do makr testujacych o wiele mowiacych nazwach //makro BOOST_REQUIRE przerywa testowanie w przypadku niespelnienia podanego mu warunku BOOST_REQUIRE( foo == 3 ); //za pomoca tego makra powinny byc wiec testowane warunki, ktorych spelnienie jest krytyczne dla //dalszego dzialania programu //BOOST_CHECK wypisuje informacje o bledzie na standardowe wyjscie po zakonczeniu testow BOOST_CHECK( square( foo ) == 9 ); BOOST_CHECK( square_err( foo ) == 9 ); BOOST_CHECK( cube( foo ) == 27 ); BOOST_CHECK( cube_err( foo ) == 27 ); if( foo != 4 ) BOOST_ERROR( "foo != 4 !!!!!" ); //ponizsze 3 sprawdzenia przy bledzie spowoduja przerwanie dalszego testowania if(cube( foo ) != 4 ) throw "cube(3) != 4"; //recznie wykryty blad, zglaszany poprzez wyjatek //ktory nie ma okazji byc zlapany if(cube( foo ) != 8 ) BOOST_FAIL("cube(3) != 8");//rowniez zglasza wyjatek BOOST_REQUIRE( foo == 4 ); //blad w BOOST_REQUIRE powoduje przerwanie testow std::cout << "Ten kod nigdy nie zostanie osiagniety\n"; return 0; } ==== Program execution monitor ==== Kolejnym udostępnianym przez bibliotekę mechanizmem jest Program execution monitor. Podobnie jak minimal testing facility tworzy on monitorowane środowisko uruchomieniowe, w którym uruchamiany jest program użytkownika. Po zakończeniu testów użytkownikowi przedstawiany jest raport z przeprowadzonych testów, zawierający informacje o testach, które się nie powiodły oraz o wyjątkach, które zostały zgłoszone, ale nie zostały obsłużone. #include int square_err( int arg ) { return arg*arg-1; //blad gruby:) } int square( int arg ) { return arg*arg; } int cpp_main( int, char* [] ) //zmieniona nazwa !!!!!! { int foo(3); if( square( foo ) != 9 ) throw "square() error"; if( square_err( foo ) != 9) throw "square_err() error"; //niezlapany wyjatek, spowoduje wypisanie na konsole //komunikatu: exception: C string: square_err() error //aby uzyskac komunikat o bledzie nalezy wyrzucic jeden z nastepujacych typow: //ciag znakow w stylu C (NULL-terminated) //instancje std::string //std::exception lub dowolna klase pochodna return 1; } Program execution monitor posiada możliwość konfiguracji przez następujące zmienne środowiskowe: * BOOST_TEST_CATCH_SYSTEM_ERRORS - pozwala wyłączyć przechwytywanie błędów systemowych, domyślnie ustawiona na "yes" * BOOST_PRG_MON_CONFIRM - domyślnie ustawiona na "yes" powoduje wyświetlanie wiadomość potwierdzającą w przypadku pomyślnego zakończenia testów. ==== Execution monitor ==== Execution monitor, czyli monitor wykonywania programu, jest jednym z niskopoziomowych elementów biblioteki Boost.Test, oraz stanowi bazę do implementacji pozostałych narzędzi przez nią oferowanych. Używany jako osobne narzędzie zapewnia monitorowane środowisko wykonywania programu oraz ujednoliconą obsługę błędów. Poniżej zaprezentowano jedną z ciekawszych jego cech, czyli rejestrowanie funkcji obsługujących własne wyjątki programisty. #include #include #include #include struct NotImplementedYetException {}; struct DivisionByZeroException {}; namespace { class TestClass { int arg; public: TestClass( int a ): arg(a){} int operator() () { if( arg > 10 ) throw NotImplementedYetException(); if( arg == 0 ) throw DivisionByZeroException(); return 10/arg; } }; void translate_NotImplementedYetException( NotImplementedYetException const& ex ) { std::cout<< "tej funkcji jeszcze nie zaimplementowano\n"; } void translate_DivisionByZeroException( DivisionByZeroException const& ex ) { std::cout<< "wymuszenie dzielenia przez zero\n"; } } //zakonczenie lokalnej przestrzeni nazw int cpp_main( int /*argc*/, char* /*argv*/[]) { ::boost::execution_monitor monitor; //rejestrowanie funkcji tlumaczacych wyjatki //pozwalaja one na bezpiecznie przechwytywanie wyjatkow przez mechanizmy biblioteki w przypadku //gdy uzytkownik nie dostarczy kodu wylapujacego te wyjatki //zapobiega to normalnemu w takich wypadkach wyjsciu z programu oraz umozliwia //przejrzenie listy zgloszonych w ten sposob wyjatkow po zakonczeniu testow monitor.register_exception_translator( &translate_NotImplementedYetException ); monitor.register_exception_translator( &translate_DivisionByZeroException ); try { monitor.execute( ::boost::unit_test::callback0( TestClass( 20 ) ) ); /* execution_monitor::execute( unit_test::callback0 const& F, bool catch_system_errors, int timeout ) F - funkcja przyjmujaca zero argumentow, rzucany jest wyjatek boost::execution_exception przy niezlapanym wyjatku, wystapieniu bledu systemowego (jesli catch_system_errors jest ustawiony na true) lub po okreslonym timeoucie */ } catch ( boost::execution_exception const& ex ) { std::cout << "Zlapano wyjatek: " << ex.what() << std::endl; } return 0; } ====Unit test framework ==== Unit test framework jest narzędziem oferującym programiście prosty i przystępny sposób na testowanie jego programów. Jest moim zdaniem najprzydatniejszym elementem biblioteki. Umożliwia tworzenie przypadków testowych testujących zarówno niezwiązane funkcje, jak i metody klas. Przypadki testowe (test cases) można grupować w zestawy testowe (test suites) tak, aby np. testy metod jednej klasy były zgrupowane w jednym zestawie.\\ \\ Funkcja main nie jest dostarczana przez programistę, robi to za niego unit test framework. Programista musi zdefiniować funkcje o nagłówku boost::unit_test::test_suite* init_unit_test_suite ( int argc, char* argv[] ) (argc i argv są parametrami wywołania, nie można ich pominąć przy deklaracji, można je zignorować pisząc init_unit_test_suite ( int, char* [] ) ). Zadaniem tej funkcji jest inicjalizacja drzewa testowego, wartością zwracaną powinien być master test suite, czyli zbiór wszystkich przypadków testowych. //naglowek zawierajacy Unit Test Framework #include //naglowek zawierajacy BOOST_CHECK_CLOSE #include #include using namespace boost::unit_test; /****************************************************************************** * wartosci zwracane przez Unit Test Framework po zakonczeniu testow: * boost::exit_success - wszystkie testy zakonczone pomyslnie * boost::exit_test_failure - wykryto niekytyczne bledy (nonfatal errors) lub * nie powiodla sie inicjalizacja unit test suite * boost::exit_exception_failure - pojawily sie bledy krytyczne lub niezlapane * wyjatki ******************************************************************************/ // przypadki testowe mozna rejestrowac na kilka roznych sposobow,najprostszym z // nich jest na pewno automatycznie rejestrowana funkcja niezwiazana z zadna klasa // rejestrujemy ja za pomoca jednego z magicznych makr boost.test // w nawiasie podawana jest nazwa przypadku testowego BOOST_AUTO_TEST_CASE( auto_test_case ) { /* makro to automatycznie tworzy obiekt function_test_case oraz umieszcza go w globalnym zestawie testowym. Testowana funkcja jest wlasnie niniejsza funkcja:) */ // w ramach kazdego przypadku testowego mozemy sprawdzic dowolna ilosc warunkow int i = 666; // zostanie wygenerowany komunikat 'error in "auto_test_case": check i == 667 failed [666 != 667]' BOOST_CHECK_EQUAL( i, 667 ); // tym razem wynik testu bedzie poprawny, nie zotanie wygenerowany zaden komunikat BOOST_CHECK( i == 666 ); } // automatycznie rejestrowane przypadki testowe mozna grupowac w zetawy testowe BOOST_AUTO_TEST_SUITE( auto_test_suite ) // ten przypadek testowy bedzie nalezal do auto_test_suite, a nie do globalnego zestawu BOOST_AUTO_TEST_CASE( nonglobal_test ) { BOOST_CHECK_MESSAGE( 2+2 == 5, "ten komunikat bedzie wypisany zamiast zwyklego komunikatu o bledzie"); double x = 1.000000, y = 1.000001; double epsilon = 1e-6; //sprawdzenie, czy dwie liczby roznia sie od siebie o nie wiecej niz zadana wartosc BOOST_CHECK_CLOSE( x, y, epsilon ); } // automatyczne zestawy testowe mozna zagniezdzac BOOST_AUTO_TEST_SUITE( internal_test_suite ) BOOST_AUTO_TEST_CASE( internal_suite_test_case ) { BOOST_ERROR( "internal_suite_test_case_error" ); } BOOST_AUTO_TEST_SUITE_END() //zakonczenie internal_test_suite BOOST_AUTO_TEST_SUITE_END() //zakonczenie auto_test_suite // mozemy definiowac niezwiazane funkcje testujace, ktore nastepnie beda dodane zamienione // na przypadki testowe w funkcji init_unit_test_suite void free_test_function() { // wypisuje 'unknown location(0): fatal error in "free_test_function": memory access violation // nazwapliku.cpp(numerLinii): last checkpoint' int* p = (int*)0x01; BOOST_CHECKPOINT( "Za chwile wystapi naruszenie ochrony pamieci" ); //BOOST_CHECK( *p == 0 ); //wywolanie tej linijki spowoduje wystapienie fatalerror // co z kolei spowoduje zakonczenie testow i wyjscie z programu BOOST_CHECK( 2 == 1 ); } class KlasaTestujaca { int i; public: explicit KlasaTestujaca(int i_): i(i_) {} void class_test_case() { BOOST_CHECK(2 == 1); } }; //____________________________________________________________________________// test_suite* init_unit_test_suite( int, char* [] ) { framework::master_test_suite().p_name.value = "Przykladowe test_suite"; //jesli nie korzystamy z globalnego zestawu testow, mozemy definiowac wlasne //za pomoca makra o nastepujacej skladni //test_suite* test= BOOST_TEST_SUITE( "nazwa zestawu testow" ); //wywolujac metode add(tak jak ponizej) mozemy dodawac przypadki testowe //oraz nowe zestawy testow, ukladajac je w drzewiasta strukture framework::master_test_suite().add( BOOST_TEST_CASE( &free_test_function ), 3 ); boost::shared_ptr instance( new KlasaTestujaca(0) ); framework::master_test_suite().add( BOOST_CLASS_TEST_CASE( &KlasaTestujaca::class_test_case, instance ) ); return 0; } Jak łatwo zauważyć biblioteka ta w znacznym stopniu polega na prostych makrach opakowujących szablony. Skutkuje to niestety znacznym wydłużeniem kompilacji, która na słabszych komputerach (takich jak np. komputer autora:) ) może trwać na prawdę długo. Elegancję tak frywolnego użycia tak dużej ilości makr pozostawiam do oceny czytelnikowi.