Tämä kurssi on jo päättynyt.

Harjoitus (PAKOLLINEN): (Yksikkö)testaus ja CI

Tämän viikon tavoitteena on harjoitella QtTest-yksikkötestauskehykseen ja sen avulla harjoitella annetun luokan yksikkötestausta. Tämän ohella tutustutaan kurssin CI-ympäristön käyttöön. Huomaa, että vaikka harjoitus voi tuntua pitkältä, voit keskittyä yhteen asiaan kerrallaan.

Harjoituksen pakollisena osana teet kohdat Testiprojekti ja GitLabCI. Palautus tehdään versionhallinnan avulla. Muista tehdä commit riittävän usein!

Tehtävän vaatimat tiedostot saat nyhtämällä versionhallinnasta. Ne sijaitsevat repositoriossa student_template_project https://course-gitlab.tuni.fi/tie-0240x-ohjelmointi-3-programming-3-2020-2021/student_template_project. Se tulee laittaa paikalliseen repositorioosi remoteksi. Tässä harjoituksessa tarvitsemasi koodit ovat hakemistossa EX3. Huomaa, sinulla ei ole oikeuksia päivittää repositoriota.

  • .gitlab-ci.yml: koko repositorion juuressa. yaml-tiedosto, jossa CI/CD-konfiguraatio
  • EX3:
    • WelcomeToTampere: alkutekijöissään oleva ohjelmistoprojekti
      • WelcomeToTampere.pro: projektin projektitiedosto
      • baddate.cc, baddate.hh, date.cc, date.hh, main.cc, morottaja.cc, morottaja.hh: kooditiedostot
    • UnitTest: projektin yksikkötestiprojekti
      • UnitTests.pro: yksikkötestiprojektin projektitiedosto
      • Morottaja: morottaja-komponentin testikoodi
        • Morottaja.pro: yksikkötestiprojekti projektitiedosto
        • morottajatest.cc: testikoodi morottaja-komponentille

Huom! Testattavassa luokassa Date saattaa olla bugeja (joko koodissa tai rajapinnan dokumentaatiossa/suunnittelussa). Näiden bugien korjaaminen ei kuitenkaan ole tämän harjoituksen tarkoitus.

Testiprojekti

Lisää UnitTest iin projekti Date päivämääräluokan yksikkötestausta varten right click->New Subproject-> Other Project-> Qt Unit Test.

Laita lähdekooditiedostot mukaan yksikkötestiprojektiin lisäämällä niistä tieto Date.pro -tiedostoon.

SOURCES +=
HEADERS +=

Jotta testeissä tarvittavat tiedostot löytyvät käännettäessä, lisää tiedostoon vielä

INCLUDEPATH +=
DEPENDPATH  +=

Katso mallia Morottaja.pro tiedostosta.

Projektin kääntäminen ja ajaminen

Kun käännät (build) ylimmän tason projektin, QtCreator kääntää sekä itse "sovelluksen" että yksikkötestit. Valitsemalla aliprojektin ja sieltä ajamisen (run), ajaa QtCreator valitusta aliprojektista riippuen joko itse "sovelluksen" tai yksikkötestit.

Yksikkötestien kirjoittaminen

Qt:n yksikkötestikehyksessä jokainen testitapaus kirjoitetaan testiluokkaan omaksi jäsenfunktiokseen. Testikehys kutsuu näitä jäsenfunktioita automaattisesti järjestyksessä. (Qt:n ohjesivu testauskehykseen löytyy täältä .)

Yksikkötestauksessa idea on, että jokainen testitapaus testaa yhtä tiettyä luokan ominaisuutta tai jäsenfunktiota (monimutkaisempien jäsenfunktioiden testaus kannattaa jakaa useaan testitapaukseen). Hyöty useaan tapaukseen jakamisessa on, että virheen löytyessä testiajon tulostuksesta näkee tarkemmin, missä virhe sattui. Lisäksi testikehys toimii niin, että jokainen yksittäinen testitapaus keskeytyy ensimmäiseen siinä huomattuun virheeseen (eikä kyseisessä jäsenfunktiossa olevia mahdollisia muita testejä ajeta), mutta itse testiajo jatkuu kuitenkin eli loppujakin testijäsenfunktioita kutsutaan.

Testitapausjäsenfunktioiden kirjoittamisessa kannattaa taas käyttää hyväkseen QtCreatorin apuja. Kirjoita jäsenfunktion esittely luokan esittelyyn (harjoituksessa luokka DateTest) ja sitten tuota automaattisesti jäsenfunktion runko valitsemalla oikea nappi > Refactor > Add definition outside class.

Testitapausten kirjoittaminen

Hyvien testitapausten valinnassa on hyvä tilaisuus käyttää mielikuvitustaan ja luovuuttaan (tietysti rautaisella analyysilla maustettuna). Testattavassa luokassa ei ole kylvettynä tässä vaiheessa tarkoituksellisia bugeja, mutta koska sitä ei ole koskaan testattu kattavasti, on lähes varmaan että bugeja löytyy... :-)

Testitapauksia kannattaa usein kirjoittaa dataohjatusti. Testiluokassa on esimerkkinä muutama testitapaus havainnollistamassa kehyksen käyttöä. Lyhyesti ideana on siis ensin luoda päiväysolio, kutsua sille testattavaa operaatiota ja sitten varmistua siitä, että operaation tulos on haluttu. Tämä testaus tehdään testikehyksen omilla testauskomennoilla (QCOMPARE, QVERIFY, QFAIL), jotta virheet saadaan kirjattua. Testausmakrojen dokumentaatio löytyy täältä.

Jos samaa testitapausta halutaan ajaa usealla eri testidatalla (tyypillistä), testikehys antaa tähän mahdollisuuden. Tällöin itse testitapaus kirjoitetaan edelleen omaksi jäsenfunktiokseen. Tämän lisäksi kirjoitetaan toinen jäsenfunktio päätteellä _data, ja tämä jäsenfunktio tuottaa testimatriisin, jossa halutut testitapaukset ovat. Tästä on esimerkkinä alla valmis testitapaus viikonpaiva. Jäsenfunktio viikonpaiva_data ensin määrittää testitapauksen "syötteiden" tyypit ja nimet (QTest::addColumn), ja sen jälkeen luo testimatriisin, jossa jokainen rivi on oma nimetty testitapauksensa (QTest::newRow). Varsinaista testijäsenfunktiota viikonpaiva kutsutaan automaattisesti jokaiselle testimatriisin riville. Jäsenfunktio lukee alussa testimatriisin syötedatan (QFETCH) ja sitten suorittaa normaalin testin. Huomaa, että QFETCH luo automaattisesti testisyötteen nimisen muuttujan jäsenfunktioon.

void Unittest::weekday()
{
   // This method tests a row of the test matrix generated by the method weekday_data.
   // It is called automatically for each row of the matrix

   // Fetching data from the matrix, variables are created automatically
   QFETCH(unsigned int, day);
   QFETCH(unsigned int, month);
   QFETCH(unsigned int, year);
   QFETCH(Date::Weekday, weekday);

   // Performing the test
   Date d(day, month, year);
   QVERIFY2(d.giveWeekday() == weekday, "Wrong weekday");
}

void Unittest::weekday_data()
{
    // This method defines the test matrix for the weekday test and generates the desired test cases there

    // Defining columns for the test matrix (types and names)
    QTest::addColumn<unsigned int>("day");
    QTest::addColumn<unsigned int>("month");
    QTest::addColumn<unsigned int>("year");
    QTest::addColumn<Date::Weekday>("weekday");

    // Generating test cases for the test matrix, 3u etc. are needed since the type is unsigned
    QTest::newRow("today")      << 3u  << 2u  << 2014u << Date::MONDAY;
    QTest::newRow("last Christmas") << 24u << 12u << 2013u << Date::TUESDAY;
    QTest::newRow("next May Day")  << 1u  << 5u  << 2014u << Date::THURSDAY;
    QTest::newRow("end of the year")<< 31u << 12u << 2013u << Date::TUESDAY;
    QTest::newRow("new year")  << 1u  << 1u  << 2014u << Date::WEDNESDAY;
}

Jotta dataohjattuja testitapauksia voi tehdä, jossain kohtaa lähdekooditiedostoa tulee olla määriteltynä Q_DECLARE_METATYPE(Date::Weekday);. Tämän testin lisäksi ainakin yksi lisätesti tulee kirjoittaa.

GitLabCI

Tietovaraston juuressa on tiedosto .gitlab-ci.yml. Sen tehtävänä on konfiguroida GitLabCI:n toimintaa. Lisätietoa CI/CD:stä sekä .gitlab-ci.yml:n konfiguroinnista löydät helpistä .

Putken konfiguroinnista

Putken konfiguroinnin keskeinen elementti on ns. jobi (engl. job). Putkessa voi olla useita jobeja. Jobit

  • ovat ylimmän tason käsitteitä putken sisällöstä
  • ovat nimeltään mielivaltaisia
  • sisältävät aina vähintään script lausekkeen
  • määritellessä määritellään myös rajoitteet, joiden puitteissa ne ajetaan
  • lukumäärää ei ole rajoitettu

Esimerkki jobista:

build_EX1:
  script:
    - cd EX1/DesignByContract
    - qmake
    - make
    - make clean

Jobia määritellessä käytetään joukkoa parametreja, jotka määrittävät, miten jobi käyttäytyy. Keskeisimmät:

  • tags: käytetään määrittämään, millä ns. runnerilla jobi ajetaan GitLabissa. Tällä kurssilla aina qt.
  • image: kertoo, mitä Docker imagea jobin ajamisessa käytetään. Tällä kurssilla määritelty valmiiksi.
  • stage: kertoo mihin pipelinen vaiheeseen jobi liittyy. Nämä määritellään koko konfiguraatiolle. Tässä tehtävässä staget ovat build ja test nimiään vastaaviin tehtäviin.
  • artifacts: määrittelee, mitkä tiedostot ja kansiot, jotka halutaan pitää tallessa seuraaville jobeille tai esim. tarkastelua varten. Voidaan määritellä säästettävän materiaalin polun paths: lisäksi mm. expire_in tallennetun materiaalin poistamiseksi tietyn ajanjakson jälkeen
  • dependencies: oletuksena kaikki tallessa pidetyt tiedostot ja kansiot välitetään jobeille, mutta dependencies avulla voidaan listata ne jobit, joiden materiaali halutaan jobille välittää. Kätevä kääännös- ja testijobien välillä.

Daten testien CI

Avaa tiedosto editoidaksesi sitä. Tavoitteena on lisätä testien ajaminen osaksi CI-putkea. Huomaat, että tällä hetkellä mukana putkessa on mukana vaiheet viikkoharjoitusten käännöksille sekä Morottajan yksikkötesteille. Tutustu tiedostoon ja lisää sinne Daten yksikkötestit.

Tiedostosi syntaksin oikeellisuuden voit tarkistaa GitLab CI:n linterillä https://course-gitlab.tuni.fi/tie-0240x-ohjelmointi-3-programming-3-2020-2021/reposi_nimi/-/ci/lint , jossa reposi_nimi vaihdetaan oman työvarastosi pelkkä nimi. Linter on työkalu, joka tarkistaa määrämuotoisesta tiedostosta (esim. yaml, kooditiedosto), että se noudattaa formaatin vaatimaa syntaksia.

Palautus

Työ palautetaan viime viikolta tuttuun tapaan tagaamalla. Tagin pitää alkaa EX3_submission.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Palautusta lähetetään...