Impariamo ad usare The Play Framework - III parte
Test driven development
All'inizio di questa serie di appuntamenti abbiamo deciso di realizzare un'applicazione "vera", ciò vuol dire che durante il ciclo di vita del progetto dovremo utilizzare tutte quelle accortezze che ci permetteranno di garantire, entro un certo limite, la correttezza e l'aderenza ai requisiti concordati con un ipotetico cliente. Il modello di sviluppo denominato Test Driven Development (più brevemente TDD) è nato con il nobile scopo di migliorare sensibilmente la qualità di ogni rilascio, favorendo allo stesso tempo altre pratiche agili di gestione dei progetti software come refactoring, cicli brevi di sviluppo, integrazione continua.
Non ci soffermeremo sul perché è utile e necessario utilizzare TDD, ci limiteremo ad utilizzarlo tramite gli strumenti che Play ci mette a disposizione: JUnit (per la creazione di test unitari) e Selenium (test funzionali).
Model
In Play il modello sfrutta le Java Persistence API (JPA) tramite Hibernate.
Una particolarità di Play, presa in prestito da Ruby On Rails e tutti i framework ad esso ispirati, è che non c'è uno strato DAO (Data Access Object): tutti i metodi di query e aggiornamento vanno implementati nelle classi stesse del modello. In realtà è sempre possibile suddividere queste funzionalità su più strati... ma non sarebbe conforme ai principi fondanti proprio di Ruby On Rails e Play stesso: Keep It Simple e Don't Repeat Yourself.
I più esperti di voi si staranno chiedendo "Ok, i metodi dei DAO sono direttamente nel modello, ma le transazioni chi le gestisce?". La risposta è che Play utilizza unl design pattern denominato Open Session in View in sostanza la transazione inizia con la richiesta HTTP e finisce quando la risposta è stata inviata al client, a meno di eccezioni che venissero lanciate durante questo flusso di esecuzione. Quindi possiamo dare per scontato che, ovunque ci si trovi nell'arco di una elaborazione, avremo a disposizione una transazione.
Le classi del modello estendono la classe play.db.jpa.Model, che fornisce di default una chiave surrogata id di tipo java.lang.Long, oltre a tutta una serie di metodi di utilità per interagire con JPA.
La classi del nostro modello per il momento saranno solo due, User e Activity, e vanno create nella directory app/models.
app/models/User.java:
package models;
import javax.persistence.Entity;
import play.db.jpa.Model;
@Entity
public class User extends Model {
private String email;
private String password;
private String fullname;
public User(String email, String password, String fullname) {
this.email = email;
this.password = password;
this.fullname = fullname;
}
...[metodi set/get omessi]
app/models/Activity.java:
package models;
import java.util.Calendar;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import play.db.jpa.Model;
@Entity
public class Activity extends Model {
@ManyToOne
private User user;
private String type;
private Calendar date;
private int lengthMinutes;
public Activity() {
}
public Activity(User user, String type, Calendar date, int lengthMinutes) {
super();
this.user = user;
this.type = type;
this.date = date;
this.lengthMinutes = lengthMinutes;
}
...[metodi set/get omessi]
Da notare:
- i metodi set/get sono stati omessi per brevità;
- le classi sono annotate con @Entity per indicare a JPA che dovranno essere salvate sul database;
- la property "user" di Activity è annotata con @ManyToOne per indicare che la relativa tabella avrà una foreign key verso la tabella User-
Sulla dashboard dell'utente abbiamo deciso di mostrare l'elenco delle attività svolte dall'utente collegato, quindi dovremo realizzare una query ad hoc. Prima di cimentarci con questa query dobbiamo creare un test che ne verifichi il funzionamento. Per prima cosa bisogna creare dei dati di test in test/data.yml:
User(bob):
email: bob@example.com
password: secret
fullname: Bob
isAdmin: false
User(john):
email: john@example.com
password: secret
fullname: John
isAdmin: false
Activity(activity_bob_01):
user: bob
type: Corsa
date: 2010-03-01
lengthMinutes: 20
Activity(activity_bob_02):
user: bob
type: Stretching
date: 2010-03-01
lengthMinutes: 45
Activity(activity_john_01):
user: john
type: Corsa
date: 2010-02-28
lengthMinutes: 35
Poi bisogna aggiungere questi metodi di test a test/BasicTest.java:
@Before
public void setup() {
Fixtures.deleteAll();
Fixtures.load("data.yml");
}
public static User getBobUser() {
User user = User.find("byEmail", "bob@example.com").first();
return user;
}
@Test
public void test_getActivitiesByUser() {
User user = getBobUser();
List<Activity> result = Activity.getActivitiesByUser(user.id);
assertNotNull(result);
assertTrue(result.size() >= 2);
}
Da notare:
- il metodo setup carica i dati di test prima di eseguire ogni singolo test, è quindi annotato con @Before (annotazione di a JUnit);
- getBobUser() verrà utilizzato per altri test, serve essenzialmente per caricare l'utente bob@example.com per utilizzarlo nei test;
- il test vero e proprio è eseguito in test_getActivitiesByUser();
Non essendoci ancora il metodo Activity.getActivitiesByUser(), il test fallirà:
- far partire Play con il comando:
play test
- aprire l'indirizzo:
- selezionare BasicTest.java e cliccare su "Start!"
Un messaggio d'errore ci informerà che il test è fallito perché il metodo sotto esame non esiste ancora! Per farlo funzionaro dovremo aggiungerlo alla
classe app/models/Activity.java:
public static List<Activity> getActivitiesByUser(long userId) {
return find("from Activity a where a.user.id = ? order by a.date desc", userId).fetch();
}
Dopo questa modifica il test tornerà ad avere successo.
Controller
Nella scorsa puntata abbiamo già incontrato, seppur brevemente, il controller app/controllers/Application.java e una delle sue view, la pagina app/views/Application/index.html. Riprendiamo in mano questi due file, e aggiorniamoli con le modifiche riportate qui sotto.
app/controllers/Application.java:
package controllers;
import java.util.*;
import models.*;
import play.data.validation.*;
import play.mvc.*;
public class Application extends Controller {
public static User getLoggedUser() {
// FIXME usare l'utente che ha fatto login
return User.find("byEmail", "bob@example.com").first();
}
@Before
static void addDefaults() {
renderArgs.put("user", getLoggedUser());
}
public static void index() {
User user = (User) renderArgs.get("user");
List<Activity> activities = Activity.getActivitiesByUser(user.id);
renderArgs.put("activities", activities);
render();
}
}
Note:
- il metodo getLoggedUser() dovrebbe restituire l'utente correntemente collegato, ma al momento carica semplicemente l'utente "bob@example.com". Vedremo nelle prossime puntate come aggiungere un meccanismo di autenticazione "vero";
- l'annotazione @Before (ATTENZIONE: dal package play.mvc e non org.junit!) sul metodo addDefaults() indica che esso va eseguito prima di ogni altra azione. Le azioni, come abbiamo visto, sono i metodi public static void.
- nell'azione index() carichiamo le attività dell'utente e le mettiamo a disposizione della view nella variabile "activities".
Vediamo ora le modifiche da apportare alla view corrispondente, cioè app/views/Application/index.html:
#{extends 'main.html' /}
#{set title:'Home' /}
Benvenuto su GymTrack!<br />
<small>Utente: <em>${user.fullname}</em></small>
<table>
<thead>
<tr>
<th>Data</th>
<th>Tipo di attività</th>
<th>Durata</th>
</tr>
</thead>
<tbody>
#{list items:activities, as:'act'}
<tr>
<td>${act.id}</td>
<td>${act.type}</td>
<td>${act.lengthMinutes}</td>
</tr>
#{/list}
</tbody>
</table>
Ecco quindi come apparirà la nuova dashboard dopo questi "trattamenti", per vederla dovete far partire il server Play in modalità "test" con il comando:
play test
e collegarvi all'indirizzo:

Sorgenti
Nell'elenco degli allegati (vedi qui sotto) potete trovare un archivio zip contenente i sorgenti del progetto. Per provarlo è sufficiente:
- estrarre i file in una directory;
- aprire una console (dos su windows o xterm su linux/unix);
- digitare il comando
play test - collegarsi al solito indirizzo
http://localhost:9000/
| Allegato | Dimensione |
|---|---|
| File di progetto | 29.85 KB |
- Aggiungi un commento
- 1227 letture
-
