Laborator 07 - Test-driven development

Introducere

De cele mai multe ori, dupa ce un programator finalizeaza scrierea unui “program” dupa anumite specificatii, acesta este trimis catre echipa de testare in vedere gasirii bug-urilor. Daca sunt gasite probleme, “programul” se intoarce in faza de dezvoltare pentru a fi remediate. Acest schimb intre programator si echipa de testare se poate face de un numar suficient de mare de ori pana cand toate problemele sa fie rezolvate. Pentru a evita situatia enumerata mai sus se poate folosi tehnica TDD prin care programatorul scrie teste inainte de a implementa o anumita functionalitate.

Ce inseama TDD?

TDD (Test Driven Development) este o abordare a dezvoltarii de software prin care testele sunt scrise inainte ca softul sa fie scris. Cu alte cuvinte, este o cale de a gândi prin prisma cerințelor sau a designului înainte de a ne apuca efectiv a scrie cod funcțional.Un alt punct de vedere este că TDD reprezintă o tehnică de programare al cărui scop este acela de a scrie cod curat care funcționează. Aceasta metoda contine 3 pasi:

  1. Se scrie un test care sa pice
  2. Se scrie minimul de cod necesar pentru a face testul sa treaca
  3. Se curata codul scris si se refactorizeaza daca este nevoie

La o prima analiza a acestei metode ar parea ca este o pierdere de timp prin faptul ca trebuie scrise si teste si astfel dezvoltarea software-ului este incetinita. Dar aici vine marele avantaj. Dupa cum observam in pasii anteriori, scrierea unui test care esueaza este in stransa legatura cu dezvoltarea. Astfel, dupa ce scriem suficient cod pentru a face testul sa mearga (sau pana cand credem ca este suficient sa treaca testul) trebuie sa testam din nou. Avand momentele de timp, cand codul functioneaza fara erori, foarte bine stabilite in timp este mult mai usor sa revii intr-o stare in care codul este functional si sa rescrii functionalitatea ce esueaza. Astfel, se castiga timpul pierdut cu debugging-ul si cu gasirea functionalitatilor care esueaza. Mereu testul ce esueaza este ultimul si prin urmare problema este la ultima functionalitate adaugata.

Concluzii

Test-driven development nu inlocuieste metoda traditionala de testare, dar defineste o modalitate de dezvoltare a codului in vederea limitarii greselilor. Mai mult, aceasta metoda aduce avantaje de timp pentru identificarea problemelor la introducerea functionalitatilor noi, permite dezvoltarea unui cod mai mic si mai usor de inteles si faciliteaza schimbul de cunostinte din cadrul unei echipe.

Cum pregatim proiectul?

In cadrul acestui laborator vom face de la 0 un proiect si vom urma toti pasii necesari pentru a dezvolta software folosind TDD.

  • Folosind interfata IntelliJ vom face un nou proiect denumit Laborator7

Pentru a separa partea de cod de partea de testare vom face 2 package-uri. In package-ul denumit Code vom adauga clasele ce vor fi folosite de software, iar in package-ul TestingModule vom adauga partea de testare.

  • Pentru a scrie prima functie de testare vom merge in package-ul TestingModule si vom face un nou fisier de tipul Java Class

  • In cadrul acestui fisier facem o clasa in interiorul careia ne vom scrie testele. Crearea unui test se realizeaza prin adnotarea unei metode cu @Test.

In cazul in care IntelliJ nu importa automat modulele necesare putem sa le adaugam manual asa cum este prezentat in poza. Desigur, putem adauga JUnit in IntelliJ asa cum am invatat si in laboratorul trecut.

Specificatiile proiectului

In cadrul laboratorului de astazi vom face un program denumit sugestiv SplitBill. Acesta va lua anumite sume de bani, persoane, procentul de tips si va returna o suma de bani ce trebuie data de fiecare persoana pentru achitarea notei.

Cerinta 1

Etapa 1: writing a failing test
  • Prima cerinta a proiectului este scrierea unui test getTotalAmountWithZeroTips(). In cadrul acestei functii vom lua o instanta a calculatorului si vom returna rezultatul unei metode getTotalAmount(double amount, double tips). Aceasta va adauga procentul de tips la suma si va returna suma totala ce trebuie platita, suma ce contine si tips-ul.

IntelliJ ne ajuta si ne face highlight pe ce nu exista. Astfel ne putem folosi de editor pentru a face clasa Calculator si a adauga in ea lucrurile necesare.

Primul din cei 3 pasi TDD a fost realizat: testul a fost scris. Dupa ce rulam acest test vom vedea faptul ca testul nu va trece intrucat noi nu avem implementata aceasta metoda in cadrul clasei. Urmatorul pas este scrierea celor mai simple linii pentru a face testul sa treaca.

Etapa 2: make the test pass

Avand in vedere ca testul nostru este conceput pentru cazul in care tips-ul este 0 ceea ce avem de facut este sa returnam suma initiala:

public class Calculator {
    public double calculate(double amount, double tips) {
        return amount;
    }
}

Daca rulam testul inca odata vom vedea ca acesta va trece:

Etapa 3: refactoring

Pentru testul curent, dat fiind faptul ca este foarte simplu, vom omite etapa refactoring considerand ca nu avem ce modifica.

Cerinta 2

Etapa 1: writing a failing test
  • A doua cerinta este scrierea unui test getTotalAmountWithTips(). De data aceasta avem ca date de intrare amount 78 si tips 10 si ne asteptam ca functia sa returneze 85.8.
@Test
public void getTotalAmountWithTips() {
    Calculator calculator = new Calculator();
    assertEquals(85.8, calculator.calculate(78, 10));
}
Etapa 2: make the test pass
public class Calculator {
    public double calculate(double amount, double tips) {
        if (tips == 0.0) {
            return amount;
        }
        else {
            var tipsAmount = amount / 100 * tips;
            return amount + tipsAmount;
        }
    }
}
Etapa 3: refactoring

De data aceasta putem face refactoring eliminand din codul redundant:

public class Calculator {
    public double calculate(double amount, double tips) {
        var tipsAmount = amount / 100 * tips;
        return amount + tipsAmount;
    }
}

Tips and Tricks

JUnit ne permite sa dam argumente functiilor de testare. Folosind adnotarea @ValueSource putem adauga un array de elemente pe care sa le folosim ca argumente.

    @ParameterizedTest
    @ValueSource(doubles = {0, 10, 100, 78, 20})
    public void getTotalAmountWithTips2(Double amount) {
        Calculator calculator = new Calculator();
        System.out.println("Amount " + amount + " with tips 10 = " + calculator.calculate(amount, 10));
    }

Folosind adnotarea @CsvSource putem da mai multe argumente functiilor de testare.

@ParameterizedTest
@CsvSource({
            "0, 0",
            "10, 11",
            "100, 110",
            "78, 85.8",
            "20, 22",
    })
    public void getTotalAmountWithTips3(Double amount, Double expected) {
        Calculator calculator = new Calculator();
        assertEquals(expected, calculator.calculate(amount, 10));
}

Este important de mentionat faptul ca atunci cand se folosesc adnotarile mentionate mai sus trebuie sa folosim @ParameterizedTest in loc de @Test.

Exercitii

Descarcati arhiva de aici: laborator7.zip.

Partea 1:

  1. Sa se scrie un failing test testWithMultipleAmounts ce va lua un array de amount-uri (double) si va returna suma ce contine si tipsul.
  2. Sa se scrie codul (si sa se refactorizeze daca este nevoie) pentru a face testul de la pasul anterior sa treaca.
  3. Sa se scrie un test ce verifica faptul ca daca exista sume negative sau procentul de tips este negativ atunci se arunca o eroare.
  4. Sa se scrie codul (si sa se refactorizeze daca este nevoie) ce face testul mentionat mai sus sa treaca.
  5. Sa se scrie un test pentru valorile de intrare amount = 100, tips = 10 si personNumber = 2 rezultatul fiind expected = 55.
  6. Sa se scrie codul necesar pentru a face testul mentionat mai sus sa treaca. (trebuie adaugat suport pentru un parametru in plus si anume numarul de persoane intre care trebuie impartita nota).
  7. Sa se scrie un test folosind adnotarea @CsvSource prin care sa se testeze 5 cazuri de intrare diferite modificand toti parametrii de intrare (lista de sume, tipsul si numarul de persoane).

Partea 2:

  1. Creati un proiect nou sau folositi scheletul din cadrul primei parti.
  2. Sa se scrie un test ce verifica daca suma numerelor dintr-un string gol este 0.
  3. Sa se implementeze functia ce face testul sa treaca (si sa se refactorizeze daca este nevoie).
  4. Sa se scrie o functie de test ce verifica faptul ca suma cu stringul de intrare “1 2 3 10 4 7” este 27. (numerele sunt separate prin exact un spatiu.)
  5. Sa se implementeze codul ce face testul de mai sus sa treaca (si sa se refactorizeze daca este nevoie).
  6. Sa se scrie o functie ce testeaza folosirea ”,” ca si delimitator in loc de spatiu.
  7. Sa se scrie codul ce face testul de mai sus sa treaca (si sa se refactorizeze daca este nevoie).
  8. Sa se scrie o functie de test ce permite folosirea literelor (mari si mici) si semnelor de punctuatie (,.?!:;) ca si delimitatori. Pot exista oricat de multi delimitatori intre numere.
  9. Sa se implementeze codul ce face testul de mai sus sa treaca (si sa se refactorizeze daca este nevoie).

Refactorizare: folositi regex

Resurse utile

icalc/laboratoare/laborator-07.txt · Last modified: 2022/05/11 20:52 by theodor.ungureanu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0