领先一步
VMware 提供培训和认证,助您快速提升。
了解更多Groovy社区是一个富有成效的群体,这意味着有大量的框架、库和工具可以使您的生活更轻松。测试领域似乎是特别肥沃的土壤,我最近一直在研究几个工具,这些工具结合在一起,承诺在编写功能性Web测试时提高您的生产力。
虽然我通常关注的是Grails,但您不必使用Grails就能获得这些工具的好处:它们适用于任何Web应用程序,并且可以很好地与任何基于Java的项目/构建集成。碰巧的是,它们都有相关的插件,使从Grails中使用它们变得非常简单。
我要谈论的第一个工具是Spock。它基于行为驱动开发(BDD)范式,该范式将重点从测试本身转移到根据预期行为来思考代码。您编写的测试用例可以作为规范来阅读,这不仅使它们更易于阅读和理解,而且更易于编写。您甚至可以将Spock集成到任何Java项目中,并从您的IDE中运行规范(只要IDE支持Groovy - 三大IDE都支持)。
第二个工具更新。它被称为Geb,它使用WebDriver来使用真实浏览器或HtmlUnit库测试Web应用程序。使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://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喜欢称之为“特性”方法)都分解成几个部分。第一个是,给定,包含任何设置代码,并为您提供测试的起始状态。然后,您声明一个何时块,它会在您正在测试的任何内容中启动一些行为,例如提交表单。最后,您在然后块中检查刺激的结果,该块包含完全验证预期行为所需的条件。与JUnit测试不同,您不需要在然后部分中进行显式断言,因为每个表达式都是一个隐式断言。
这是一个简单的概念,但是一旦您习惯了编写规范,您就会发现Spock使编写测试变得更容易。这是我无法解释的事情。我最好的猜测是语法和结构与您在脑海中制定测试的方式相匹配,因此在思考要测试的内容和编写物理测试用例之间几乎没有阻抗。但与其相信我的话,我敦促您尝试一下。您可以将Spock用于单元测试以及功能测试,因此很容易尝试。
测试中的几乎所有其他内容都是Geb,包括到()和在()方法。这两个方法都作用于页面对象,您必须自己编写这些对象。幸运的是,这很容易,从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") }
}
}
让我们分别看看这个类中的静态属性
因此,在上面的示例中,您可以看到登录页面具有“auth/login”的相对URL。相对于什么?到测试的baseUrl。确定当前页面是否为登录页面,只需在在闭包中检查页面的标题是否为“登录”。最后,内容块通过Geb的$()方法提供对登录表单(页面上唯一的表单)、info/error消息“div”和“登录”按钮的直接访问。
如果您回顾测试,您会发现我能够访问内容元素,例如loginForm就像它们是测试的属性一样。Geb的此功能允许进行非常简洁和自描述的测试,但更重要的是,它促进了代码重用。想象一下,您的HTML页面发生了更改,并且其中一个表达式不再匹配您想要的内容。如果您不使用页面对象,则必须执行可能不可靠的全局搜索和替换。相反,更改页面对象中的一个引用要好得多!
该$()函数不仅限于内容块 - 如果需要,您可以直接从测试代码中使用它。考虑这个测试
...
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的内置模拟框架或它对数据驱动测试的支持(查看项目文档中的在哪里子句)。即使其断言的输出也是一大好处
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是测试领域的重要发展,值得研究。