18. listopadu 2013, Brno ´ Pˇripravil: David Prochazka
´ ı Defenzivn´ı programovan´ Programovac´ı jazyk C++
Procˇ testovat
ˇ Obsah pˇredna´ sky 1
Proˇc testovat
2
Aserce
3
´ ı Testovan´
4
CUnit a ty dalˇs´ı
5
Shrnut´ı
Strana 2 / 36
Procˇ testovat
Strana 3 / 36
Motivace Pˇriznejme si: aplikace jsou sloˇzite´ a plne´ chyb. ´ ret co nejmen ´ eˇ chybov´y kod, ´ proto Chceme vˇsak vytvaˇ ´ mus´ıme pouˇz´ıvat nastroje, ktere´ je eliminuj´ı. Na aplikaci se budeme d´ıvat ze dvou pohledu: ˚ ´ ´ pohledu programatora: jak to ma´ fungovat – hl´ıdame pomoc´ı aserce, ˇ a´ – hl´ıdame ´ pohledu testera: co to doopravdy del pomoc´ı testu. ˚
ˇ ovladat ´ Jedna persona by nemela druhou ( Tester“ by ” ˇ ved ˇ et“, ˇ co metoda udel ˇ a.). ´ nemel ”
Aserce
ˇ Obsah pˇredna´ sky 1
Proˇc testovat
2
Aserce
3
´ ı Testovan´
4
CUnit a ty dalˇs´ı
5
Shrnut´ı
Strana 4 / 36
Aserce
Strana 5 / 36
Invariant ˇ aplikace byste meli ˇ b´yt schopni prohlasit, ´ Pˇri behu zˇ e urˇcite´ podm´ınky plat´ı (napˇr. zˇ e hodnota tohoto indexu je menˇs´ı, neˇz x). ˇ aplikace mus´ı b´yt Tyto v´yroky, ktere´ pro korektn´ı beh ˇ naz´yvame ´ splneny invarianty. ˇ b´yt definovany ´ pouze u podm´ınek Invarinaty by mely jejichˇz platnost jsme schopni zajistit. ´ ı vstupu od uˇzivatele. Nepouˇz´ıvat napˇr. na testovan´ ˇ ˇ rit pomoc´ı makra Platnost techto invariantu˚ muˇ ˚ zeme oveˇ assert().
Aserce
Strana 6 / 36
Pˇr´ıklad aserce 1 2 3 4 5 6 7 8 9
MyVector :: push_back ( int value ){ if ( pozice == kapacita ){ zvetsit (); } // nikdy nepredpokladejte , ze neco plati // dokazte to ... assert ( pozice < kapacita ); data [ pozice +1] = value ; }
´ ´ V pˇr´ıpadeˇ neplatnosti bude vraceno nasleduj´ ıc´ı: Assertion failed: (pozice
Aserce
Strana 7 / 36
ˇ ı pˇr´ıklad aserce Dals´ 1 2
# include < iostream > # include < cassert >
3 4 5 6 7 8 9 10 11 12 13 14 15 16
class Trida { private : float normalizeVector ( float x , float y , float z ){ float size = sqrt ( sqr ( x )+ sqr ( y )+ sqr ( z ) ); x /= size ; y /= size ; z /= size ; } public : void ca lculat eEquat ion ( float x , float y , float z ){ ... normalizeVector (x , y , z ); assert (( x <=1) and (y <=1) and (z <=1)); } };
Aserce
Strana 8 / 36
Vypnut´ı aserce ´ ı makra NDEBUG. Aserci muˇ ˚ zeme vypnout definovan´ Obvykle definujeme pro celou aplikaci najednou: c++ -DNDEBUG main.cc ... ´ ri cˇ asto aserci vyp´ınaj´ı pˇri finaln´ ´ ım sestaven´ı Programatoˇ aplikace. ´ eˇ vyp´ınan´ ´ ı je diskutabiln´ı. Je pˇrirovnav ´ ano ´ Nicmen k tomu, ´ ´ kdyˇz zaˇc´ınaj´ıc´ı namoˇ rn´ık nos´ı plovac´ı vestu na treningu, ale nevezme si ji na moˇre.
Aserce
Strana 9 / 36
´ ˇ Zakladn´ ı myslenka aserce ´ ı zdroju˚ Na oˇsetˇren´ı vstupu˚ od uˇzivatele a selhan´ ´ ´ pouˇz´ıvame podm´ınky a v´yjimky. Aserce je pouˇz´ıvana na oˇsetˇren´ı vnitˇrn´ı logiky programu. ´ Veˇskere´ problemy signalizovane´ aserc´ı je nutne´ vyˇreˇsit v dobeˇ v´yvoje aplikace.
´ ı Testovan´
ˇ Obsah pˇredna´ sky 1
Proˇc testovat
2
Aserce
3
´ ı Testovan´
4
CUnit a ty dalˇs´ı
5
Shrnut´ı
Strana 10 / 36
´ ı Testovan´
Strana 11 / 36
´ ı Testovan´ ´ ı je v zasad ´ Metodika testovan´ eˇ manaˇzersk´y/filozofick´y postoj. ´ ı integrovat do Existuje rˇada postupu, ˚ jak testovan´ ´ v´yvojoveho cyklu aplikace. ´ ı je Vˇsechny metodiky se shoduj´ı na tom, zˇ e testovan´ potˇreba.
´ ı Testovan´
Strana 12 / 36
ˇ dˇr´ıve, ˇ neˇz kod ´ Pˇr´ıstup prvn´ı: Testy piste
2
Rozmyslete si, jak ma´ aplikace fungovat a stanovte vstupy, ´ hlaviˇckov´y soubor ). v´ystupy (z´ıskate ˇ Urˇcete mezn´ı podm´ınky a reakce na ne.
3
Napiˇste testy.
4
ˇ Implementujte metody tak, aby splnovaly testy
1
Proˇc takto? 1 2 3
´ ri radi ´ pouˇz´ıvaj´ı ukazn ´ en ˇ e´ vstupy. Programatoˇ ´ Je nutne´ proˇsmatrat tmave´ kouty“ aplikace. ” Dobr´y test je ten, kter´y odhal´ı chybu. Ne ten, kter´y skonˇc´ı ˇ bezchybne.
´ ı v´yjimky nebo pad ´ aplikace. Stane se, zˇ e test i zpusob´ ı vyvolan´ ˚ ´ ´ ˇ To je v poˇradku. Postupneˇ testy odkomentovavejte a zjistete kter´y to byl (a proˇc).
´ ı Testovan´
Strana 13 / 36
Mejme tˇr´ıdu datum 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class Date { public : Date (); Date ( int year , int month , int day ); Date ( const std :: string &); int getYear () const ; int getMonth () const ; int getDay () const ; std :: string toString () const ; friend bool operator <( const Date & , const Date &); friend bool operator >( const Date & , const Date &); friend bool operator <=( const Date & , const Date &); friend bool operator >=( const Date & , const Date &); friend bool operator ==( const Date & , const Date &); friend bool operator !=( const Date & , const Date &); };
´ ı Testovan´
Strana 14 / 36
Pˇred implementac´ı navrhneme test 1 2 3
# include " Date . h " # include < iostream > using namespace std ;
4 5 6 7 8
int nPass = 0 , nFail = 0; // testovaci pocitadlo void test ( bool t ) { if ( t ) nPass ++; else nFail ++; }
9 10 11 12 13 14 15 16 17
int main () { Date mybday (1951 , 10 , 1); test ( mybday . getYear () == 1951); test ( mybday . getMonth () == 10); test ( mybday . getDay () == 1); cout << " Passed : " << nPass << " , Failed : " << nFail << endl ; }
´ ı Testovan´
´ Potˇrebne´ testy: operatory 1 2 3 4 5 6 7 8 9 10 11
// test operatoru test ( mybday < today ); test ( mybday <= today ); test ( mybday != today ); test ( mybday == mybday ); test ( mybday >= mybday ); test ( mybday <= mybday ); test ( myevebday < mybday ); test ( mybday > myevebday ); test ( mybday >= myevebday ); test ( mybday != myevebday );
Strana 15 / 36
´ ı Testovan´
Potˇrebne´ testy: metody 1 2 3 4 5 6 7 8
// testy metod test ( mybday . getYear () == 1951); test ( mybday . getMonth () == 10); test ( mybday . getDay () == 1); test ( myevebday . getYear () == 1951); test ( myevebday . getMonth () == 9); test ( myevebday . getDay () == 30); test ( mybday . toString () == " 19511001 " );
Strana 16 / 36
´ ı Testovan´
Strana 17 / 36
ˇ Ted’ doopravdy: nap´ıseme testovac´ı tˇr´ıdu 1 2 3 4 5 6 7 8 9 10 11 12 13 14
class DateTest : public TestSuite :: Test { Date mybday ; Date today ; Date myevebday ; public : DateTest (){ mybday = Date (1951 , 10 , 1); myevebday = Date ( " 19510930 " ); }; void run (); // prekryta z TestSuite private : void testOps (); void testFunctions (); };
´ ı Testovan´
Implementace testu opratoru˚ 1 2 3 4 5 6 7 8 9 10 11 12
void DateTest :: testOps () { test_ ( mybday < today ); test_ ( mybday <= today ); test_ ( mybday != today ); test_ ( mybday == mybday ); test_ ( mybday >= mybday ); test_ ( mybday <= mybday ); test_ ( myevebday < mybday ); test_ ( mybday > myevebday ); test_ ( mybday >= myevebday ); test_ ( mybday != myevebday ); }
Strana 18 / 36
´ ı Testovan´
Strana 19 / 36
Implementace testu zakladn´ıch metod 1 2 3 4 5 6 7 8 9 10
void DateTest :: testFunctions () { test_ ( mybday . getYear () == 1951); test_ ( mybday . getMonth () == 10); test_ ( mybday . getDay () == 1); test_ ( myevebday . getYear () == 1951); test_ ( myevebday . getMonth () == 9); test_ ( myevebday . getDay () == 30); test_ ( mybday . toString () == " 19511001 " ); test_ ( myevebday . toString () == " 19510930 " ); }
´ ı Testovan´
´ ı testu Zavolan´ 1 2 3
# include < iostream > # include " DateTest . h " using namespace std ;
4 5 6 7 8 9
int main () { DateTest test ; test . run (); // return test . report (); }
Strana 20 / 36
´ ı Testovan´
Strana 21 / 36
ˇ a´ test ? Co del 1 2 3 4 5 6 7 8 9 10 11 12 13 14
// pseudokod void test_ ( bool condition ){ if ( condition ){ nPass ++ // rozumne by bylo zapisovat si to // do atributu a ten vypsat v metode report cout << " Test " << typeid (* this ). name () << " Passed " << endl ; } else { nFail ++; cout << " Test " << typeid (* this ). name () << " Failed " << endl ; } }
´ ı Testovan´
Strana 22 / 36
´ ı v´ıce tˇr´ıd Testovan´ ˇ ame ´ ´ ´ et. ˇ Udel si zasobn´ ık testu, ˚ ktere´ se budou provad 1 2 3 4 5 6 7 8 9
TestSuite suite ( " Date and Time Tests " ); suite . addTest ( new MonthInfoTest ); suite . addTest ( new JulianDateTest ); suite . addTest ( new JulianTimeTest ); suite . addTest ( new DateTest ); suite . addTest ( new TimeTest ); suite . run (); suite . report (); ...
´ Aby bylo moˇzne´ testy provolavat, byla pro odvozen´ı testu ˇ cnost. pouˇzita dediˇ
´ ı Testovan´
Strana 23 / 36
´ ı vyjimek Testovan´ ´ 1 2 3 4 5 6 7
void testExceptions () { try { Date d (0 ,0 ,0); // Invalid fail_ ( " Invalid date undetec . in Date int ctor " ); } catch ( Date :: DateError &) { succeed_ (); }
8
try { Date d ( " " ); // Invalid fail_ ( " Inv . date undetec . in Date string ctor " ); } catch ( Date :: DateError &) { succeed_ (); }
9 10 11 12 13 14 15
}
ˇı CUnit a ty dals´
ˇ Obsah pˇredna´ sky 1
Proˇc testovat
2
Aserce
3
´ ı Testovan´
4
CUnit a ty dalˇs´ı
5
Shrnut´ı
Strana 24 / 36
ˇı CUnit a ty dals´
Strana 25 / 36
´ ı Knihovny pro automatizovane´ testovan´ ´ ejˇ ˇ s´ı je knihovna CUnit: Zˇrejmeˇ nejznam http://cunit.sourceforge.net/, ´ eˇ pro C, ktera´ se pˇrilinkuje. Staticka´ knihovna primarn ˇ Nekolik rozhran´ı: Automated (v´ystup do XML), Basic (standardn´ı v´ystup), Console (interaktivn´ı reˇzim), Curses (interaktivn´ı grafick´y“ reˇzim). ”
Dobra´ je knihovna Boost: http://www.boost.org/:
´ ı funkce pro C++, Experimentaln´ http://www.boost.org/doc/libs/1_45_0/libs/test/ doc/html/index.html,
Paralelou JUnit je CppUnit (2): https://launchpad.net/cppunit2: ´ a´ knihovna, podpora pro TDD. Rozsahl
Jednoduchou alternativou je CuTest: http://cutest.sourceforge.net/, Jeden .c a .h soubor, pˇriloˇz´ı se k projektu.
ˇı CUnit a ty dals´
Strana 26 / 36
´ Zakladn´ ı test v CppUnit: MoneyApp.cpp 1 2 3 4
# include # include # include # include
" stdafx . h " < cppunit / Compi lerOut putter .h > < cppunit / extensions / T e s tF a c to r y Re g i st r y .h > < cppunit / ui / text / TestRunner .h >
5 6 7 8 9 10 11 12 13 14 15 16 17
int main ( int argc , char * argv []){ // vytvorime testovaci suite CppUnit :: Test * suite = CppUnit :: T es t F ac t o ry R e gi s t ry :: getRegistry (). makeTest (); // pridame spoustec testu CppUnit :: TextUi :: TestRunner runner ; runner . addTest ( suite ); // nastaveni vystupu runner . setOutputter ( new CppUnit :: C ompile rOutpu tter (& runner . result () , std :: cerr ) ); ...
ˇı CUnit a ty dals´
Strana 27 / 36
´ Zakladn´ ı test v CppUnit: MoneyApp.cpp 2 1 2 3 4 5 6
// spusteni testu bool wasSucessful = runner . run (); // Return error code 1 if the one of test failed . return wasSucessful ? 0 : 1; } To nejhorsi mame za sebou ...
ˇı CUnit a ty dals´
Strana 28 / 36
´ ˇ Zakladn´ ı test: hlavickov y´ soubor MoneyTest 1 2
# ifndef MONEYTEST_H # define MONEYTEST_H
3 4
# include < cppunit / extensions / HelperMacros .h >
5 6 7 8 9 10 11 12 13
class MoneyTest : public CppUnit :: TestFixture { CPPU NI T_ TE ST _S UI TE ( MoneyTest ); // skupina CPPUNIT_TEST ( testConstructor ); // test konst . CP PU N I T _ T E S T _ S U I T E _ E N D (); public : void testConstructor (); }; # endif // MONEYTEST_H
ˇı CUnit a ty dals´
Strana 29 / 36
´ Zakladn´ ı test: implem. soubor MoneyTest 1
# include " MoneyTest . h "
2 3
C P P U N I T _ T E S T _ S U I T E _ R E G I S T R A T I O N ( MoneyTest );
4 5 6 7 8
void MoneyTest :: testConstructor (){ // zatim prazdna implementace , jen hlaseni chyby CPPUNIT_FAIL ( " not implemented " ); }
ˇı CUnit a ty dals´
Strana 30 / 36
Uˇz v´ıme jak bude vypadat tˇr´ıda a jej´ı konstr. ˇ ame ´ Udel nejdˇr´ıve jeho testovac´ı metodu. 1 2 3 4
void MoneyTest :: testConstructor (){ // vytvoreni pomocnych promennych const std :: string currencyFF = " FF " ; const double longNumber = 12345678.90123;
5
// pokus o vytvoreni Money money ( longNumber , currencyFF );
6 7 8
// aserce CPPU N I T _ A S S E R T _ E Q U A L ( longNumber , money . getAmount ()); CPPU N I T _ A S S E R T _ E Q U A L ( currencyFF , money . getCurrency ());
9 10 11 12 13 14
}
ˇı CUnit a ty dals´
Strana 31 / 36
Vytvoˇr´ıme tˇr´ıdu s konstruktorem 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
... class Money { double m_amount ; std :: string m_currency ; public : Money ( double amount , std :: string currency ){ m_amount = amount ; m_currency = m_currency ; } double getAmount () const { return m_amount ; } std :: string getCurrency () const { return m_currency ; } ...
ˇı CUnit a ty dals´
Strana 32 / 36
ˇ ı chybou... A test skonc´ Takˇze oprav´ıme konstruktor... 1 2 3 4
Money ( double amount , std :: string currency ){ m_amount = amount ; m_currency = currency ; // tady byl preklep }
ˇı CUnit a ty dals´
´ ame ´ ˇ ı testy... A postupneˇ dodav dals´ V MoneyTest.h: 1 2 3 4 5
... CPPUNI T_ TE ST _S UITE ( MoneyTest ); CPPUNIT_TEST ( testConstructor ); CPPUNIT_TEST ( testEqual ); CP PU NI T _ T E S T _ S U I T E _ E N D ();
6 7 8 9 10
public : ... void testEqual (); ...
Strana 33 / 36
ˇı CUnit a ty dals´
Strana 34 / 36
Implementujeme novy´ test... V MoneyTest.cpp: 1 2 3 4 5 6
void MoneyTest :: testEqual (){ // pomocne promenne const Money money123FF ( 123 , " FF " ); const Money money123USD ( 123 , " USD " ); const Money money12FF ( 12 , " FF " ); const Money money12USD ( 12 , " USD " );
7
// stejna mena CPPUNIT_ASSERT ( CPPUNIT_ASSERT ( // ruzna mena CPPUNIT_ASSERT ( CPPUNIT_ASSERT (
8 9 10 11 12 13 14
}
money123FF == money123FF ); money12FF != money123FF ); money123USD != money123FF ); money12USD != money123FF );
Shrnut´ı
ˇ Obsah pˇredna´ sky 1
Proˇc testovat
2
Aserce
3
´ ı Testovan´
4
CUnit a ty dalˇs´ı
5
Shrnut´ı
Strana 35 / 36
Shrnut´ı
Strana 36 / 36
´ ˇ ´ ı Zakladn´ ı myslenky testovan´ ´ Testujeme systematicky (po sebemenˇs´ı uprav eˇ v systemu), ´ ´ testujeme automatizovaneˇ (nemame s´ılu testovat ˇ systematicky ruˇcne), ´ av ´ ame, ´ ´ testuje dusledn eˇ (nic nepˇredpoklad zˇ adnou ˚ ˇ s´ı) podm´ınku nevynechame). ´ (sebejasnejˇ