© 2023 – 2026, Pau Fernández

PRO2

PRO2

Avaluació
Professorat
Pràctica
•

Lab 2: Piles i Cues

•

Doctest

•

Verifica la classe Racional amb Doctest

•

Escriu tests per a la suma, la resta, la multiplicació i la divisió

•

Les classes Stack<T> i Queue<T>

•

Escriu els tests per a Stack<T>

•

Escriu els tests per a Queue<T>

•

Problemes que fan servir piles i cues

•

Els streams de C++: istream, ostream i stringstream

•

istream: un canal d'entrada qualsevol

•

ostream: un canal de sortida qualsevol

•

istringstream i ostringstream: canals que fan servir cadenes de caràcters

•

Tests amb streams

•

7 Problemes de Piles i Cues

•

Piles

•

Cues

Lab 2: Piles i Cues

Aquesta sessió també es fa sense Jutge! El treball és personal i es pot fer localment en el teu ordinador.

En aquesta sessió de laboratori veurem la classe Stack i la classe Queue, però abans aprendrem una forma d'assegurar-nos que els nostres programes fan el que esperem. Provar programes és costós, però la bona notícia és que amb la mateixa programació ho podem automatitzar, i per tant és típic utilitzar algun mecanisme per poder testar que un programa, una funció o una classe fan el que volem.

La primera part tracta sobre com es fa servir Doctest, a la segona farem les classes Stack i Queue, i a la tercera, problemes que fan servir piles i cues. En cap moment ens caldrà el Jutge perquè nosaltres mateixos farem el paper del Jutge!

Doctest

Doctest és una llibreria per testar programes en C++ molt potent. Descarrega-la primer:

doctest.h

Recorda treballar en una carpeta nova i obrir aquesta carpeta per treballar amb VSCode!
No obris una carpeta amb tots els teus fitxers!

Ara tindràs el fitxer doctest.h en una carpeta, per si sol. Per escriure tests amb doctest.h, s'ha de fer un programa com el següent:

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"

#include <string>
using namespace std;

TEST_CASE("primer test") {
    int a = 1;
    CHECK(a == 1);
}

() {
    string s = ;
    (s.() == );
}

TEST_CASE
"segon test"
"hola"
CHECK
size
4

Bàsicament:

  • Cal posar l'#include "doctest.h" (compte amb el .h, no és .hh), afegint el #define ... primer, que fa que quan compilem el programa, tingui un main sense haver-lo de fer nosaltres.

  • Cada test és com una "funció" TEST_CASE, amb el nom del test entre parèntesis, i després un bloc de codi que fa el test. El codi farà certes operacions i per comprovar que els resultats són els bons, es crida CHECK, amb un valor que hauria de donar cert.

Copia el codi anterior en un fitxer test_prova.cc, compila'l i executa'l.

Hauries de veure una cosa com:

[doctest] doctest version is "2.4.12"
[doctest] run with "--help" for options
==========================================================
[doctest] test cases: 2 | 2 passed | 0 failed | 0 skipped
[doctest] assertions: 2 | 2 passed | 0 failed |
[doctest] Status: SUCCESS!

Verifica la classe Racional amb Doctest

El test_prova.cc era per provar una mica Doctest, però ara anem a fer-lo servir de veritat. Descarrega el ZIP següent:

racional.zip

Crea un directori nou i posa-hi els dos fitxers racional.hh i racional.cc a dins. Copia o descarrega de nou doctest.h i posa'l juntament amb els altres.

Veuràs que la classe Racional és la solució de l'exercici de la primera sessió de laboratori. Però per poder testar hauràs de fer un nou mètode. Es tracta d'un mètode per comparar dos racionals. Ara mateix, no podem fer això:

Racional r1, r2;
CHECK(r1 == r2); // ERROR: no tenim "==" per Racionals :(

Afegeix un mètode igual_que a la classe Racional de manera que poguem escriure tests així:

Racional r1, r2(0, 1);
CHECK(r1.igual_que(r2));

Escriu tests per a la suma, la resta, la multiplicació i la divisió

Així, doncs, escriu tests senzills per poder comprovar que les operacions aritmètiques amb Racionals funcionen, incloent la comparació:

TEST_CASE("comparacio") {
    Racional a, b(0, 1);
    CHECK(a.igual_que(b));

    Racional c(1,5), d(2, 10);
    CHECK(c.igual_que(d));
}

De fet, sabem que hi ha algun bug a la implementació de Racional (algunes IAs són un desastre... 🙄), així que és possible que alguns tests funcionin i d'altres no. Assegura't primer que els tests estan comprovant coses que realment saps que són certes! Si no, els tests t'estaran enganyant. Compila i executa el programa i mira la sortida per veure quins tests van bé i quins no.

Quan un test falla, apareix una cosa com:

TEST CASE:  resta

test_racional.cc:36: ERROR: CHECK( ... ) is NOT correct!

Si fas servir el terminal de dins de VSCode, pots prémer Ctrl i clicar test_racional.cc:36: i aniràs a parar al punt exacte del codi que ha fallat!

Amb la informació que tens, mira ara d'arreglar els errors i tornar a executar els tests. Hi ha un truc que consisteix en executar la compilació i l'execució en una sola comanda al terminal:

g++ -o test_racional *.cc ; ./test_racional

El ; permet posar dues comandes seguides. Però té un problema, què passa si la compilació dóna errors? Doncs que s'executarà el test igualment, amb la versió antiga! Hi ha una manera d'arreglar això:

g++ -o test_racional *.cc && ./test_racional

Igual que amb les expressions Booleanes en C++, si separes les comandes del terminal amb && la segona no s'executa si la primera no ha anat bé!

Les classes Stack<T> i Queue<T>

En aquesta secció treballarem amb Stack<T> i Queue<T> i completarem els seus tests. També farem tests que comproven que quan es crida un mètode, aquest produeixi un error, com passa amb la pila si fem pop quan està buida.

Escriu els tests per a Stack<T>

Descarrega el directori amb el projecte de Stack:

stack.zip

Descomprimeix el ZIP en un directori i fes make. Veuràs que molts tests no tenen cap CHECK i que l'últim produeix un error sense ni tan sols tenir CHECK.

  • Completa els tests de la classe pila posant CHECKs per comprovar que els mètodes funcionen bé. Ves fent make per veure com progresses.

  • Per arreglar el test anomenat "Pop en pila buida", el que cal fer és cridar una variant de CHECK que es diu CHECK_THROWS_WITH, que es crida així:

    CHECK_THROWS_WITH(pila.pop(), "Cagada pastoret!");
    

    El que fa aquest CHECK és realment comprovar que si cridem pop amb una pila buida, dóna un error, i que l'error retorna un missatge concret (que no és "Cagada pastoret!"). Posa el CHECK_THROWS_WITH per aquest cas amb els paràmetres que calguin. També busca un altre assert dins de la pila que es dispara en una condició diferent i que necessita un test per comprovar que també funciona.

Escriu els tests per a Queue<T>

Amb la cua hem de fer semblant, cal descarregar el ZIP, descomprimir, i acabar els tests de la cua, incloent els que produeixen un error per l'assert:

queue.zip

Problemes que fan servir piles i cues

Els streams de C++: istream, ostream i stringstream

Per poder practicar els problemes d'aquesta secció, necessitem saber coses noves sobre els streams de C++, que ens seran molt útils quan fem tests de programes que llegeixen o escriuen.

istream: un canal d'entrada qualsevol

D'entrada, cin és un objecte, i la seva classe és istream (una abreviació de "input stream"). Malgrat ara només coneixem cin, hi ha més canals d'entrada i tots ells es comporten igual (tots tenen l'operador >> per poder llegir enters, caràcters, etc.).

Això ens permet fer una funció com:

#include <iostream>
using namespace std;

int llegeix_enter(istream& in) {
    int x;
    in >> x;
    return x;
}

int main() {
    int a = llegeix_enter(cin);
}

Aquest programa és interessant perquè té una funció llegeix_enter que no llegeix de cin, sinó d'un canal qualsevol, que és un objecte que es passa com a paràmetre. És a dir, podem fer que llegeix_enter llegeixi d'on vulguem, de fet. El nom de la variable és in però podria ser qualsevol altre, el que passa és que in resulta més similar a cin, perquè suggereix "input".

ostream: un canal de sortida qualsevol

Una cosa semblant passa amb ostream. Quan volem escriure, podem fer servir qualsevol objecte de classe ostream i podrem fer el mateix que fem amb cout:

void escriu_dos_cops(ostream& out, string p) {
    out << p << ' ' << p;
}

Per cridar aquesta funció es podria fer:

escriu_dos_cops(cout, "Bora");

i torna a passar com abans, estem passant cout com a paràmetre de la funció però podríem passar una altra cosa.

istringstream i ostringstream: canals que fan servir cadenes de caràcters

Per entendre la utilitat de istringstream i ostringstream, fem primer un programa que faci servir alhora l'entrada i la sortida:

void suma_sequencia_enters(istream& in, ostream& out) {
    int suma = 0, x;
    while (in >> x) {
        suma += x;
    }
    out << suma << endl;
}

La funció suma_sequencia_enters podria ser un main i fer servir cin i cout, però fent-ho així, podrem provar el programa passant-li una entrada que prové d'un string, i recollir la sortida també en un string.

L'entrada serà un istringstream, o sigui un canal d'entrada construit a partir d'un string:

istringstream sin("1 2 3 4 5");

Per fer servir aquesta classe cal fer #include <sstream>, i la declaració anterior, clarament crida un constructor que crea un objecte sin (que és un istream), a partir d'un text. El nom és expressament semblant a cin, però amb una s perquè fa servir strings per dins.

Ara podem crear el canal de sortida, que no omplirem nosaltres (sinó el programa suma_sequencia_enters):

ostringstream sout;

Amb els dos canals preparats, es pot invocar el programa anterior així:

suma_sequencia_enters(sin, sout);

que s'executarà i anirà llegint els caràcters de sin, que efectivament són una seqüència d'enters, i finalment escriurà sobre sout, que teòricament emmagatzemarà la sortida.

Només ens cal recollir la sortida amb el mètode str i mirar-la:

// el mètode 'str' retorna el contingut de `sout`
string resultat = sout.str();

Tests amb streams

Tot plegat, si volem escriure un test per a la funció suma_sequencia_enters, podem fer-ho així:

TEST_CASE("suma curta") {
    istringstream sin("1 2 3 4 5");
    ostringstream sout;

    suma_sequencia_enters(sin, sout);

    CHECK(sout.str() == "15\n"); // '\n' és perquè hi ha un 'endl'!!
}

En funció del sistema operatiu, és possible que el \n sigui un \r\n (Windows).

7 Problemes de Piles i Cues

Els següents problemes tots tenen una estructura que fa servir la tècnica explicada a la secció anterior per testar les entrades i sortides de certs problemes, però el codi del problema està per fer. O sigui, que descarregant cada ZIP tindràs una petita carpeta amb un problema "tipus Jutge", però en què tots els tests són visibles i controlats per tu mateix.

Piles

p1-reverse.zip
p2-parentesis.zip
p3-recursivitat.zip
p4-indexant-parentesis.zip

Cues

p5-patata-calenta.zip
p6-compta-recents.zip
p7-cremallera.zip