Tesztelést segítő osztály
„Próbáljuk ki, hogy működik-e!”
A manuális tesztelés sok időnket elviszi. Pedig a tesztelést nem muszáj ám kézzel csinálni. Ha adott egy függvény, amiről tudjuk hogy milyen bemenetre milyen kimenetet kell adjon... Akkor hívjuk meg, és ellenőrizzük a kapott eredményt! A várt eredményeket a kapottakkal összevethetjük; egy osztály minden funkcióját tesztelhetjük ilyen módon.
Az automatikus teszt futtatható specifikáció. Elindítjuk a tesztet, és onnantól minden további teendő nélkül megkapjuk a választ: teljesíti-e a program a specifikációját, vagy nem.
A tesztek kétfélék lehetnek:
-
A specifikáció alapján készült tesztek. Ebben a szemléletben: amire nincs teszt, az mintha nem is lenne előírva. Ha szükséges egy bizonyos funkció, tessék rá tesztet írni.
-
Az élet hozta tesztek. Hogyan lesz egy ilyen?
- Kiderül, hogy van egy bug a programban.
- Kitalálunk egy bemenetet, amivel a bug előcsalogatható.
- Megírjuk a tesztet, ami ezzel a bemenettel eteti a programot. Kipróbáljuk, hogy tényleg előjön-e a hiba.
- Kijavítjuk a programot. A javítás után a teszt is jelzi, hogy rendben van a kimenet.
Miért jó ez? Mert az a hiba soha nem fog többé előjönni; a teszteknek része lesz ez is.
Írj tesztelést segítő osztályt! Ennek legyen egy .test()
metódusa,
teszt_neve
, elvárt_érték
, kapott_érték
paraméterekkel, amely
- Kiírja, hogy sikeres a teszt, ha
elvárt_érték == kapott_érték
. - Sikertelenséget jelez, ha nem egyenlőek.
- Közben számolja, hány teszt volt sikeres, és mennyi volt összesen.
Test t1;
t1.test("A gép tud osztani.", 2, 10/5);
t1.test("Én tudok szorozni.", 11, 3*4);
t1.report();
OK A gép tud osztani.
FAIL Én meg tudok szorozni. 11 != 12
1 sikeres teszt, 2 összesen.
Használd ezt a következő feladatban!
A maximumkeresés
Adott az alábbi, elfuserált maximumkereső függvény:
#include <iostream>
int max(double *tomb, size_t meret) {
double m = tomb[1];
for (size_t i = 1; i < meret-1; ++i)
if (tomb[i] > m)
m = tomb[i];
return m;
}
int main() {
double szamok[5] = { 6, 8, 13.1, 8.7, 4 };
std::cout << max(szamok, 5);
}
Mi a legszembetűnőbb hibajelenség, ami futtatás után is látszik? Mielőtt kijavítanád a hibát, írj rá tesztet! Előbb lásd, hogy a teszt jelzi, probléma van, és a hibát utána javítsd csak ki!
A függvényben két további hiba is el van rejtve. Találj ki olyan bemeneteket, amelyek esetén jelentkeznek a problémák, írd meg a teszteket, végül pedig javítsd a hibákat!
Bevezető
**
van: az egyik azért, mert dinamikus tömböt
tárol, a másik pedig azért, mert a dinamikus tömbben dinamikus sztringek vannak.
class SztringTomb {
char **data;
/* ... */
};
Ennek az osztálynak másoló konstruktort, destruktort írni szörnyűséges feladat.
Itt egy másik „dinamikus sztringek dinamikus tömbje” osztály:
class SztringTomb {
std::vector<std::string> data;
/* ... */
};
Az egyik *
a vektor osztályba került, a másik *
a sztringbe. Mivel a vektor és a sztring is
rendelkezik saját másoló konstruktorral és destruktorral (mindkettő érték szerint kezelhető), ezért nekünk semelyiket sem kell
megírni.
A probléma
Itt egy heterogén kollekció:
class Alakzat {
public:
virtual void kiir() const = 0;
virtual ~Alakzat() {}
};
class Tegla : public Alakzat {
public:
virtual void kiir() const {
std::cout << "Egy egy teglalap vagyok.\n";
}
};
class Kor : public Alakzat {
public:
virtual void kiir() const {
std::cout << "En egy kor vagyok.\n";
}
};
class Rajztabla {
private:
Alakzat **a;
public:
/* ... */
};
Ebben megint két *
-ot látunk. Az egyik *
a dinamikus tömb miatt van, a másik *
pedig a
heterogén típusok miatt. A tömb miatti *
-gal már tudjuk, mit kezdjünk:
class Rajztabla {
private:
std::vector<Alakzat*> a;
public:
/* ... */
};
Miért baj a másik *
? Azért, mert emiatt még mindig destruktort kell írnunk a rajztáblának. És azért, mert hiába
másoljuk le a pointereket, új alakzatok nem keletkeznek (bekavar az indirekció) – az új rajztábla a régi alakzatait használja,
azokra hivatkozik csak.
Most ezt a *
-ot fogjuk eltüntetni: másik osztályba kiszervezni, így refaktorálni a kódot.
A feladatok
Először:
-
Írj a rajztáblának
hozzaad(Alakzat *)
éslistaz()
függvényt! -
Írj rövid teszt kódot, amelyben hozzáadsz a rajztáblához néhány dinamikusan foglalt alakzat.
-
Írd meg a rajztábla destruktorát és másoló konstruktorát, hogy lásd, pontosan mi a probléma. Vedd észre, hogy itt a heterogén kollekció másolása miatt szükség van az alakzatok klónozására; vezesd be az ehhez szükséges
klonoz()
függvényt az osztályhierarchiába! -
Teszteld a másoló konstruktort, hogy meggyőződj róla: helyes a másolás, tényleg új alakzatok keletkeznek! Ezt leginkább úgy fogod látni, hogy a destruktor nem száll el (mert nem akarja kétszer felszabadítani ugyanazt az alakzat).
És most jön a lényeg. Az ötlet az, hogy létrehozol egy AlakzatProxy
osztályt. Ez az osztály
egyetlen egy darab alakzat fog tartalmazni (amely persze
heterogén lehet, mert lehet téglalap, kör vagy bármi más). Az AlakzatProxy
objektum érték
szerint kezelhető. Ha a proxy másolódik, a benne lévő alakzat is másolódik. Ha a proxy megszűnik, a benne
lévő alakzat is megszűnik. Ezen felül pedig, duplikálja az alakzat interfészét: mindent, amit az alakzattal
lehet csinálni (kiírni, kerületét kiszámítani stb. – most csak a kiírás van), azt a proxyval is lehet; minden
függvényhívást a tárolt alakzatnak továbbít, helyettesíti azt.
AlakzatProxy a1(new Kor);
{
AlakzatProxy a2 = a1; // a kör másolata
a2.kiir(); // Én egy kör vagyok
// a2 megszűnik, másolt kör megszűnik
}
Tehát másodszor:
- Implementáld a fenti minta alapján az
AlakzatProxy
osztályt. - Dolgozd át a rajztábla osztályt úgy, hogy a benne lévő
Alakzat*
-otAlakzatProxy
-ra cseréled. - Vizsgáld meg a rajztábla előbb megírt másoló konstruktorát és destruktorát. Hogyan kell módosítani őket? Miért?
Extrák
- Zavarhat, hogy az
Alakzat
osztály interfészét azAlakzatProxy
osztály interfészén duplikálni kellett. Töröld ki azt onnan, és írj helyetteoperator*
(a tárolt alakzat referenciáját visszaadó), ésoperator->
(a tárolt alakzatra mutató pointert visszaadó) függvényt! Így ezekkel bármelyik függvény elérhetővé válik:
AlakzatProxy a1(new Kor);
rajzol(*a1); // void rajzol(Alakzat&)
a1->kiir(); // Alakzat::kiir()
- Rejtsd el a dinamikus memóriakezelést az
AlakzatProxy
osztályba egyszer s mindenkorra! Írj azAlakzatProxy
osztálynak konstruktort, amelyik tetszőleges paraméterként kapottAlakzat
objektumról dinamikus másolatot készít, és azt tárolja!
AlakzatProxy a1(Kor());
AlakzatProxy a2(Tegla());
a1->kiir(); // En egy kor vagyok.
a2->kiir(); // En egy teglalap vagyok.
Vedd észre, hogy ismeretlen típusú alakzatról dinamikus másolatot készítő függvényed már van.