Tehtävien palautuksen deadline su 19.11. klo 23.59 Ohjausta tehtävien tekoon to ja pe klo 14-16 salissa B221
- tee palautusta varten uusi repositorio tai käytä aikaisempien viikkojen repositoriotasi
- palautusrepositorion nimi ilmoitetaan tehtävien lopussa olevalla palautuslomakkeella
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.
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:
- HTTP-pyynnön tekemiseen Apache HttpClient Fluent API
- json-muotoisen merkkijonon muuttaminen olioksi http://code.google.com/p/google-gson/
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!
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.
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.
- 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.
Lue sivulla https://github.com/mluukkai/ohjelmistotuotanto2017/blob/master/web/cucumber.md oleva Cucumber-johdanto ja tee siihen liittyvät tehtävät
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.
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.
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
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.
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ä
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.
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.
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 kirjaus:
- Kirjaa tekemäsi tehtävät osoitteeseen https://studies.cs.helsinki.fi/ohtustats
- huom: tehtävien palautuksen deadline on su 19.11. klo 23.59
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