Yleistä:
Tämän luokan avulla voit luoda testitulosteita näytölle tai tiedostoon siten, että ne sisentyvät automaattisesti sen mukaan kuinka pitkällä funktioiden kutsuhierarkiassa ohjelma on menossa. Käyttäjän testitulosteiden lisäksi luokka tulostaa viestin, kun funktioon saavutaan ja kun siitä poistutaan.
Luokan käyttö on hyvin helppoa ja sen käyttöliittymä muistuttaa tuttua cout:n käyttöä. Myös tulosteiden muotoilu <iomanip> kirjaston avulla toimii. Luokasta voisi olla erityisesti hyötyä aloittelijoille, jotka haluavat havainnoida koodinsa toimintaa tai etsiä virheitä.
Eli luokkaa voi hyvin käyttää, vaikka sen toteutusta ei ymmärtäisikään.
Käyttö:
Jokaisessa funktiossa, jossa halutaan käyttää testeriä, pitää luoda testeriolio.
Ensimmäistä Testeriä luotaessa käytetään syntaksia:
Testeri olion_nimi([funktion_nimi], [näyttö_tulosteet], [tiedosto_tulosteet], [tiedosto_nimi]);
Muita olioita luotaessa riittää funktion nimi, sillä ensimmäisellä kerralla asetetut asetukset säilyvät, eikä niitä voi muuttaa.
Esim:
// Ensimmäinen instanssi Testeri test_main("main()", true, true, "tiedosto.txt"); // Toinen instanssi Testeri test_funktio("funktio()");
Kun Testeri on luotu, sitä voi käyttää samaan tapaan kuin cout:ia. Huomaa kuitenkin, että mitään ei tulostu ennen kuin endl on tulostettu.
Esim:
// Luodaan testeri Testeri test_main("main()", true, false, "log.txt"); // Tehdään jotain tulosteita test_main << "Testituloste" << endl; int luku = 10; test_main << "Muuttujan arvo: " << luku << endl;
Esimerkki:
Lyhyt ohjelman, jossa käytetään testeriä:
#include "testeri.h" #include <iostream> #include <iomanip> using namespace std; void funktio(double *taulukko, int koko) { // Luodaan funktiolle oma testeri. Tällä kertaa // tarvitsee asettaa vain funktion nimi Testeri test_funktio("funktio(int*, int)"); for(int i=0; i<koko; ++i) { // Käyttäjän syöte double syote; cout << "Syota luku: "; cin >> syote; // Otetaan testituloste ja samalla käytetään iomanip kirjastoa test_funktio << "i:n arvo: " << setw(2) << i; test_funktio << ", Syote: " << setprecision(4) << syote << endl; } } int main() { // Luodaan ensimmäinen testeri ja asetetaan sekä // näyttötulosteet että lokitiedosto päälle Testeri test_main("main()", true, true, "testi.txt"); double taulukko[5]; funktio(taulukko, 5); }
Ohjelman tuloste tiedostoon "testi.txt" erään suorituksen jälkeen:
* TESTERI: Mon Jan 10 23:20:15 2011 * Entering: main() * Entering: funktio(int*, int) * [funktio(int*, int)] i:n arvo: 0, Syote: 4.568 * [funktio(int*, int)] i:n arvo: 1, Syote: 3.235 * [funktio(int*, int)] i:n arvo: 2, Syote: 1.235 * [funktio(int*, int)] i:n arvo: 3, Syote: 5.345 * [funktio(int*, int)] i:n arvo: 4, Syote: 3.234 * Exiting: funktio(int*, int) * Exiting: main()
Käyttöönotto:
Luokan saat käyttöösi, kun lisäät projektiisi tiedostot "testeri.h" sekä "testeri.cpp" (tiedoston pääte saattaa olla eri erilaisissa kehitysympäristöissä) ja kopioit niihin seuraavassa osassa olevan koodin. Sen lisäksi lisäät #include "testeri.h" rivin kaikkiin tiedostoihin, joissa haluat testeriä käyttää.
Toteutus:
Luokan toteutuksesta saa esittää kysymyksiä, kommentteja sekä parannusehdotuksia.
TESTERI.H
// // Rajapinta: testeri.hh // 2010 (C) Sami Pietikäinen // // Luokan avulla voidaan saada testitulosteita // ja havainnoida funktioiden toimintaa. // Halutessaan käyttäjä voi saada tulosteet myös // tiedostoon ja ottaa konsolitulosteet pois // käytöstä. // #ifndef TESTERI_HH #define TESTERI_HH #include <string> #include <ostream> #include <sstream> using std::string; using std::ostream; using std::stringstream; class Testeri { public: Testeri(const string& nimi); Testeri(const string& nimi, const bool& tulosteet, const bool& loki, const string& tiedostonimi); ~Testeri(); template<typename TYYPPI> Testeri& operator<<(TYYPPI data); // endl:n hallinta typedef ostream& (*TesteriEndl)(ostream&); Testeri& operator<<(TesteriEndl manip); private: // Yksityisen rajapinnan funktiot: void alusta_p(); void tulosta_p(); // Jäsenmuuttujat static unsigned lkm_; static bool tulosteet_; static bool loki_; static string tiedostonimi_; static bool alustettu_; string funktio_; stringstream buffer_; }; // Lisäysoperaattorin toteutus template<typename TYYPPI> Testeri& Testeri::operator<<(TYYPPI data) { buffer_ << data; return *this; } #endif // TESTERI_HH
TESTERI.CPP
// // Moduuli: testeri.cpp // 2010 (C) Sami Pietikäinen // // Testeri-luokan toteutus // #include "testeri.h" #include <time.h> #include <iomanip> #include <iostream> #include <fstream> #include <string> using std::cout; using std::endl; using std::setfill; using std::setw; using std::ofstream; // Alustetaan staattiset muuttujat unsigned Testeri::lkm_ = 0; bool Testeri::tulosteet_ = true; bool Testeri::loki_ = false; string Testeri::tiedostonimi_ = "loki.txt"; bool Testeri::alustettu_ = false; // Vakiomerkkijonot: const string TESTERI_START = "\n* TESTERI: "; const string TESTERI_ENTER = "Entering: "; const string TESTERI_EXIT = "Exiting: "; // Testeri-luokan rakentaja, jota on tarkoitus käyttää, // kun joku luokan instanssi on jo alustettu Testeri::Testeri(const string& nimi) { // Kasvatetaan laskuria ja asetetaan funktion nimi ++lkm_; funktio_ = nimi; alusta_p(); } // Testeri-luokan rakentaja, jota on tarkoitus kutsua // ensimmäistä instanssia luotaessa, mikäli ei haluta // käyttää vakioasetuksia Testeri::Testeri(const string& nimi, const bool& tulosteet, const bool& loki, const string& tiedostonimi) { // Jos ei olla alustamassa ensimmäistä instanssia, // niin ei muuteta mitään arvoja. if(!alustettu_) { tulosteet_ = tulosteet; loki_ = loki; tiedostonimi_ = tiedostonimi; } // Kasvatetaan laskuria ja asetetaan funktion nimi ++lkm_; funktio_ = nimi; alusta_p(); } // Testeri-luokan purkaja Testeri::~Testeri() { // Tuloste näytölle if(tulosteet_) { cout << "* "; cout << setw((lkm_-1)*2) << setfill(' ') << " "; cout << TESTERI_EXIT << funktio_ << endl; } // Tallennus lokitiedostoon if(loki_) { ofstream tiedosto(tiedostonimi_, std::ios_base::app); // Jos tiedoston avaus epäonnistuu, ei tallenneta mitään if(tiedosto) { tiedosto << "* "; tiedosto << setw((lkm_-1)*2) << setfill(' ') << " "; tiedosto << TESTERI_EXIT << funktio_ << endl; } } // Vähennetään laskuria --lkm_; } // Funktio tulostaa yhen rivin dataa sisennettynä // Jos lokitoiminto on päällä, niin tallennetaan // sama rivi myös tiedostoon. void Testeri::tulosta_p() { string data = buffer_.str(); buffer_.str(""); // Tulostus näytölle if(tulosteet_) { // Sisennys cout << "* "; cout << setw((lkm_-1)*2) << setfill(' ') << " "; // Varsinainen data cout << "[" << funktio_ << "] " << data << endl; } // Tallennus lokitiedostoon if(loki_) { ofstream tiedosto(tiedostonimi_, std::ios_base::app); // Jos tiedoston avaus epäonnistuu, ei tallenneta mitään if(tiedosto) { // Sisennys tiedosto << "* "; tiedosto << setw((lkm_-1)*2) << setfill(' ') << " "; // Varsinainen data tiedosto << "[" << funktio_ << "] " << data << endl; } } } // Funktio suorittaa kummallekin rakentajalle yhteiset // alustustoimet. void Testeri::alusta_p() { // Tuloste näytölle if(tulosteet_) { // Jos tämä on ensimmäinen instanssi, tulostetaan // aloitusmerkkijono: if(!alustettu_) { time_t aika; time(&aika); cout << TESTERI_START << ctime(&aika); } // Sisennys cout << "* "; cout << setw((lkm_-1)*2) << setfill(' ') << " "; // Varsinainen viesti cout << TESTERI_ENTER << funktio_ << endl; } // Tallennus lokitiedostoon if(loki_) { ofstream tiedosto(tiedostonimi_, std::ios_base::app); // Jos tiedoston avaus epäonnistuu, ei tulosteta mitään if(tiedosto) { // Jos tämä on ensimmäinen instanssi, tallennetaan // aloitusmerkkijono: if(!alustettu_) { time_t aika; time(&aika); tiedosto << TESTERI_START << ctime(&aika); } // Sisennys tiedosto << "* "; tiedosto << setw((lkm_-1)*2) << setfill(' ') << " "; // Varsinainen viesti tiedosto << TESTERI_ENTER << funktio_ << endl; } } // Varmuuden vuoksi asetetaan alustusmuuttuja alustettu_ = true; } // Endl:n hallinta Testeri& Testeri::operator<<(TesteriEndl manip) { tulosta_p(); return *this; }
Loppukommentit:
Jos koodista löytyy bugeja tai muuta parannettavaa, niin olisi mukava kuulla niistä. Samoin kokemuksia käytöstä sekä mahdollisia muita kommentteja voi vapaasti lähettää.
Ajattelin, että tässä olisi sopivasti samassa paketissa aloittelijoille hyödyllinen luokka, jonka toteutuksessa on joitain yksityiskohtia, joista ehkäpä edistyneemmätkin voivat hyötyä (std::endl hallinta omassa luokassa sekä template ainakin.) :)
Tiedostojen nimet olisi syytä kirjoittaa joka paikassa samalla tavalla; suuri osa käyttöjärjestelmistä käsittelee isot ja pienet kirjaimet eri tavalla, joten TESTERI.H on eri kuin testeri.h.
Koodissasi on koko joukko vikoja ja outouksia, joista suurimmat äkkiä huomaamani ovat tässä:
Luokan voisi hyvinkin toteuttaa korjauksineen noin 80 koodirivillä, jos hyväksytään sellainen minimaalinen epätarkkuus, että jokainen tulostus päättyy flushaukseen. (Usein tämä on debuggauksen kohdalla jopa suotavaa, jottei data joudu hukkateille edes ohjelman kaatuessa.)
Aihe on jo aika vanha, joten et voi enää vastata siihen.