領先一步
VMware 提供培訓和認證,助你加速進步。
瞭解更多Groovy 社群是一群高效的人,這意味著有大量的框架、庫和工具可以讓你更輕鬆。測試領域似乎是一片特別肥沃的土壤,我最近一直在研究幾個工具,它們結合起來有望在編寫功能性 Web 測試時顯著提高你的生產力。
雖然我通常關注 Grails,但你不必使用 Grails 也能獲得這些工具的好處:它們適用於任何 Web 應用程式,並且能很好地整合到任何基於 Java 的專案/構建中。碰巧的是,它們都有相關的外掛,使得從 Grails 使用它們變得非常簡單。
我想談論的第一個工具是 Spock。它基於行為驅動開發 (BDD) 正規化,將焦點從測試本身轉移到根據 預期行為 來思考你的程式碼。你編寫的測試用例就像規範一樣,這不僅使它們更容易閱讀和理解,也更容易編寫。你甚至可以將 Spock 整合到任何 Java 專案中,並在 IDE 中執行你的規範(只要 IDE 支援 Groovy - 三個主要的 IDE 都支援)。
第二個工具更新一些。它叫做 Geb,它使用 WebDriver 來測試 Web 應用程式,可以使用真實瀏覽器或 HtmlUnit 庫。Geb 與眾不同之處在於其用於查詢 HTML 頁面的類似 jQuery 的語法以及對 頁面物件模式 的內建支援。
那麼為什麼我認為這是一個成功的組合呢?因為它們讓編寫功能性 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,包含任何 setup 程式碼並給出測試的起始狀態。然後你宣告一個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。判斷當前頁面是否是登入頁面,只需檢查頁面標題是否是 "Login",在at閉包中。最後,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 是測試領域的重要發展,非常值得研究。