領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多Groovy 社群是一個高產的群體,這意味著有大量的框架、庫和工具可以讓您的生活更輕鬆。測試領域似乎特別肥沃,我最近一直在研究兩種工具,當它們結合使用時,有望在您編寫功能性 Web 測試時帶來生產力的飛躍。
儘管我通常關注的是 Grails,但你無需使用 Grails 也能從這些工具中受益:它們適用於任何 Web 應用程式,並且能與任何基於 Java 的專案/構建良好整合。碰巧的是,它們都關聯了外掛,使得在 Grails 中使用它們變得相當簡單。
我想討論的第一個工具是 Spock。它基於行為驅動開發 (BDD) 正規化,將焦點從測試本身轉移到從預期行為的角度思考程式碼。你編寫的測試用例讀起來像規範,這不僅使它們更容易閱讀和理解,也更容易編寫。你甚至可以將 Spock 整合到任何 Java 專案中,並從你的 IDE(只要 IDE 支援 Groovy——三大主流 IDE 都支援)執行你的規範。
第二個工具甚至更新。它叫做 Geb,它使用 WebDriver 透過真實瀏覽器或 HtmlUnit 庫來測試 Web 應用程式。Geb 之所以脫穎而出,是因為它具有類似 jQuery 的語法,用於查詢 HTML 頁面,並且內建支援 頁面物件模式。
那麼,為什麼我認為它們是成功的組合呢?因為它們讓編寫功能性 Web 測試變得儘可能簡單!讓我們看看它們如何協同工作。
想象一下,你有一個簡單的登入頁面需要測試。它接受使用者名稱和密碼,並有一個“登入”按鈕。HTML 看起來像這樣:
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="/wildcard-realm/auth/signIn" method="post" >
<input type="hidden" name="targetUri" value="" />
<table>
<tbody>
<tr>
<td>Username:</td>
<td><input type="text" name="username" value="" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password" value="" /></td>
</tr>
<tr>
<td>Remember me?:</td>
<td>
<input type="hidden" name="_rememberMe" />
<input type="checkbox" name="rememberMe" id="rememberMe" />
</td>
</tr>
<tr>
<td />
<td><input type="submit" value="Sign in" /></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
現在,看看下面的 Spock 規範,試著弄清楚它正在測試什麼行為:
import geb.spock.GebReportingSpec
import pages.*
class MySpec extends GebReportingSpec {
String getBaseUrl() { "https://:8080/wildcard-realm" }
File getReportDir() { new File("target/reports/geb") }
def "Test invalid password"() {
given: "I'm at the login page"
to LoginPage
when: "I enter an invalid password for 'admin'"
loginForm.username = "admin"
loginForm.password = "sdfkjhk"
signIn.click()
then: "I'm redirected back to the login page with the password field empty and an error message"
at LoginPage
loginForm.username == "admin"
!loginForm.password
message.text() == "Invalid username and/or password"
}
def "Test valid login"() {
given: "I'm at the login page"
to LoginPage
when: "I enter a valid username and password"
loginForm.username = "admin"
loginForm.password = "admin"
signIn.click(HomePage)
then: "I'm redirected to the home page, which displays my username"
at HomePage
$().text().contains("Welcome back admin!")
}
}
我不知道你怎麼樣,但我發現很容易弄清楚這個測試試圖做什麼。即使你在這個階段不知道變數是從哪裡來的,你也可以有效地將規範作為自然語言來閱讀。這種易於理解性是像 Spock 這樣的 BDD 工具的一大優勢。
讓我們更詳細地看一下這個規範。每個測試方法(或者 Spock 喜歡稱之為“特性”方法)都分解為幾個部分。第一個部分,given,包含任何設定程式碼,併為你提供測試的起始狀態。然後你宣告一個when塊,它在你正在測試的任何內容中啟動一些行為,例如透過提交表單。最後,你在then塊中檢查刺激的結果,該塊包含你需要充分驗證預期行為的條件。與 JUnit 測試不同,你不需要在then部分內進行顯式斷言,因為每個表示式都是隱式斷言。
這是一個簡單的概念,但一旦你習慣了編寫規範,你會發現 Spock 讓編寫測試變得更容易。這是我無法真正解釋的事情。我最好的猜測是,語法和結構與你在頭腦中構思測試的方式相匹配,因此思考要測試什麼與編寫實際測試用例之間幾乎沒有阻礙。但與其聽信我的話,我敦促你嘗試一下。你可以使用 Spock 進行單元測試和功能測試,所以很容易上手。
測試中的其他所有內容都屬於 Geb,包括to()還是at()方法。這兩個方法都作用於頁面物件,你需要自己編寫。幸運的是,這足夠簡單,正如你可以從LoginPage類中看出的那樣:
package pages
import geb.Page
class LoginPage extends Page {
static url = "auth/login"
static at = { title == "Login" }
static content = {
loginForm { $("form") }
message { $("div.message") }
signIn { $("input", value: "Sign in") }
}
}
讓我們分別看看這個類中的靜態屬性:
因此在上面的例子中,你可以看到登入頁面的相對 URL 是 “auth/login”。相對於什麼?相對於測試的baseUrl。判斷當前頁面是否是登入頁面,只需在at閉包中檢查頁面標題是否為“Login”即可。最後,content塊提供了對登入表單(頁面上唯一的表單)、資訊/錯誤訊息“div”和“登入”按鈕的直接訪問——所有這些都透過 Geb 的$()方法實現。
如果你回頭看測試,你會發現我可以像訪問測試的屬性一樣訪問內容元素,例如loginForm。Geb 的這一特性允許編寫非常簡潔和自描述的測試,但更重要的是,它促進了程式碼重用。想象一下你的 HTML 頁面發生變化,其中一個表示式不再匹配你想要的內容。如果你沒有使用頁面物件,你將不得不執行一個可能不可靠的全域性搜尋和替換。相反,在頁面物件中更改那個引用會好多少!
的$()功能不僅限於content塊——如果你願意,你可以直接從測試程式碼中使用它。考慮這個測試:
...
def "Test authentication redirect with query string"() {
when: "I access the book list page with a sort query string"
login "admin", "admin", BookListPage, [sort: 'title', order: 'desc']
then: "The list of books is displayed in the correct order"
at BookListPage
$("tbody tr").size() == 3
$("tbody tr")*.find("td", 1)*.text() == [ "Misery", "Guns, Germs, and Steel", "Colossus" ]
}
...
/**
* Logs into the application either via a target page that requires
* authentication or by directly requesting the login page.
*/
private login(username, password, targetPage = null, params = [:]) {
if (targetPage) {
to([*:params], targetPage)
page LoginPage
}
else {
to LoginPage
}
loginForm.username = username
loginForm.password = password
if (targetPage) signIn.click(targetPage)
else signIn.click(HomePage)
}
...
類似 jQuery 的語法使得匹配 HTML 頁面中的元素並提取屬性值和內容變得非常容易。它的語法以及 Geb 的許多其他功能在 Geb 手冊 中有詳細介紹。
前面的例子也演示瞭如何將程式碼從測試中抽取出來,放入可重用方法中。因為 Spock 的語法起初相當陌生,你可能不認為這有可能。但規範最終是類,所以你可以像對待類一樣對待它們。
我可以一直講 Spock 和 Geb 的功能以及如何使用它們,但本文不是教程。它更像是一個讓你感興趣的嚐鮮。如果你想檢視我從中提取上述片段的完整 Spock 規範,請檢視它的原始碼以及相關的頁面物件。
Spock 和 Geb 目前都還很年輕(都未達到 1.0 版),但它們已經發展到人們積極將其應用於專案中的程度。即使在這個階段,它們也展現出了引人注目的優勢:功能性 Web 測試既相對易於編寫又易於理解。
這可不是小事。自動化功能測試對於確保 Web 應用程式正常執行至關重要,但當前的方法(至少在 Java 生態系統中)通常笨拙且阻礙了這些測試的編寫。因此,團隊最終依賴手動測試,而手動測試永遠無法提供你真正需要的可靠覆蓋率。
一切都盡善盡美嗎?當然不是。但我們這裡有兩款工具,它們讓原本應該容易測試的東西,真正變得容易測試——這在功能性 Web 測試領域絕非易事。並且,隨著你需要測試的頁面變得更加複雜,它們將繼續為你提供支援。也許最大的問題是處理 Javascript 並從測試中觸發 DOM 事件,但即使在那方面,你也會發現 Geb 正在迅速發展以幫助你。
我甚至還沒有提及 Spock 內建的模擬框架,或者它對資料驅動測試的支援(請查閱專案文件中的where子句)。就連它的斷言輸出也是一大優點:
dateService.getMonthString(new Date().updated(month: month)) == expected | | | | | | | | June | | 5 | July | | | false | | | 2 differences (50% similarity) | | | Ju(ne) | | | Ju(ly) | | Sun Jun 27 12:24:02 BST 2010 | Fri Aug 27 12:24:02 BST 2010 org.grails.util.DateService@527f58ef
在 Geb 方面,可以看看模組功能,它允許你將頁面物件分解為可重用部分。非常適合複雜的頁面。由於它底層使用 WebDriver,因此你既可以用真實瀏覽器執行測試,也可以在無頭模式下(透過 HtmlUnit)執行。
最後,我重申這些工具在 Java 專案中與在 Groovy 或 Grails 專案中同樣適用。僅僅因為你的 Web 應用程式是用 Java 編寫的,並不意味著你的測試也必須如此。還要明白,Spock 可以用於任何型別的 Java 專案——用於單元測試、整合測試和/或功能測試。
如果說我學到了一件事,那就是編寫測試必須儘可能容易,否則它們根本就不會被編寫。這就是為什麼我認為 Geb 和 Spock 是測試領域的重要發展,值得深入研究。