Skip to content

Latest commit

 

History

History
723 lines (481 loc) · 33 KB

File metadata and controls

723 lines (481 loc) · 33 KB
Tehtävien palautuksen deadline su 19.11. klo 23.59

Ohjausta tehtävien tekoon to ja pe klo 14-16 salissa B221

palautetaan GitHubin kautta

  • tee palautusta varten uusi repositorio tai käytä aikaisempien viikkojen repositoriotasi
  • palautusrepositorion nimi ilmoitetaan tehtävien lopussa olevalla palautuslomakkeella

Typokorjauksia & vinkkejä

Typot

Jos huomaat tehtävissä tai muussa materiaalissa kirjoitusvirheitä, paina sivulla olevaa kynä-symbolia:

korjaa virhe ja ehdota muutosta (nappi sivun alalaidassa):

luo pullrequest:

ja varmista vielä:

Korjausehdotuksesta tulee nyt pullrequest ja se päivittyy siinä vaiheessa sivulle kun joku kurssihenkilökunnasta hyväksyy sen.

Vinkit

Joskus ohtun laskareissa tulee vastaan kaikkea sekalaista ongelmaa joiden kanssa taisteleminen ei välttämättä ole parasta ajankäyttöä oppimiselle. Tätä varten kerätään opiskelijoilla vastaantulleita ongelmia ja ratkaisuja. Jos törmäät ongelmaan niin tarkista mahdolliset vinkit tai lisää oma vinkkisi tiedostoon 3-tips.md! Ongelman ei tarvitse olla vaikea eikä haittaa, jos ratkaisu tuntuikin ilmiselvältä.

Jos ongelma on pahempi, niin siihen liittyvän vinkin voi lisätä typo-korjauksen tapaan myös suoraan tähän laskarisivuun.

1. lisää gradlea: riippuvuuksien lisääminen

Hae kurssirepositorion https://github.com/mluukkai/ohjelmistotuotanto2017 hakemistossa https://github.com/mluukkai/ohjelmistotuotanto2017/koodi/viikko3/Kurssistatistiikka lähes tyhjä gradle-projektin runko.

  • mukana on kohta tarvitsemasi luokka Submission

HUOM: kun teet muutoksia gradlen konfiguraatioon, esim. lisäät projektiin plugineja ja riippuvuuksia, ei NetBeans huomaa lisäyksiä automaattisesti. Ongelma korjautuu kun reloadaat projektin:

Tehdään ohjelma, jonka avulla voit lukea kurssilla palauttamiesi tehtävien statistiikan osoitteesta https://studies.cs.helsinki.fi/ohtustats

Näet palauttamasi tehtävät sivulta https://studies.cs.helsinki.fi/ohtustats/students/011120775/submissions (vaihda 011120775 omaksi opiskelijanumeroksesi). Palvelin palauttaa tietosi json-muodossa

Tavoitteena on tehdä ohjelma, joka ottaa komentoriviparametrina opiskelijanumeron ja tulostaa palautettujen tehtävien statistiikan ihmisystävällisessä muodossa.

Ohjelmassa tarvitaan muutamaa kirjastoa eli riippuvuutta:

Kertaa nopeasti viime viikolta, miten gradle-projektin riippuvuudet määritellään. Tarvittaessa lisää tietoa löytyy Gradlen manuaalista

Liitä projektisi käännösaikaisiksi (compile) riippuvuuksiksi

  • Apache HttpClient Fluent API ja gson
  • löydät riippuvuuksien tiedot osoitteesta http://mvnrepository.com/
  • Ota molemmista uusin versio

Voit ottaa projektisi pohjaksi seuraavan tiedoston:

package ohtu;

import com.google.gson.Gson;
import java.io.IOException;
import org.apache.http.client.fluent.Request;

public class Main {

    public static void main(String[] args) throws IOException {
        // vaihda oma opiskelijanumerosi seuraavaan, ÄLÄ kuitenkaan laita githubiin omaa opiskelijanumeroasi
        String studentNr = "011120775";
        if ( args.length>0) {
            studentNr = args[0];
        }

        String url = "https://studies.cs.helsinki.fi/ohtustats/students/"+studentNr+"/submissions";

        String bodyText = Request.Get(url).execute().returnContent().asString();

        System.out.println("json-muotoinen data:");
        System.out.println( bodyText );

        Gson mapper = new Gson();
        Submission[] subs = mapper.fromJson(bodyText, Submission[].class);
        
        System.out.println("Oliot:");
        for (Submission submission : subs) {
            System.out.println(submission);
        }

    }
}

Tehtäväpohjassa on valmiina luokan Submission koodin runko. Gson-kirjaston avulla json-muotoisesta datasta saadaan taulukollinen Submission-olioita, joissa jokainen olio vastaa yhden viikon palautusta. Tee luokkaan oliomuuttuja (sekä tarvittaessa getteri ja setteri) jokaiselle json-datassa olevalle kentälle. Json-datan kenttää exercises vastaavan oliomuuttujan tyypi voi olla taulukko tai lista.

Tee kuitenkin ohjelmastasi tulostusasultaan miellyttävämpi, esim. seuraavaan tyyliin:

opiskelijanumero: 011120775

 viikko 1: tehtyjä tehtäviä yhteensä: 9, aikaa kului 3 tuntia, tehdyt tehtävät: 1 2 3 4 5 6 7 9 11 
 viikko 2: tehtyjä tehtäviä yhteensä: 6, aikaa kului 4 tuntia, tehdyt tehtävät: 1 2 3 6 7 8  

yhteensä: 15 tehtävää 7 tuntia

HUOM poista opiskelijanumerosi koodista ennen kuin pushaat koodin githubiin!

2. viikkomaksimit

Laajenna ohjelmaa siten, että se lisää tulostukseen myös kurssin nimen ja viikoittaiset tehtävien maksimimäärät.

Ohjelman tulostus voi olla esim. seuraavanlainen

Kurssi: Ohjelmistotuotanto, syksy 2017

opiskelijanumero: 011120775

 viikko 1: 
   tehtyjä tehtäviä yhteensä: 9 (maksimi 12), aikaa kului 3 tuntia, tehdyt tehtävät: 1 2 3 4 5 6 7 9 11 
 viikko 2: 
   tehtyjä tehtäviä yhteensä: 6 (maksimi 8), aikaa kului 4 tuntia, tehdyt tehtävät: 1 2 3 6 7 8  

yhteensä: 15 tehtävää 7 tuntia

Ohjelman tulee hakea kurssin json-muodossa olevat tiedot osoitteesta https://studies.cs.helsinki.fi/ohtustats/courseinfo

Hae tiedot samoin kuin haet opiskelijankin tiedot, eli käyttäen Apache HttpClientin metodia Request.Get ja muuta metodin palauttama ohjelmassa helpommin käsiteltävään muotoon Gson-kirjaston avulla.

3. kaikkien palautukset

Laajenna ohjelmaa siten, että se tulostaa myös kurssin kaikkien opiskelijoiden palautusten ja palautettujen tehtävien määrän, esim. seuraavasti:

Kurssi: Ohjelmistotuotanto, syksy 2017

opiskelijanumero: 011120775

 viikko 1: 
   tehtyjä tehtäviä yhteensä: 9 (maksimi 12), aikaa kului 3 tuntia, tehdyt tehtävät: 1 2 3 4 5 6 7 9 11 
 viikko 2: 
   tehtyjä tehtäviä yhteensä: 6 (maksimi 8), aikaa kului 4 tuntia, tehdyt tehtävät: 1 2 3 6 7 8  

yhteensä: 15 tehtävää 7 tuntia

kurssilla yhteensä 98 palautusta, palautettuja tehtäviä 1551 kpl

Ohjelman voi hakea json-muodossa olevat kurssistatistiikat osoitteesta https://studies.cs.helsinki.fi/ohtustats/stats

Huomaat, että edellisen tehtävän lähestymistapa, eli palvelimen vastausten parsiminen sopivan olion avulla ei toimi.

Voimme suorittaa parsiminen hyödyntämällä gsonin luokkaa JsonParser. Importtaa JsonParser ja luokka JsonObject koodisi:

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

Ole tarkkana importin kanssa, NetBeansin automaattisen importtauksen kanssa saatat vahingossa importata melkein samannimisen (JSONParser) luokan Javan oletuspakkauksista!

Parsiminen tapahtuu nyt seuraavasti:

    String statsResponse = ... // palvelimelta haettu opiskelijoiden palautusstatistiikka

    JsonParser parser = new JsonParser();
    JsonObject parsittuData = parser.parse(statsResponse).getAsJsonObject();

Palvelimelta haettu data on nyt parsittu ja talletettu JsonObject-oliona muuttujaan parsittuData. Pienellä mielikuvituksen käytöllä saat kysyttyä haluamasi datan oliolta.

Löydät tarvittaessa apua Gsonin api-kuvauksesta

Huomaa, että tähän tehtävään ei kannata juuttua, viikolla on vielä paljon tekemistä. Tämä tehtävä on kuitenkin hyvää harjoitusta ulkopuolisten kirjastojen käytön harjoitteluun.

4. lisää gradlea: jar joka sisältää kaikki riippuvuudet

  • tehdään äskeisen tehtävän projektista jar-tiedosto komennolla gradle jar
  • suoritetaan ohjelma komennolla java -jar build/libs/Kurssistatistiikka.jar
  • mutta ohjelma ei toimikaan, tulostuu:
$ java -jar build/libs/Kurssistatistiikka.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/client/fluent/Request
	at ohtu.Main.main(Main.java:21)
Caused by: java.lang.ClassNotFoundException: org.apache.http.client.fluent.Request
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 1 more

Mistä on kyse? Ohjelman riippuvuuksia eli projekteja Apache HttpClientin ja gson vastaavat jar-tiedostot eivät ole käytettävissä, joten ohjelma ei toimi.

Saamme generoitua ohjelmasta jar-tiedoston joka sisältää myös kaikki riippuvuudet gradlen shadow-pluginin avulla. Ota plugin käyttöön lisäämällä seuraava tiedoston build.gradle alkuun:

plugins {
  id 'com.github.johnrengelman.shadow' version '2.0.1'
}

Tutki komennon gradle tasks avulla, miten saat muodostettua riippuvuudet sisältävän jarrin. Shadow pluginin dokumentaatio kertoo myös oikean taskin jos et sitä muuten löydä.

Generoi jar ja varmista, että ohjelma toimii komennolla java -jar shadowilla_tehty_jarri.jar

Tutki, mitä taskit installShadow ja shadowDistZip tekevät.

Ennen komentojen toiminnan tutkimista kannattaa suorittaa gradle clean, joka poistaa hakemiston build.

5. Tutustuminen cucumberiin

Lue sivulla https://github.com/mluukkai/ohjelmistotuotanto2017/blob/master/web/cucumber.md oleva Cucumber-johdanto ja tee siihen liittyvät tehtävät

6. Kirjautumisen testit

Hae kurssirepositorion hakemistossa koodi/viikko3/LoginCucumber oleva NetBeans-projekti.

Tutustu ohjelman rakenteeseen. Piirrä ohjelman rakenteesta UML-kaavio.

Huomaa, että ohjelman AuthenticationService-olio ei talleta suoraan User-oliota vaan epäsuorasti UserDAO-rajapinnan kautta. Mistä on kysymys?

DAO eli Data Access Object on yleisesti käytetty suunnittelumalli jonka avulla abstrahoidaan sovellukselta se miten oliot on talletettu, ks. http://www.corej2eepatterns.com/Patterns2ndEd/DataAccessObject.htm

Ideana on, että sovellus "hakee" ja "tallettaa" User-oliot aina UserDAO-rajapinnan metodeja käyttäen. Sovellukselle on injektoitu konkreettinen toteutus, joka tallettaa oliot esim. tietokantaan tai tiedostoon. Se minne talletus tapahtuu on kuitenkin läpinäkyvää sovelluksen muiden osien kannalta.

Ohjelmaamme on määritelty testauskäyttöön sopiva InMemoryUserDao, joka tallettaa User-oliot ainoastaan muistiin. Muu ohjelma säilyisi täysin muuttumattomana jos määriteltäisiin esim. MySQLUserDao, joka hoitaa talletuksen tietokantaan ja injektoitaisiin tämä sovellukselle.

Kokeile ohjelman suorittamista (ohjelman tuntemat komennot ovat login ja new) ja suorita siihen liittyvät testit.

Tutki miten testien stepit on määritelty suoritettavaksi tiedostossa src/test/java/ohtu/StepDefs.java Huomioi erityisesti miten testit käyttävät testaamisen mahdollistavaa stubolioa käyttäjän syötteen ja ohjelman tulosteen käsittelyyn. Periaate tässä on täsmälleen sama kuin viikon 1 riippuvuuksien injektointiin liittyvissä esimerkeissä.

Lisää User storylle User can log in with valid username/password-combination seuraavat skenaariot ja määrittele niihin sopivat When ja Then -stepit:

    Scenario: user can not login with incorrect password
        Given command login is selected
        When  ...
        Then  ...

    Scenario: nonexistent user can not login to 
        Given command login is selected
        When  ...
        Then  ...

Tee stepeistä suoritettavat ja varmista että testit menevät läpi.

7. Uuden käyttäjän rekisteröitymisen testit

Tee User storylle A new user account can be created if a proper unused username and a proper password are given seuraavat skenaariot ja niille sopivat stepit:

Feature: A new user account can be created if a proper unused username and password are given

    Scenario: creation is successful with valid username and password
        Given command new user is selected
        When  ...
        Then  ...
    
    Scenario: creation fails with already taken username and valid password
        Given command new user is selected
        When  ...
        Then  ...

    Scenario: creation fails with too short username and valid password
        Given command new user is selected
        When  ...
        Then  ...

    Scenario: creation fails with valid username and too short password
        Given command new user is selected
        When  ...
        Then  ...

    Scenario: creation fails with valid username and password enough long but consisting of only letters
        Given command new user is selected
        When  ...
        Then  ...

    Scenario: can login with successfully generated account
        Given user "eero" with password "salainen1" is created
        And   command login is selected
        When  ...
        Then  ...  
  • käyttäjätunnuksen on oltava merkeistä a-z koostuva vähintään 3 merkin pituinen merkkijono, joka ei ole vielä käytössä
  • salasanan on oltava pituudeltaan vähintään 8 merkkiä ja sen tulee sisältää vähintään yksi numero tai erikoismerkki (vihje)

Tee stepeistä suoritettavia ja täydennä ohjelmaa siten että testit menevät läpi. Oikea paikka koodiin tuleville muutoksille on luokan AuthenticationService metodi invalid

HUOM skenaarioita kannattaa tehdä yksi kerrallaan, laittaen samalla vastaava ominaisuus ohjelmasta kuntoon. Eli ÄLÄ copypastea ylläolevaa kerralla feature-tiedostoon vaan etene pienin askelin. Jos yksi skenaario ei mene läpi, älä aloita uuden tekemistä ennen kuin kaikki ongelmat on selvitetty.

8. Spring jälleen kerran

Ennen kuin sovellus päästään käynnistämään, on se konfiguroitava:

public static void main(String[] args) {
    UserDao dao = new InMemoryUserDao();
    IO io = new ConsoleIO();
    AuthenticationService auth = new AuthenticationService(dao);
    new App(io, auth).run();
}

Muuta ohjelmaa siten, että sovelluksen konfigurointi hoidetaan Springin avulla (joko xml- tai annotaatioperustaisesti), ja main:iksi riittää:

public static void main(String[] args) {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("src/main/resources/spring-context.xml");
 
    App application = ctx.getBean(App.class);
    application.run();
}

Ohjeita löytyy viikon 2 laskareiden lisämateriaalista

9. WebLogin

HUOM: tätä tehtävää varten koneellasi on oltava Java 8

Tarkastellaan edellisestä tehtävästä tutun toiminnallisuuden tarjoamaa esimerkkiprojektia, joka löytyy kurssirepositorion hakemistossa koodi/viikko3/WebLogin

Sovellus on toteutettu Spark-nimisellä minimalistisella Web-sovelluskehyksellä. Spark on jo monille tuttu kurssilta Tietokantojen perusteet.

Hae projekti ja käynnistä se komennolla gradle run

Pääset käyttämään sovellusta avaamalla selaimella osoitteen http://localhost:4567

Sovellus siis toimii localhostilla eli paikallisella koneellasi portissa 4567.

Sovelluksen rakenne on suunnilleen sama kuin tehtävien 6-8 ohjelmassa. Poikkeuksen muodostaa pääohjelma, joka sisältää koodin, joka käsittelee sovelluksen eri sivuille tulevat pyynnöt. Tässä vaiheessa ei ole tarpeen tuntea sivupyyntöjä käsittelevää koodia kovin tarkasti. Katsotaan kuitenkin pintapuolisesti mistä on kysymys.

Sovelluksen juureen (polulle "/"), eli osoitteeseen http://localhost:4567 tulevat pyynnöt käsittelee mainista seuraava koodinpätkä:

    get("/", (request, response) -> {
        HashMap<String, String> model = new HashMap<>();
        model.put("template", "templates/index.html");
        return new ModelAndView(model, LAYOUT);
    }, new VelocityTemplateEngine());             

koodin oleellinen sisältö on se, että se pyytää muodostamaan osoitteessa templates/index.html olevan HTML-sivun ja palauttamaan sen käyttäjän selaimelle.

Sivun HTML-koodi on seuraava:

<h1>Ohtu App</h1>

<ul>
    <li><a href="login">login</a></li>
    <li><a href="user">register new user</a></li>
</ul>

kaikki get-alkuiset määrittelyt ovat samanlaisia, ne ainoastaan muodostavat HTML-koodin (jotka sijaitsevat hakemistossa templates) ja palauttavat koodin selaimelle.

post-alkuiset määrittelyt ovat monimutkaisempia, ne käsittelevät lomakkeiden avulla lähetettyä tietoa. Esim. käyttäjän kirjautumisyrityksen käsittelee seuraava koodi:

    post("/login", (request, response) -> {
        HashMap<String, String> model = new HashMap<>();
        String username = request.queryParams("username");
        String password = request.queryParams("password");
        
        if ( !authenticationService().logIn(username, password) ) {
            model.put("error", "invalid username or password");
            model.put("template", "templates/login.html");
            return new ModelAndView(model, LAYOUT);
        }
            
       response.redirect("/ohtu");
       return new ModelAndView(model, LAYOUT);
    }, new VelocityTemplateEngine());

Koodi pääsee käsiksi käyttäjän lomakkeen avulla lähettämiin tietoihin request-olion kautta:

    String username = request.queryParams("username");
    String password = request.queryParams("password");

Koodi käyttää metodikutsulla authenticationService() saamaansa AuthenticationService-olioa kirjautumisen onnistumisen varmistamiseen. Jos kirjautuminen ei onnistu, eli mennään if-haaraan, palataan kirjautumislomakkeelle. Lomakkeelle näytettäväksi liitetään virheilmoitus invalid username or password.

Tutustu nyt sovelluksen rakenteeseen ja toiminnallisuuteen. Saat sammutettua sovelluksen painamalla konsolissa ctrl+c tai ctrl+d.

10. Selenium, eli web-selaimen simulointi ohjelmakoodista

Web-selaimen simulointi onnistuu mukavasti Selenium WebDriver -kirjaston avulla. Edellisessä tehtävässä olevassa projektissa on luokassa ohtu.Tester.java pääohjelma, jonka koodi on seuraava:

public static void main(String[] args) {
    WebDriver driver = new ChromeDriver();

    driver.get("http://localhost:4567");
     
    WebElement element = driver.findElement(By.linkText("login"));
    element.click();

    element = driver.findElement(By.name("username"));
    element.sendKeys("pekka");
    element = driver.findElement(By.name("password"));
    element.sendKeys("akkep");
    element = driver.findElement(By.name("login"));
    element.submit();

    driver.quit();
}

Käynnistä websovellus edellisen tehtävän tapaan komentoriviltä. Varmista selaimella että sovellus on päällä.

Avaa toinen terminaali ja suorita siellä komento gradle browse joka on konfiguroitu suorittamaan luokan Tester metodin main koodi.

HUOM: osalla on ollut ongelmia seleniumin kanssa. Tänne on koottu joitain tapoja, miten ongelmia on saatu ratkaistua. Jos törmäät ongelmaan ja saat sen ratkaistua jollain em. dokumentissa mainitsemattomalla tavalla, lisää ohje dokumenttiin.

Seuraa avautuvasta selaimesta mitä tapahtuu.

Tester-ohjelmassa luodaan alussa selainta koodista käsin käyttävä olio WebDriver driver. Tämän jälkeen mennään selaimella osoitteeseen localhost:4567. Tämän jälkeen haetaan sivulta elementti, jossa on linkkiteksti login eli

    WebElement element = driver.findElement(By.linkText("login"));

Linkkielementtiä klikataan. Seuraavaksi etsitään sivulta elementti, jonka nimi on username, kyseessä on lomakkeen input-kenttä, ja ohjelma "kirjoittaa" kenttään metodia sendKeys() käyttäen nimen "pekka"

    element = driver.findElement(By.name("username"));
    element.sendKeys("pekka");

Mistä tiedetään, miten lomakkeen elementti tulee etsiä, eli miksi sen nimi oli nyt username? Elementin nimi on määritelty tiedostossa src/main/resources/templates/login.html:

Tämän jälkeen täytetään vielä salasanakenttä ja painetaan lomakkeessa olevaa nappia. Lopuksi tulostetaan vielä sivun lähdekoodi.

Ohjelma siis simuloi selaimen käyttöskenaarion, jossa kirjaudutaan sovellukseen.

Koodin seassa on kutsuttu sopivissa paikoin metodia sleep joka hidastaa selainsimulaation etenemistä siten että ihminenkin pystyy seuraamaan tapahtumia.

Muuta nyt koodia siten, että läpikäyt seuraavat skenaariot

  • epäonnistunut kirjautuminen: oikea käyttäjätunnus, väärä salasana
  • epäonnistunut kirjautuminen: ei-olemassaoleva käyttäjätunnus
  • uuden käyttäjätunnuksen luominen
  • uuden käyttäjätunnuksen luomisen jälkeen tapahtuva ulkoskirjautuminen sovelluksesta

HUOM0: jos törmäät ongelmiin tai ehkä jo ennen sitä lue alla olevat kolme huomautusta!

HUOM1: salasanan varmistuskentän (confirm password) nimi on passwordConfirmation

HUOM2:

Uuden käyttäjän luomisen kokeilua hankaloittaa se, että käyttäjänimen on oltava uniikki. Kannattanee generoida koodissa satunnaisia käyttäjänimiä esim. seuraavasti:

    Random r = new Random();
        
    element = driver.findElement(By.name("username"));
    element.sendKeys("arto"+r.nextInt(100000));

HUOM3:

Joskus linkin klikkaaminen Seleniumissa aiheuttaa poikkeuksen StaleElementReferenceException

Käytännössä syynä on se, että selenium yrittää klikata linkkiä "liian aikaisin". Ongelma on mahdollista kiertää klikkaamalla poikkeuksen tapahtuessa linkkiä uudelleen. Jos törmäät ongelmaan, voit ottaa koodiisi seuraavassa olevan apumetodin clickLinkWithText, joka suorittaa sopivan määrän uudelleenklikkauksia:

public class Tester {

    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();

        driver.get("http://localhost:4567");
        
        clickLinkWithText("register new user", driver);

        // ...
    }


    private static void clickLinkWithText(String text, WebDriver driver) {
        int trials = 0;
        while( trials++<5 ) {
            try{
                WebElement element = driver.findElement(By.linkText(text));
                element.click();
                break;           
            } catch(Exception e) {
                System.out.println(e.getStackTrace());
            }
        }
    }

Lisää asiasta esim. täällä

11. Web-sovelluksen testaaminen: Cucumber+Selenium

Pääsemme jälleen käyttämään cucumberia.

Projektissa on valmiina User storystä As a registered user can log in with valid username/password-combination kaksi eri feature-määrittelyä:

  • logging_in.feature ja
  • logging_in_antipattern.feature

Näistä ensimmäinen, eli logging_in.feature on tehty "hyvien käytäntöjen" mukaan ja jälkimmäinen eli logging_in_antipattern.feature on taas huonompi.

Huonommassa versiossa skenaarioiden stepeistä on tehty monikäyttöisemmät. Sekä onnistuneet että epäonnistuneen skenaariot käyttävät samoja steppejä ja eroavat ainoastaan parametreiltaan.

Paremmassa versiossa taas stepit ovat erilaiset, paremmin tilannetta kuvaavat:

Tästä seurauksena on se, että stepit mappaavia metodeja tulee suurempi määrä. Metodit kannattaakin määritellä siten, että ne kutsuvat testejä varten määriteltyjä apumetodeita, jotta koodiin ei tule turhaa toistoa:

    @When("^correct username \"([^\"]*)\" and password \"([^\"]*)\" are given$")
    public void username_correct_and_password_are_given(String username, String password) throws Throwable {
        logInWith(username, password);
    }

    @When("^correct username \"([^\"]*)\" and incorrect password \"([^\"]*)\" are given$")
    public void username_and_incorrect_password_are_given(String username, String password) throws Throwable {
        logInWith(username, password);
    } 

    private void logInWith(String username, String password) {
        assertTrue(driver.getPageSource().contains("Give your credentials to login"));
        WebElement element = driver.findElement(By.name("username"));
        element.sendKeys(username);
        element = driver.findElement(By.name("password"));
        element.sendKeys(password);
        element = driver.findElement(By.name("login"));
        element.submit();  
    }     

Vaikka siis kuvaavammin kirjoitetut stepit johtavatkin hieman suurempaan määrään mappayksestä huolehtivaa koodia, on stepit syytä kirjata mahdollisimman kuvaavasti ja huolehtia detaljeista mappaavan koodin puolella. Stepit mappaavien eri metodien samankaltainen koodi kannattaa ehdottomasti eriyttää omiin apumetodeihin kuten esimerkissäkin tapahtuu (metodit logInWith ja pageHasContent).

Testien konfiguraatioon liittyy vielä muutama detalji. Testit alustava luokka RunCukesTest on nyt seuraava:

@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty"})
public class RunCukesTest {
    @ClassRule
    public static ServerRule server = new ServerRule(4567);
}

Luokka määrittelee testeille ClassRule:n, eli joukon toimenpiteitä, jotka suoritetaan ennen kuin testien suoritus aloitetaan ja kun testien suoritus on ohi. Toimenpiteet määrittelevä luokka ServerRule näyttää seuraavalta:

public class ServerRule extends ExternalResource {
    
    private final int port;

    public ServerRule(int port) {
        this.port = port;
    }

    @Override
    protected void before() throws Throwable {
        Spark.port(port);
        UserDao dao = new UserDaoForTests();
        dao.add(new User("jukka", "akkuj"));
        Main.setDao(dao);
        Main.main(null);
    }

    @Override
    protected void after() {
        Spark.stop();
    }
    
}

Metodi before käynnistää web-sovelluksen ennen testien suorittamista (komento Main.main(null)). Web-sovellukselle myös injektoidaan testejä varten tehty UserDao-olio, jolle on lisätty yksi käyttäjä- ja salasana.

Metodi after sulkee web-sovelluksen testien päätteeksi.

Suorita nyt testit komennolla gradle test

Huomaa, että testit käynnistävät sovelluksen samaan porttiin kuin sovellus käynnistyy komennolla gradle run. Eli jos saat (erittäin pitkän) virheilmoituksen:

syynä on todennäköisesti se, että sovellus on päällä. Joudutkin sulkemaan sovelluksen testien suorittamisen ajaksi.

Jos haluat pitää sovelluksen päällä testatessasi, käynnistä se johonkin muuhun portiin, esim. komento PORT=4569 gradle run käynnistää sovelluksen porttiin 4569.

Voit nyt halutessasi poistaa testien huonon version eli tiedoston logging_in_antipattern.feature ja siihen liittyvät Java-stepit.

Lisää User storylle User can log in with valid username/password-combination seuraava skenaario ja määrittele sille sopivat When ja Then -stepit:

    Scenario: nonexistent user can not login to 
        Given login is selected
        When  ...
        Then  ...

Tee stepien nimistä kuvaavasti nimettyjä.

Protip jos et saa jotain testiä menemään läpi, kannattaa "pysäyttää" testin suoritus ongelmalliseen paikkaan lisäämällä stepin koodiin esim. rivi

try{ Thread.sleep(120000); } catch(Exception e){}  // suoritus pysähtyy 120 sekunniksi

ja tarkastella sitten ohjelman tilaa testin käyttämästä selaimesta.

12. Web-sovelluksen testaaminen osa 2

HUOM: saat testien suorituksen huomattavasti nopeammaksi käyttämällä ChromeDriverin sijaan HtmlUnitDriver:iä joka ns. headless eli käyttöliittymätön selain. Ohje HtmlUnitDriverin käyttöön täällä

HtmlUnitDriver vaikeuttaa testien debuggaamista, joten jos jotain ongelmia ilmenee, kannattanee debuggaamiseen käyttää ChromeDriveriä.

Tee User storylle A new user account can be created if a proper unused username and a proper password are given seuraavat skenaariot ja niille sopivat stepit:

Feature: A new user account can be created if a proper unused username and password are given

    Scenario: creation is successful with valid username and password
        Given command new user is selected
        When  a valid username "liisa" and password "salainen1" and matching password confirmation are entered
        Then  a new user is created

    Scenario: creation fails with too short username and valid password
        Given command new user is selected
        When  ...
        Then user is not created and error "username should have at least 3 characters" is reported   

    Scenario: creation fails with correct username and too short password
        Given command new user is selected
        When  ...
        Then user is not created and error "password should have at least 8 characters" is reported   

    Scenario: creation fails with already taken username and valid password
        Given command new user is selected
        When  ...
        Then user is not created and error "username is already taken" is reported 

    Scenario: creation fails when password and password confirmation do not match
        Given command new user is selected
        When  ...
        Then user is not created and error "password and password confirmation do not match" is reported   

Käyttäjätunnus ja salasana noudattavat samoja sääntöjä kuin tehtävässä 5 eli

  • käyttäjätunnuksen on oltava merkeistä a-z koostuva vähintään 3 merkin pituinen merkkijono, joka ei ole vielä käytössä
  • salasanan on oltava pituudeltaan vähintään 8 merkkiä ja sen tulee sisältää vähintään yksi numero tai erikoismerkki

Laajenna koodiasi siten, että testit menevät läpi.

13. Web-sovelluksen testaaminen osa 3

Tee User storylle A new user account can be created if a proper unused username and a proper password are given vielä seuraavat skenaariot ja niille sopivat stepit:

    Scenario: user can login with successfully generated account
        Given user with username "lea" with password "salainen1" is successfully created
        And   login is selected
        When  ...
        Then  ...  

    Scenario: user can not login with account that is not successfully created
        Given user with username "aa" and password "bad" is tried to be created
        And   login is selected
        When  ...
        Then  ...  

tehtävien kirjaaminen palautetuksi

tehtävien kirjaus:

palaute tehtävistä:

  • tee viikon 1 viimeisessä forkaamasi repositorioon jokin muutos
  • tee viime viikon tehtävän tapaan pull-request
    • anna tehtävistä palautetta avautuvaan lomakkeeseen
    • huom: jos teeh tehtävät alkuviikosta, voi olla, että edellistä pull-requestiasi ei ole vielä ehditty hyväksyä ja et pääse vielä tekemään uutta requestia