Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: C++: Luokka testitulosteiden luomiseen ja ohjelman kulun havainnointiin

Sampe [10.01.2011 23:33:06]

#

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.) :)

Metabolix [13.01.2011 18:02:53]

#

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.)

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta