Laborator FIS 8

Testare, Depanare si Validare

Orice aplicatie are nevoie sa fie testata, in asa fel incat sa ne asiguram ca ea corespunde cerintelor de proiect. Testarea poate fi efectuata prin doua mari metode:

    • white-box - se realizeaza tinand cont de structura interna a programului, clase, metode, flux de date, etc. Se urmareste, manual sau automat, sa se intre pe cat mai multe dintre alternativele aplicatiei, cu cat mai multe seturi de date, folosind analiza codului sursa. De exemplu, daca exista o metoda "int max(int a,int b)", care intoarce maximul dintre a si b, se va urmari sa se testeze aceasta metoda pentru toate cazurile posibile (a<b, a==b, a>b).

    • black-box - aceasta testare se realizeaza din perspectiva utilizatorului (sau a datelor de intrare), deci fara sa se cunoasca codul sursa. Aplicatia se ruleaza in cele mai diverse conditii si acolo unde exista interactiuni cu utilizatorul, se vor testa cat mai multe cazuri posibile. De exemplu, daca se cere un numar de la tastatura, se pot testa urmatoarele situatii: niciun caracter introdus, doar spatii, numar cu una sau cu mai multe cifre (cu sau fara - in fata), numar care contine in el caractere nepermise (sau spatii inainte sau dupa el), doar caractere nepermise, numar invalid (cu virgula pentru cazurile in care se cer doar numere intregi, sau negativ pentru cand se cer doar numere pozitive), etc. Aceasta testare este foarte bine sa se faca de catre alta persoana decat cea care a scris aplicatia, din mai multe motive:

      • cel care a scris aplicatia are tendinta sa introduca seturi de date corecte si deci sa nu depisteze toate cazurile posibile de eroare

      • poate exista si alte moduri specifice de interactiune cu aplicatia, la care autorul nu s-a gandit, dar care sunt puse in practica de catre un utilizator, in special cand nu a fost instruit cum sa foloseasca aplicatia

      • de obicei exista o anumita graba din partea programatorilor sa termine o aplicatie, si atunci din comoditate sau din limite de resurse, de obicei testarea nu se face decat superficial

In ambele metode de testare de mai sus, se vor testa si cazuri speciale, gen:

    • ce se petrece daca aplicatia, sau o anumita fereastra este inchisa brusc, fara a se folosi butonul de inchidere? (de exemplu atunci cand cade tensiunea) - aceasta testare este in special importanta cand este nevoie sa se lucreze cu fisiere sau cu baze de date, pentru a se verifica in aceste cazuri cat din consistenta datelor este mentinuta

    • ce se petrece daca nu mai exista memorie disponibila, sau spatiu liber pe disc?

    • care sunt cerintele minime de sistem pentru a rula aplicatia? (memorie, spatiu liber pe disc, rezolutie ecran, versiune minima de sistem de operare, necesar de componente preinstalate in sistem {versiune java sau .net necesara,...}, etc)

    • ce se petrece dupa ce aplicatia este inchisa si repornita din nou? - testarea este in special importanta cand trebuie salvate date automat, sau preferintele utilizatorului (marime fonturi, paleta de culori, directoare active, etc)

Daca apar anumite probleme la testare, in principiu urmeaza ori o faza de imbunatarire a aplicatiei (atunci cand in prima faza nu s-a tinut cont de situatiile care au dus la aparitia acelor probleme), ori o faza de depanare, atunci cand problemele aparute deriva din erori de la nivel de cod sursa. De exemplu, daca o aplicatie cere un numar de la utilizator, dar din start presupune ca utilizatorul introduce doar numere valide, atunci evident ca nu se poate considera o defectiune a aplicatiei faptul ca un numar introdus gresit genereaza rezultate invalide. In aceasta situatie eventual se poate decide imbunatatirea aplicatiei, prin validarea datelor introduse de utilizator si atentionarea acestuia daca a introdus date gresite. Daca insa s-a introdus un numar valid, iar rezultatele sunt invalide, atunci in mod sigur este o problema in aplicatie (bug) si trebuie sa se treaca la o etapa de depanare.

In special cand aplicatia primeste date din exterior (interactiune cu utilizatorul, fisiere, retea, etc), in principiu este bine sa se faca o validare cat mai amanuntita a acestor date. Cazurile in care validarea datelor din exterior nu a fost facuta trebuie specificate clar in documentatie, pentru ca utilizatorii si ceilalti programatori care vor dezvolta in continuare aplicatia sa fie avertizati. La testare, uneori trebuie sa se ia o decizie foarte importanta si anume cat de corecte se pot considera datele dintro anumita sectiune a aplicatiei, decizie care se poate lua in trei situatii:

    • datele de intrare sunt corecte in toate cazurile - nu este nevoie de nicio validare. Exemplu: daca avem o functie implementata corect, care intoarce maximul a doua numere intregi, cu orice numere am apela functia, ea va intoarce maximul celor doua numere

    • pot sa existe date incorecte care provin chiar din aplicatie - se trateaza prin asertiuni (cand se doreste inlaturarea acestor situatii) sau prin exceptii (cand se intentioneaza ca aceste situatii sa se verifice si in rularea normala a aplicatiei). Exemplu: daca avem o functie care intoarce maximul unor numere intregi dintrun vector dat ca argument, putem considera caz de eroare situatia in care vectorul primit este null. S-ar putea uneori ca aceasta functie sa primeasca un argument null si atunci avem doua variante:

      • folosim asertiuni in faza de testare a aplicatiei, pentru a depista si inlatura toate sursele de unde ar putea proveni acel vector null. In Java, o asertiune se foloseste astfel:

      • int max(int[] a){

      • assert(a!=null);

      • .....

      • }

      • In parentezele de dupa assert se pune o conditie logica. Daca acesta conditie nu este respectata, se va genera o exceptie de tipul "AssertionError". Pentru a se verifica exceptiile in NetBeans, la "Projects" se face "click dreapta pe numele proiectului->Properties->Run". Si la "VM Options" se introduce "-enableassertions" (fara ghilimele). Aceasta poate ramane asa pe toata faza de dezvoltare si testare a aplicatiei, iar cand aplicatia este livrata beneficiarului se sterge "-enableassertions". In aceasta situatie este ca si cum asertiunile au fost scoase complet din cod, ele nemai folosind resurse de memorie sau de microprocesor.

      • folosim exceptii, atunci cand depindem de alte module pe care nu le putem valida si care si ele pot furniza date:

      • int max(int[] a)throws Exception{

      • if(a==null)throw new Exception("eroare interna in functia max(int[]). Va rugam sa ne contactati pentru remedierea acestei erori");

      • ....

      • }

      • In acesta situatie, daca de undeva din interiorul aplicatiei se apeleaza functia "max(int[] a)" cu parametru null, utilizatorul va fi rugat sa contacteze dezvoltatorul pentru remedierea situatiei. In acest caz, dezvoltatorul va putea sa afle de la utilizator (sau din niste loguri inregistrate si trimise automat de catre aplicatie) care a fost secventa de actiuni care a dus la acea situatie de eroare si sa o remedieze.

    • pot sa existe date incorecte care provin din exteriorul aplicatiei - se trateaza ori prin validare de date si atentionarea utilizatorului atunci cand se urmareste remedierea lor (de exemplu numere introduse gresit), ori prin exceptii si iesirea din acea operatie sau chiar din aplicatie, atunci cand situatia nu se poate remedia (de exemplu cand se citeste un fisier de intrare care are un format eronat).

Este bine ca atunci cand se folosesc exceptii pentru testare sau pentru situatii de eroare, acestea sa fie derivate din "Exception" si sa nu se foloseasca direct "Exception". Astfel ele vor putea fi tratate special in blocuri "try...catch" dedicate doar lor, si care vor lasa alte exceptii sa treaca.

In etapa de depanare, cel mai simplu de depanat sunt erorile sintactice, pentru ca ele sunt semnalizate automat de compilator. O clasa speciala de mesaje ale compilatorului il reprezinta atentionarile (warnings). Acestea nu sunt erori, dar de cele mai multe ori indica o problema de conceptie. Exemplu: "variable not used" indica faptul ca avem o variabila declarata dar nefolosita nicaieri. Cel mai bine este sa stergem acea variabila, sau sa o comentam daca stim sigur ca o vom folosi ulterior, pentru ca daca mai tarziu vom consulta codul si o vom vedea, nu vom mai sti la ce foloseste. Este foarte bine sa avem obiceiul sa programam in asa fel incat sa remediem toate atentionarile care pot sa apara.

Pentru depanarea erorilor semantice (de conceptie), cel mai simplu se poate folosi debugger-ul integrat (vezi laboratorul 4). Se va recrea situatia care a dus la aparitia erorii, setand breakpoint-uri care sa izoleze din ce in ce mai bine eroarea, iar apoi de la breakpoint-uri se va rula programul pas cu pas si se vor inspecta valorile variabilelor, pentru a se vedea unde apar probleme. Dupa ce s-a depistat sursa problemei, acesta va putea fi eliminata.

Asociata cu testarea este si faza de validare a aplicatiei. Aceasta se refera la verificarea faptului ca programul face ceea ce trebuie (pe cand testarea de obicei verifica daca o rutina functioneaza cu toate datele de intrare). De exemplu, functia:

int max(int a,int b)

{

if(a<=b)return b;

return b;

}

va functiona pentru toate datele de intrare, in sensul ca nu va genera exceptii gen "null pointer", "division by zero", etc...dar semantic este incorecta. In general prin testare se subintelege si validare. Validarea aplicatiei difera de validarea datelor de intrare (de la utilizator, fisiere, ....).

In acest laborator vom introduce mai multe validari in programul dezvoltat in laboratorul 7. In acest sens se cer urmatoarele:

    • id-urile trebuie testate daca sunt numere intregi pozitive (sunt formate doar din cifre). Daca formatul nu este valid, se va afisa "id-ul trebuie sa fie format doar din cifre".

    • daca un id nu exista in BD, se va afisa mesajul "id-ul ... nu exista in baza de date". Existenta unei inregistrari in baza de date se poate face printr-un select de forma "SELECT f_id FROM t_users WHERE f_id=..." si sa se testeze daca exista cel putin o valoare returnata.

    • daca o carte este deja imprumutata, se va afisa mesajul "cartea avand id-ul ... este deja imprumutata".

    • numele de utilizatori trebuie sa contina doar litere, spatii si semnul "-". Daca mai sunt si alte caractere, se va afisa mesajul "in nume apar caractere invalide. sunt permise doar litere, spatii si -".

    • numele de utilizatori si de carti trebuie sa aiba minim 3 caractere (incluzand spatiile). Daca numele sunt prea scurte, se va afisa mesajul "numele trebue sa aiba minim 3 caractere".

In Java testarea unor caractere se poate face folosind metodele "is..." din clasa "Character" (de exemplu "Character.isLetter" testeaza daca un caracter este litera), sau cu expresii regulate (de exemplu t.matches("[a-zA-Z]+") testeaza daca variabila

"t" de tip String contine doar caractere si este nevida). Formatul expresiilor regulate nu face obiectul acestui laborator. Pentru cei interesati, formatul expresiilor regulate acceptate de Java poate fi gasit consultand documentatia functiei "String.matches", iar exemple pot fi gasite aici sau aici.