Автоматизация Grails приложений с Spock и Geb


(Mykhailo Poliarush) #1

Сообщество специалистов по Groovy - очень продуктивно, что означает, что сообщество делает большое количество фреймворков, библиотек и инструментов, которые сделают нашу с вами жизнь значительно проще. Тестирование приложений кажется, особенно плодородной почвой. Я недавно смотрел на пару инструментов, которые, при совместном использовании, обещает увеличить скорость написании функциональных веб тестов.

Хотя обычно я сфокусирован на Grails, но вам не обязательно использовать Grails для того, чтобы воспользоваться преимуществами этих инструментов: инструменты будут работать с любым веб-приложением и они хорошо интегрируются с любыми проектами/сборками на основе Java. В таком случае они оба будут иметь соответствующие плагины, благодаря чему их довольно просто использовать с Grails.

Первый из инструментов, о которых я хочу рассказать - Spock. Он основан на парадигме Behaviour Driven Development (BDD), смещающей фокус внимания с самих тестов на размышления о вашем коде с учетом ожидаемого поведения. Тестовые случаи, которые вы пишете, читаются как спецификации, благодаря чему их не только проще читать и понимать, но также и писать. Вы можете даже интегрировать Spock в любой Java проект и запускать из ваших спецификаций из вашего IDE (если IDE имеет поддержку Groovy).

Второй инструмент, является даже еще более новым. Называется он Geb и использует WebDriver как основу для тестирования веб-приложений, используя настоящие браузеры или библиотеку HtmlUnit . Geb отличается от своих конкурентов синтаксисом похожим на jQuery для испытания ваших HTML страниц и встроенной поддержкой Page Object pattern.

Итак, почему же я считаю это выигрышной комбинацией? Потому, что они делают написание функциональных веб тестов максимально простым! Давайте посмотрим на эту парочку в действии.

Простой пример

Представьте, что у вас есть простая страница входа, которую вы хотите протестировать. Она принимает имя пользователя и пароль и имеет кнопку 'Войти'. HTML выглядит следующим образом:

{syntaxhighlighter brush: xml;fontsize: 100; first-line: 1; }<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>{/syntaxhighlighter}

А теперь, посмотрите на следующую спецификацию Spock и постарайтесь определить, какое поведение она тестирует: 

{syntaxhighlighter brush: groovy;fontsize: 100; first-line: 1; }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!")
}

}{/syntaxhighlighter}

Я не знаю как вы, но мне кажется, чтобы определить, что именно пытается сделать тест, довольно просто. Даже, если на этой стадии вы не знаете происхождения переменных, вы можете прочитать спецификацию так же эффективно, как и естественный язык. Такое облегченное понимание – одно из преимуществ таких инструментов BDD как Spock.

Давайте более подробно посмотрим на спецификации. Каждый тестовый метод (или "feature" method, как их любит называть Spock) разбивается на несколько секций. Первый, given, содержит любой код установки и дает вам начальное состояние теста. Затем, блок when, инициирующий некоторое поведение того, что вы тестируете, например, путем отправки формы. Вы заканчиваете, проверяя результаты в блоке then, который содержит условия, необходимые для того, чтобы полностью проверить ожидаемое поведение. В отличие от тестов JUnit, у вас нет необходимости в выражении утверждений внутри секции then, потому, что каждое выражение является подразумеваемым утверждением.

Это очень простая концепция, но когда вы привыкнете к написанию спецификаций, вы убедитесь, что Spock делает написание тестов проще. Это то, что я не могу объяснить. Мне кажется, что синтаксис и структура совпадают с тем, как вы формулируете тесты у себя в голове, таким образом, существует небольшое сопротивление между обдумыванием того, что тестировать и написанием физического тестового случая. Но я считаю, что вы должны не верить мне на слово, а попробовать сами. Вы можете использовать Spock как для тестирования элементов, так и для функционального тестирования, «играться» с ним очень просто.

Все остальное в тесте - Geb, включая методы to() и at(). Они оба работают над объектами страницы, которые необходимо написать самостоятельно. К счастью это достаточно легко, как вы можете сказать по классу LoginPage :

{syntaxhighlighter brush: groovy;fontsize: 100; first-line: 1; }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") }
}

}{/syntaxhighlighter}

Давайте рассмотрим статические свойства в этом классе индивидуально:

  • url – относительный URL страницы; используемый методом to() для того, чтобы определить какой URL должен отправить запрос HTTP.
  • at – замкнутое выражение, указывающее является ли текущая страница этой или нет – вызывается методом at(); он должен вернуть булин, но вы также можете включить утверждение.
  • content – описание контента страницы, позволяющего простой доступ к частям, заявленным тут.

Таким образом, в примере, приведенном выше, вы можете увидеть, что страница входа имеет относительный URL "auth/login". Относительный к чему? К baseUrl.теста. Определение того, является ли текущая страница страницей входа, включает простую проверку того, что заглавием страницы является "Login" в замкнутом выражении at. В итоге, блок content предоставляет прямой доступ к форме входа (являющейся единственной формой на странице), сообщению информации/ошибки "div", и кнопке "Sign in" – по методу Geb's $() .

Если вы посмотрите назад на тест, вы увидите, что мне удалось получить доступ к элементам контента, таким как loginForm as если они были характеристиками теста. Такая характерная черта Geb позволяет проведение тестов в сжатые сроки, не требующих дополнительного описания, и, что более важно, предполагает повторное использование кода. Представьте, что ваша страница HTML меняется и одно из выражений уже не соответствует тому, что вам необходимо. Если бы вы не использовали объекты страницы, вам необходимо было бы выполнить потенциально ненадежный глобальный поиск и замену. Но ведь настолько лучше изменить всего один указатель в объекте страницы вместо этого!

Функция $() не ограничена исключительно блоками content – вы можете использовать ее прямо из тестового кода, при желании. Рассмотрите этот тест:

{syntaxhighlighter brush: groovy;fontsize: 100; first-line: 1; }...
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)

}
...{/syntaxhighlighter}

Благодаря синтаксису типа jQuery – облегчено совпадение элементов на HTML странице и выделение значений параметров и контента. О синтаксисе хорошо рассказано в Geb manual, как и о многих других характеристиках Geb.

Предыдущий пример также демонстрирует то, как вы можете вынести код из тестов в методы, которые могут использоваться повторно. Так как синтаксис Spock сначала довольно непривычен, вам может показаться, что это просто невозможно. Но в конце концов спецификации являются классами, так что вы можете обращаться с ними надлежащим образом.

Я мог бы говорить о Spock и Geb и их использовании еще и еще, но эта статья ведь не руководство к пользованию. Скорее ее можно назвать рекламной дегустацией, предназначенной для того, чтобы вызвать ваш интерес. Если вы хотите увидеть полностью спецификации Spock из которых я выделил вышеприведенные отрывки, смотрите исходный код и связанные объекты страницы .

Чего вы ожидаете?

Spock и Geb являются довольно молодыми технологиями на данный момент (ни один из них еще не достиг версии 1.0), но они уже довольно популярны и используются в различных проектах. Даже на этот момент, они являются очень привлекательными: функциональные веб-тесты, которые довольно просты не только в написании, но и для понимания.

Это немаловажно. Автоматизированные функциональные тесты необходимы для того, чтобы гарантировать, что веб приложения ведут себя именно так, как и должны, но существующие подходы (как минимум это касается Java) довольно громоздки, что обычно усложняет и удлиняет процесс написания таких тестов. Таким образом, все заканчивается тем, что команды полагаются на ручное тестирование, с помощью которого вы никогда не сможете добиться необходимой степени надежности.

Так ли идеальны приложения? Конечно, нет. Но то, что мы имеем – два инструмента, которые позволяют тестировать легко то, что и должно тестироваться легко – безусловно, имеет ценность для мира функционального веб тестирования. И они продолжат поддерживать вас, вкдь страницы, которые вам необходимо будет тестировать, будут становиться все сложнее и сложнее. Возможно, самое сложное - работа с Javascript и вызов событий DOM из ваших тестов, но даже так, вы увидите, что Geb развивается очень быстро, чтобы оценивать вам помощь.

Я даже не упомянул встроенный mocking фреймворке или поддержке тестирования с внешними данными (проверьте условие where в документации по проекту). Даже выход из его утверждений – уже большой бонус:

{syntaxhighlighter brush: bash;fontsize: 100; first-line: 1; }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{/syntaxhighlighter}

Посмотрите на характеристики модуля, которые помогают вам разложить объект страницы на части, которые можно использовать повторно. Такая возможность просто замечательна для сложных страниц. А так как он использует WebDriver, вы можете проводить тесты, как с настоящими браузерами, так и в headless режиме (с помощью HtmlUnit).

В завершение я хотел бы повторить, что эти инструменты будут работать как в Java проектах, так и в Groovy или Grails. То, что ваше веб приложение написано в Java абсолютно не означает, что и тестировать его нужно тоже в Java. Также, поймите, что Spock может использоваться для любого типа проектов Java – юнит, интеграционное и функциональное тестирование.

Одна вещь, которой я научился – написание тестов должно быть максимально простым, иначе, они просто не будут писаться. Именно поэтому я думаю, что Geb и Spock являются важными разработками и их стоит изучить.