Arvutiteaduse instituut
  1. Kursused
  2. 2024/25 kevad
  3. Objektorienteeritud programmeerimine (LTAT.03.003)
EN
Logi sisse

Objektorienteeritud programmeerimine 2024/25 kevad

  • Kodutööd ja praktikumid
  • Loengud
  • Kursuse korraldus
  • IDE juhend
  • Süvendusrühm
  • Silumisest

Lisamaterjalid 10

Testide kirjutamine

Projektide käsitsi testimine võib olla tüütu ja aeganõudev. Selle protsessi automatiseerimiseks eksisteerib paljusid raamistikke, nagu JUnit, Selenium, TestNG, Mockito ja Cucumber. Praegu keskendume neist kahele: JUnit ja Mockito. Neid on mugav kasutada koos üksustestimiseks (unit testing). Seda tüüpi testimine kontrollib kindlaid koodi osi, mis on Java kontekstis klassid ja meetodid. Üksustestide kasutamine ei garanteeri, et kõik töötab nii nagu ettenähtud, aga need annavad kindlust.

Loome uue Java projekti, mis kasutab Mavenit ja kus on linnuke „Add sample code“ ees:

Teste kirjutame klassile Rahakott, mis asub kausta src/main/java/org.example uues failis Rahakott.java. Sellel on isendiväli raha ja neli meetodit. Esimene meetod lisaRaha suurendab raha ja osta vähendab seda. Raha tagastamiseks ja väljastamiseks eksisteerivad meetodid getRaha ja väljastaRaha. Erind visatakse, kui ostuks pole piisavalt raha või lisatav raha on negatiivne.

public class Rahakott {
    private double raha;

    public Rahakott(double raha) {
        this.raha = raha;
    }

    public void lisaRaha(double summa) throws IllegalArgumentException {
        if (summa <= 0) {
            throw new IllegalArgumentException("Lisatav summa peab olema üle nulli!");
        }
        raha += summa;
    }

    public void osta(double ostusumma, boolean sooduskupong) throws IllegalArgumentException {
        if (ostusumma < 0) {
            throw new IllegalArgumentException("Ostmisega ei tule raha juurde!");
        }

        if (sooduskupong) {
            ostusumma -= 0.20 * ostusumma;
        }
        if (raha - ostusumma < 0) {
            throw new IllegalArgumentException("Selleks ostuks pole piisavalt raha!");
        }

        raha -= ostusumma;
        väljastaRaha();
    }

    public void väljastaRaha() {
        System.out.println(raha);
    }

    public double getRaha() {
        return raha;
    }
}

JUnit 5-l on suur hulk annotatsioone, mis aitavad teste kirjutada:

  • @Test – märgib, et meetod on test;
  • @DisplayName – muudab testi nime;
  • @Timeout – seab maksimaalse testi jooksutamise aja;
  • @Disabled – keelab testi jooksutamise;
  • @BeforeEach ja @AfterEach – märgivad, et meetod käivitatakse vastavalt enne või pärast igat testi;
  • @BeforeAll ja @AfterAll – märgivad, et meetod käivitatakse vastavalt enne või pärast kõiki klassi teste;
  • @TestMethodOrder ja @TestClassOrder – nende abil saab seada testide käivitamise järjekorda.

JUnitil on ka väga palju klasse, aga algeliseks üksustestimiseks piisab klassist org.junit.jupiter.api.Assertions, mille meetodid keskenduvad väärtuste võrdlemisele:

  • assertEquals – kontrollib, kas kahe sama tüüpi argumendi väärtused on võrdsed;
  • assertSame – kontrollib, kas kaks argumenti on sama isend;
  • assertNull – kontrollib, kas argument on null;
  • assertTrue – kontrollib, kas argumendiks antud tõeväärtus on tõene;
  • assertThrows – kontrollib, kas visatakse kindel erind ja tagastab selle.

Igal võrdlemise meetodil eksisteerib vastand, näiteks assertNotNull kontrollib, et argument ei oleks null. Lisaks võib anda nendele meetoditele lisaargumendiks sõne, mis kuvatakse kontrolli ebaõnnestumisel.

JUniti kasutamiseks peab täiendama faili pom.xml. Selleks ava vastav fail ja lisa JUnit: Code → Generate... või ALT + INSERT. Avanenud menüüst vali Dependency ja sisesta otsinguribale org.junit.jupiter:junit-jupiter-engine ning vajuta Add:

Tulemusena peaks tekkima faili pom.xml read:

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.8.1</version>
    </dependency>
</dependencies>

Lõpuks vajuta Maveni sünkroniseerimise nupule paremal üleval:

Esimesteks testideks loome kausta src/test/java paketi org.example ja sellesse faili RahakottTest.java. Klassil RahakottTest on meetod enneTesti, millega luuakse enne igat testi uus klassi Rahakott isend ja millel on 100 ühikut raha. Esimene test lisaRaha lisab rahakotti 10 ühikut ja meetod assertEquals kontrollib, kas rahakotis on kokku 110 ühikut. See katab olukorra, kus lisatav summa pole null või väiksem:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class RahakottTest {
    Rahakott rahakott;

    @BeforeEach
    void enneTesti() {
        rahakott = new Rahakott(100);
    }

    @Test
    void testiLisaRaha() {
        rahakott.lisaRaha(10);
        assertEquals(rahakott.getRaha(), 110);
    }
}

Järgnev test lisaRahaNegatiivne kontrollib olukorda, kus rahakotti lisatav summa on negatiivne. Meetodi lisaRaha erindi viskamist saab tuvastada meetodiga assertThrows, mis ka tagastab konkreetse erindi. Selle sisu, mida kontrollitakse meetodiga assertEquals, saamiseks eksisteerib meetod getMessage.

@Test
void testiLisaRahaNegatiivne() {
    Exception erind = assertThrows(IllegalArgumentException.class,
            () -> rahakott.lisaRaha(-30));

    assertEquals("Lisatav summa peab olema üle nulli!", erind.getMessage());
}

Programmi töö võib sõltuda mitmest tegurist, sealhulgas andmebaasidest ja API-dest. Üksustestides üldiselt välditakse selliseid sõltuvusi asendades neid nii-öelda imitatsioonidega, mida võimaldab klassi org.mockito.Mockito meetod mock. Imiteeritud klassi meetodid saavad tagastada väärtusi ja visata erindeid kasutades meetodit when, mille argumendiks on kutsutav meetod ja selle argumendid. Need võivad olla näiteks konkreetsed väärtused või mingi täisarv kasutades klassi org.mockito.ArgumentMatchers meetodit anyInt või ükskõik mis väärtus kasutades meetodit any. Meetod thenReturn tagastab mingi väärtuse ja thenThrow viskab erindi. Meetodiga verify kontrollitakse mitu korda on kindlat meetodit kutsutud. Kui kõiki klassi meetodeid pole vaja imiteerida, saab kasutada meetodit spy. Objekti funktsionaalsus jääb puutumata, aga sellel saab ikka kasutada eelnevalt tutvustatud meetodeid.

Mockito kasutamiseks on vajalik lisada faili pom.xml org.mockito:mockito-core ja org.apache.maven.plugins:maven-surefire-plugin (vt JUniti lisamise juhendit):

Lisaks nendele on vajalik enne </project> lõppu ka:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <argLine>
                    -javaagent:${settings.localRepository}/org/mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar
                    -Xshare:off
                </argLine>
            </configuration>
        </plugin>
    </plugins>
</build>

Lisame klassi RahakottTest muutujad võltsRahakott ja spioonRahakott, mis kasutavad meetodeid mock ja spy. Test ostaVõlts muudab meetodit getRaha nii, et see tagastaks igal kutsel ujukomaarvu 40.0. Seejärel kutsutakse välja klassi Rahakott meetod osta ja kontrollitakse, kas seda kutsuti välja täpselt üks kord kasutades meetodit verify. Lõpuks võrreldakse meetodiga assertEquals, kas meetod getRaha tagastus on võrdne 40-ga.

import static org.mockito.Mockito.*;


class RahakottTest {
    Rahakott rahakott;
    Rahakott võltsRahakott;
    Rahakott spioonRahakott;

    @BeforeEach
    void enneTesti() {
        rahakott = new Rahakott(100);
        võltsRahakott = mock(Rahakott.class);
        spioonRahakott = spy(rahakott);
    }

   ...

   @Test
    void ostaVõlts() {
        when(võltsRahakott.getRaha()).thenReturn(40.0);

        võltsRahakott.osta(10, false);
        verify(võltsRahakott, times(1)).osta(anyDouble(), anyBoolean());

        assertEquals(võltsRahakott.getRaha(), 40);
    }
}

Testis ostaSpioon, mis kasutab muutujat spioonRahakott, tehakse kindlaks meetodiga verify, kas peale ostu on meetodeid osta ja väljastaRaha kutsutud täpselt üks kord. Lisaks kontrollitakse ostujärgset raha vähenemist kümne võrra meetodiga assertEquals.

@Test
void ostaSpioon() {
    spioonRahakott.osta(10, false);
    verify(spioonRahakott, times(1)).osta(anyDouble(), anyBoolean());
    verify(spioonRahakott, times(1)).väljastaRaha();

    assertEquals(spioonRahakott.getRaha(), 90);
}

Enesekontroll

  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused