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.