Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Previous revision Next revision | Previous revision Next revision Both sides next revision | ||
boosttest [2008/04/16 01:45] twroniak |
boosttest [2008/04/16 22:52] twroniak |
||
---|---|---|---|
Linia 1: | Linia 1: | ||
- | ==== Opis biblioteki ==== | + | 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 === | + | 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(). |
+ | <code cpp> | ||
+ | #include <boost/test/minimal.hpp> | ||
+ | #include <iostream> | ||
+ | int square_err( int arg ) | ||
+ | { | ||
+ | return arg*arg-1; //blad gruby:) | ||
+ | } | ||
- | === Program execution monitor === | + | int square( int arg ) |
+ | { | ||
+ | return arg*arg; | ||
+ | } | ||
+ | int cube( int arg ) | ||
+ | { | ||
+ | return arg*arg*arg; | ||
+ | } | ||
- | === Execution monitor === | + | 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; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | |||
+ | ==== 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. | ||
+ | |||
+ | <code cpp> | ||
+ | #include <boost/test/prg_exec_monitor.hpp> | ||
+ | |||
+ | 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; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <code cpp> | ||
+ | #include <boost/test/prg_exec_monitor.hpp> | ||
+ | #include <boost/test/execution_monitor.hpp> | ||
+ | #include <boost/test/utils/basic_cstring/io.hpp> | ||
+ | |||
+ | #include <iostream> | ||
+ | |||
+ | 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<NotImplementedYetException>( &translate_NotImplementedYetException ); | ||
+ | monitor.register_exception_translator<DivisionByZeroException>( &translate_DivisionByZeroException ); | ||
+ | |||
+ | try | ||
+ | { | ||
+ | monitor.execute( ::boost::unit_test::callback0<int>( TestClass( 20 ) ) ); | ||
+ | /* | ||
+ | execution_monitor::execute( unit_test::callback0<int> 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; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ====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. | ||
+ | |||
+ | <code cpp> | ||
+ | |||
+ | //naglowek zawierajacy Unit Test Framework | ||
+ | #include <boost/test/unit_test.hpp> | ||
+ | //naglowek zawierajacy BOOST_CHECK_CLOSE | ||
+ | #include <boost/test/floating_point_comparison.hpp> | ||
+ | #include <boost/shared_ptr.hpp> | ||
+ | using namespace boost::unit_test; | ||
+ | |||
+ | /******************************************************************************* | ||
+ | // funkcja main nie jest dostarczana przez programiste, robi to za niego unit | ||
+ | // test framework. Programista musi zdefiniowac funkcje o naglowku | ||
+ | // boost::unit_test::test_suite* init_unit_test_suite ( int argc, char* argv[] ) | ||
+ | // (argc i argv sa parametrami wywolania, nie mozna ich pominac przy deklaracj, | ||
+ | // mozna je zignorowac piszac init_unit_test_suite ( int, char* [] ) ). | ||
+ | // Zadaniem tej funkcji jest inicjalizacja drzewa testowego, wartoscia zwracana | ||
+ | // powinien byc master test suite, czyli zbior wszystkich przypadkow tesotwych. | ||
+ | // w przypadku zwrocenia wartosci NULL testy nie beda wykonane, a program | ||
+ | // zakonczy sie zwroceniem boost::exit_test_failure | ||
+ | *******************************************************************************/ | ||
+ | |||
+ | /****************************************************************************** | ||
+ | * 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<KlasaTestujaca> instance( new KlasaTestujaca(0) ); | ||
+ | framework::master_test_suite().add( BOOST_CLASS_TEST_CASE( &KlasaTestujaca::class_test_case, instance ) ); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | 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. | ||
- | ===Unit test framework === |