取得領先
VMware 提供培訓和認證,以加速您的進度。
了解更多Groovy 社群是一個高產出的團體,這意味著有大量的框架、函式庫和工具可以讓您的生活更輕鬆。測試領域似乎是特別肥沃的土壤,我最近一直在研究一些工具,當它們結合在一起時,有望在編寫功能性網頁測試時,您的生產力會產生躍進式的變化。
雖然我通常的重點是 Grails,但您不必使用 Grails 也能獲得這些工具的好處:它們可以與任何網頁應用程式一起使用,並且可以很好地與任何基於 Java 的專案/建置整合。恰好它們都有相關的外掛程式,使得從 Grails 使用它們非常簡單。
我想談論的第一個工具是 Spock。 它基於 行為驅動開發 (BDD) 範例,該範例將焦點從測試本身轉移到根據預期行為來思考您的程式碼。 您編寫的測試案例讀起來就像規格,這不僅使它們更容易閱讀和理解,而且更容易編寫。 您甚至可以將 Spock 整合到任何 Java 專案中,並從您的 IDE 執行您的規格(只要 IDE 具有 Groovy 支援 - 三大 IDE 都有)。
第二個工具甚至更新。 它稱為 Geb,它使用 WebDriver 來測試使用真實瀏覽器或 HtmlUnit 函式庫的網頁應用程式。 Geb 與其他工具的不同之處在於類似 jQuery 的語法,用於查詢您的 HTML 頁面及其對 頁面物件模式 的內建支援。
那麼,我為什麼認為這些工具是成功的組合呢? 因為它們使編寫功能性網頁測試變得盡可能的簡單! 讓我們看看它們的實際運作。
想像一下,您有一個簡單的登入頁面要測試。 它接受使用者名稱和密碼,並具有一個“登入”按鈕。 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://127.0.0.1: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區塊提供對登入表單(這是頁面上唯一的表單)、info/error 訊息 "div" 和 "Sign in" 按鈕的直接存取 - 所有這些都通過 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),但它們已經發展到人們正在積極地在他們的專案中使用它們。 即使在這個階段,它們也提出了一個令人信服的理由:功能性網頁測試既相對容易編寫又容易理解。
這絕非小事。 自動化的功能測試對於確保網頁應用程式的行為符合預期至關重要,但是目前的方法(至少在 Java 領域)通常笨拙且會抑制編寫這些測試的意願。 因此,團隊最終依賴於手動測試,這永遠無法給您真正需要的覆蓋範圍的可靠性。
一切都很美好嗎? 當然不是。 但是我們在這裡擁有的是兩種工具,它們使應該容易測試的東西,實際上很容易測試 - 這在功能性網頁測試的世界中絕非易事。 並且它們將繼續為您提供支援,因為您需要測試的頁面變得更加複雜。 也許最大的問題是處理 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 專案中同樣適用。 僅僅因為您的網頁應用程式是用 Java 編寫的,並不意味著您的測試也必須如此。 也要理解 Spock 可以用於任何類型的 Java 專案 - 用於單元、整合和/或功能測試。
如果我學到了一件事,那就是編寫測試必須盡可能的容易,否則它們根本不會被編寫。 這就是為什麼我認為 Geb 和 Spock 是測試領域的重要發展,並且非常值得研究。