领先一步
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() { "http://localhost: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 是测试领域的重要发展,非常值得研究。