JUnit來測試Applicaiton.
Model的連帶處理考量
從上回簡單的訊息表示Model(MsgData),這次我們再加上一個新的使用者情報Model(PersonData),這樣子顯示訊息時
不僅可以顯示使用者名稱,通常是使用SQL的JOIN句子,來互相關連,不過在Play!裡是使用JPA,Data並不是SQL table,
都是作為物件永續化管理,Model的關聯也是被設計成物件和物件的互相關聯。
首先在「models」裡作成PersonData類別
package models; import java.util.*; import javax.persistence.Entity; import javax.persistence.OneToMany; import play.db.jpa.Model; @Entity public class PersonData extends Model { public String name; public String mail; @OneToMany public List<MsgData> messages; public PersonData(String name,String mail){ this.name = name; this.mail = mail; this.messages = new ArrayList<MsgData>(); } }
在這裡除了name和mail兩個欄位以外,還有保管MsgData實例的List欄位,這個List就是用來整合關聯MsgData物件。
這裡一定要注意的是「@OneToManay宣告」,JPA裡物件的永續化時,如果沒加上@OneToMany的話,就無法達成永續化。
藉由加上@OneToManay,這就告訴JPA這個欄位成了一對多對應的物件參照欄位。
MsgData的修正
接下來MsgData裡使用PersonData來取代name
package models; import java.util.Calendar; import java.util.Date; import javax.persistence.Entity; import javax.persistence.ManyToOne; import play.db.jpa.Model; @Entity public class MsgData extends Model { private static final long serialVersionUID = 1L; public String message; @ManyToOne public PersonData person; public Date time = Calendar.getInstance().getTime(); public MsgData(String message,PersonData person){ this.person = person; this.message = message; person.messages.add(this); } }
在這裡面有保管PersonData實例的person欄位,並加上了「@ManyToOne」的宣告,這樣一來就能夠將MsgData永續化。
new的時候,傳入引數裡的PersonData實例,在設定person屬性的同時,呼叫出person的message.add,在person
這邊將訊息保管起來。
PersonData的Controller
接下來作成PersonData的Controller,在「controller」資料夾裡做一個新的「Person.java」檔案。
這次就先加入add和index兩個基本的方法。
package controllers; import java.util.List; import play.mvc.Controller; import models.*; public class Person extends Controller { public static void index(String name) { List<PersonData> datas = null; datas = PersonData.findAll(); render(datas); } public static void add(String name,String mail){ String msg = "請輸入資料。"; if (request.method.equals("POST")) { PersonData person = new PersonData(name,mail); person.save(); msg = "資料已經被保存了"; } render(msg); } }
準備PersonData用的Template
接下來在「views」資料夾裡做一個新的「Person」資料夾,然後加入兩個檔案「index.html」「add.html」
#index.html的程式碼
#{extends 'main.html' /} #{set title:'Home' /} <h1>表示訊息</h1> #{list items:datas, as:'data'} <li>${data.id}: ${data.name}(${data.mail})</li> #{/list}
#add.html的程式碼
#{extends 'main.html' /} #{set title:'Home' /} <h1>作成訊息</h1> ${msg} <form method="post" action="@{Person.add}"> <table> <tr><td>name:</td><td><input type="text" id="name" name="name" /></td></tr> <tr><td>mail:</td><td><input type="text" id="mail" name="mail" /></td></tr> <tr><td></td><td><input type="submit" value="送出" /></td></tr> </table> </form>
接下來修正MsgData的Controller
package controllers; import java.util.List; import models.*; import play.mvc.Controller; public class Application extends Controller { public static void index(String name) { List<MsgData> datas = null; datas = MsgData.findAll(); render(datas); } public static void add(String message,String name){ String msg = "請輸入資料。"; if (request.method.equals("POST")) { PersonData person = PersonData.find("name",name).first(); if (person != null){ MsgData data = new MsgData(message,person); data.save(); person.save(); msg = "資料已經被保存了。"; } else { msg = "個人資料尚未登入。"; } } render(msg); } }
作成MsgData用的Template
#index.html的原始碼
#{extends 'main.html' /} #{set title:'Home' /} <h1>表示訊息</h1> #{list items:datas, as:'data'} <li>${data.id}: ${data.message}(${data.person.name}, ${data.person.mail})</li> #{/list}
#add.html的原始碼
#{extends 'main.html' /} #{set title:'Home' /} <h1>作成訊息</h1> ${msg} <form method="post" action="@{Application.add}"> <table> <tr><td>name:</td><td><input type="text" id="name" name="name" /></td></tr> <tr><td>message:</td><td><input type="text" id="message" name="message" /></td></tr> <tr><td></td><td><input id="submit" type="submit" value="送出" /></td></tr> </table> </form>
顯示使用者投稿的全部訊息
從MsgData裡取得PersonData的@ManyToOne處理已經明白了之後。接下來利用看看@OneToMany關聯的實例。
稍微修正剛才Person的index.html,將它改成可以顯示所有使用者的訊息。
#{extends 'main.html' /} #{set title:'Home' /} <h1>表示訊息</h1> #{list items:datas, as:'data'} <li>${data.id}: ${data.name}(${data.mail}) <ul> #{list items:data.messages, as:'message'} <li>${message.message}</li> #{/list} </ul> </li> #{/list}
完全不需要改動到Controller,在#{list items:datas, as:'data'}的重複操作中,又重複操作
#{list items:data.messages, as:'message'}來取出PersonData裡的messages全部元素。
有關測試
在Play!裡備有JUnit的標準測試機能,在作成的專案裡都會有「test」這個資料夾,這個資料夾就是為了要配置測試用的
程式碼的地方。裡面有標準的「ApplicationTest.java」「BasicTest.java」「Application.test.html」等名稱
的程式碼檔案。這是在專案作成時自動生成的測試用程式碼,當然也可以修正這些檔案來做自己想要的測試。
在Play!裡有三種測試種類。「UnitTest(單元測試)」「FunctionalTest(功能測試)」「SeleniumTest」。
想要實行測試模式時,只要輸入
play test sampleapp
然後測試下面的網址
http://localhost:9000/@tests
檢查UnitTest程式碼
首先先看實行UnitTest的「BasicTest.java」檔案
import org.junit.*; import java.util.*; import play.test.*; import models.*; public class BasicTest extends UnitTest { @Test public void aVeryImportantThingToTest() { assertEquals(2, 1 + 1); } }
在這裡「assertEquals」方法傳入兩個引數比較是否相等。這個UnitTest類別是直接繼承JUnit的org.junit.Assert類別,
並直接使用JUnit的assert方法。
檢查FunctionalTest程式碼
import org.junit.*; import play.test.*; import play.mvc.*; import play.mvc.Http.*; import models.*; public class ApplicationTest extends FunctionalTest { @Test public void testThatIndexPageWorks() { Response response = GET("/"); assertIsOk(response); assertContentType("text/html", response); assertCharset("utf-8", response); } }
接下來在FunctionalTest裡加上一些各自的定義。頁面的存取已經在上面的範例測試過了,這次就定義測試Model的方法。
@Before public void setup() { Fixtures.deleteAll(); } @Test public void testModelData() { PersonData p = new PersonData("測試", "test@test.com"); p.save(); MsgData m = new MsgData("這是測試。", p); m.save(); p.messages.add(m); p.save(); assertNotNull(p); assertNotNull(m); assertEquals(p.name, m.person.name); assertEquals(m.id,p.messages.get(0).id); List<PersonData> plist = PersonData.find("name", "測試").fetch(); assertEquals(1, plist.size()); List<MsgData> mlist = MsgData.find("person", plist.get(0)).fetch(); assertEquals(1, mlist.size()); }
在這裡作成PersonData和MsgData,首先作成PersonData實例並保存,接下來將訊息和PersonData作為
引數作成MsgData實例,然後確認p和m的依存關係。assertNotNull確認引數是不是null。
assertEquals確認PersonData和MsgData的相同參照物件的name和id是否是參照到正確的物件。
這個測試之前,有一個setup的方法,並使用了「@Before」的宣告,這是表示說這是在測試前的前置動作,這裡用「Fixtures.deleteAll();」
就是將之前測試用的資料全部刪除、在每一個測試開始之前基本上都要先像這樣將資料初始化。
透過data.yml來生成測試資料
如果像之前生成資料、確認資料都還要寫一堆程式碼,在生成大量資料的時候就變得很麻煩。而且當要改變資料內容來驗證時
又還需要重新更改程式碼,如果可以的話,希望能夠把Model和程式碼切離。
在Play!的情況,「test」資料夾裡有一個「data.yml」檔案,紀錄了Model相關的情報,藉由這個來自動生成資料是可能的。
PersonData(tuyano): name: tuyano mail: tuyano@mac.com PersonData(taro): name: taro mail: taro@yamada.com MsgData(msg1): message: Hello person: tuyano MsgData(msg2): message: Good-bye person: taro
把data.yml打開,然後以這樣的格式紀錄Model內容
Model名稱 ( 實例名稱 ):
屬性名稱: 値
屬性名稱: 値
……以下略……
屬性名稱: 値
屬性名稱: 値
……以下略……
測試用的程式碼
@Test public void testFullData() { Fixtures.load("data.yml"); assertEquals(2, PersonData.count()); assertEquals(2, MsgData.count()); }
SeleniumTest的原始碼
SeleniumTest是在以HTML為基礎的Template檔案裡嵌入特殊的標籤來實行的。打開「Application.test.html」就能知道了。
#{selenium} open('/') waitForPageToLoad(1000) assertNotTitle('Application error') #{/selenium}
使用seleniumTest測試add頁面
在Application.test.html裡加入
#{fixture delete:'all', load:'data.yml' /} #{selenium} open('/add') waitForPageToLoad(1000) type('name','test') type('message','*** this is selenium test.***') clickAndWait('submit') #{/selenium}
參考文章:http://codezine.jp/article/detail/4752