Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
BDD and browser automation with
Geb and Spock
Данилюк Богдан
Jeeconf | may 2014 | @bogdand
Главный вопрос жизни
вселенной и всего такого
Главный вопрос жизни
вселенной и всего такого
Как в любой момент быть уверенным в
качестве текущей версии вашего
приложени...
Кто я?
● C Groovy / Grails начиная с 2008 года
● Успешно внедрили и используем Geb /
Spock
● - Groovy / Grails курс
● Перв...
TransferWise
TransferWise
С чего все начиналось?
≈ 0% покрытие тестами
С чего все начиналось?
Smoke testing
● Тестировалось <10 страниц
● Время прохождения тестов ≈1 минута
● Обнаруженных ошибок - мало =)
Smoke testing
Geb
very groovy browser automation
Geb
● Groovy обертка вокруг Selenium 2
● Лицензия ASL2
● Текщая версия: 0.9.2
● Дружит с JUnit и Spock
● Groovy Page objec...
Geb - WebDriver
● Мобильные браузеры:
○ iPad
○ iPhone
○ Android
○ Blackberry
● PhantomJS
● HtmlUnit
Простой Geb test
go "http://www.google.com"
$("input", name: "q").value("JeeConf")
$("button", name: "btnG").click()
waitF...
Простой Geb test
go "http://www.google.com"
$("input", name: "q").value("JeeConf")
$("button", name: "btnG").click()
waitF...
Простой Geb test
go "http://www.google.com"
$("input", name: "q").value("JeeConf")
$("button", name: "btnG").click()
waitF...
Простой Geb test
go "http://www.google.com"
$("input", name: "q").value("JeeConf")
$("button", name: "btnG").click()
waitF...
Простой Geb test
go "http://www.google.com"
$("input", name: "q").value("JeeConf")
$("button", name: "btnG").click()
waitF...
Простой Geb test
go "http://www.google.com"
$("input", name: "q").value("JeeConf")
$("button", name: "btnG").click()
waitF...
Метод $()
$(«css selector», «index or range», «attribute / text matchers»)
Примеры
$("div") // все div элементы
$("div", 0...
CSS3 селекторы
$("div.some-class p:first[title='some']")
$("ul li a")
$("table tr:nth-child(2n+1) td")
$("div#content p:fi...
Атрибуты
Поиск по атрибутам
//<div foo="bar">
$("div", foo: "bar")
Атрибуты
Поиск по атрибутам
//<div foo="bar">
$("div", foo: "bar")
Поиск по тексту
//<div>foo</div>
$("div", text: "foo")
Навигация
Поиск по атрибутам
//<div foo="bar">
$("div", foo: "bar")
Поиск по тексту
//<div>foo</div>
$("div", text: "foo")...
Вспомогательные функции
$("p", text: startsWith("p"))
$("p", class: contains("section"))
$("p", id: endsWith(~/d/))
Больше...
Относительный поиск
$("p").previous()
$("p").prevAll()
$("p").next()
$("p").parent()
$("p").siblings()
$("div").children()...
Functional testing
Functional testing
● Время прохождения тестов - 20 минут
● Обнаруженных ошибок - уже больше =)
● Грядут редизайны веб стра...
Geb
Страницы и модули
Page Object
class WikipediaPage extends Page {
static url = "https://www.wikipedia.org"
static at = { title == "Wikipedia"...
Page Object
class WikipediaPage extends Page {
static url = "https://www.wikipedia.org"
static at = { title == "Wikipedia"...
Page Object
class WikipediaPage extends Page {
static url = "https://www.wikipedia.org"
static at = { title == "Wikipedia"...
Page Object
class WikipediaPage extends Page {
static url = "https://www.wikipedia.org"
static at = { title == "Wikipedia"...
Page Object использование
Browser.drive {
to WikipediaPage
assert at(WikipediaPage)
search = "JeeConf"
}
Опциональный контент
class OptionalPage extends Page {
static content = {
errorMsg(required: false) { $("p.errorMsg") }
}
}
Динамический контент
class DynamicPage extends Page {
static content = {
errorMsg(wait: true) { $("p.errorMsg") }
}
}
Наследование Page Object
● Блоки content будут слиты
● Методы унаследуются
Повторное использование
● Блоки, повторяющиеся на многих
страницах
● Блоки,
повторяющиеся
на одной
странице
Объявление модуля
class NavBarModule extends Module {
static content = {
homePageLink(to: HomePage) { $("a#home") }
profil...
Использование модулей
class HomePage extends Page {
static content = {
navBarModule { module NavBarModule }
}
void goToPro...
Пример moduleList
class ProfilePage extends Page {
static content = {
paymentListTable {moduleList PaymentRow,
$("table#pa...
Проблема поддержки
to HomePage
loginButton.click()
username = "user"
password = "password"
loginButton.click()
Page Object builder
● Подход предложен Craig Atkinson
● По умолчанию Geb делегирует вызовы
методов текущей странице
● Билд...
Page Object builder
HomePage homePage = to HomePage
LoginPage loginPage = homePage.clickLoginButton()
DashboardPage dashbo...
Реализация HomePage
class HomePage extends Page {
static content = {
loginButton(to: LoginPage, wait: true) {
$("#loginBut...
Geb + Spock
Архитектура
App
Browser
WebDriverGeb
geb-spock
(Testing adapter)
Spok
Spock тест
class GoogleSpec extends GebReportingSpec {
def "the first link should be wikipedia"() {
when:
to GoogleHomePag...
Spock data-driven тест
when:
LoginPage loginPage = loginAsUser(username)
then:
assert loginPage.error == expectedErrorMess...
Проблемы
● Тестировались в основном длительные
цепочки страниц
● Вся подготовка данных делалась через
веб интерфейс
Некрасивый тест
class LoginSpec extends GebReportingSpec {
def "login"() {
given: "a valid user"
go RegistrationPage
regis...
Некрасивый тест
class LoginSpec extends GebReportingSpec {
def "login"() {
given: "a valid user"
go RegistrationPage
regis...
Use case тестирование
Чего мы хотим?
● Результат тестов не булевое значение
● Экранирование сценариев
● Упор на документирование
● Привлечение н...
Экранирование сценариев
Чего мы хотим достигнуть?
● Каждый тест готовит данные для себя
● Тест должен знать как можно мень...
Первая идея
А давайте обновлять базу?
Проблемы:
● Тесты знают много низкоуровневой
информации о приложении
● Тысты очень ч...
Remote control
Groovy remote control
“is a library for executing closures defined in one
Groovy application to be executed...
Remote control - сервер
def receiver = new Receiver()
def handler = new RemoteControlHttpHandler(receiver)
def server =
Ht...
Remote control - сервер
def receiver = new Receiver()
def handler = new RemoteControlHttpHandler(receiver)
def server =
Ht...
Remote control - сервер
def receiver = new Receiver()
def handler = new RemoteControlHttpHandler(receiver)
def server =
Ht...
Remote control - клиент
def transport =
new HttpTransport("http://example.org:8080/groovy-rc")
def remote = new RemoteCont...
Remote control - клиент
def transport =
new HttpTransport("http://example.org:8080/groovy-rc")
def remote = new RemoteCont...
Remote control - клиент
def transport =
new HttpTransport("http://example.org:8080/groovy-rc")
def remote = new RemoteCont...
Remote control - клиент
def transport =
new HttpTransport("http://example.org:8080/groovy-rc")
def remote = new RemoteCont...
Некрасивый тест
class LoginSpec extends GebReportingSpec {
def "login"() {
given: "a valid user"
go RegistrationPage
regis...
Красивый тест
class LoginSpec extends GebReportingSpec {
def "login"() {
given: "a valid user"
remote {
SpringUtils.getReg...
Отчеты
Стандартный Grails отчет
Стандартный Grails отчет
Spock отчеты
Требования
● Логическая групировка тестов
● Возможность запуска группы тестов
● Человекочитаемая документация
Пример отчета
Spock отчеты
Решение
● Расширили Athaydes Spock Reports
○ Добавили группировку
○ Возможность запуска группы
спецификаций
@Group('Invite program')
@SpecName('Invitee')
class InviteesSpec extends GebBaseSpec {
def "Invitee should get a free paym...
Что дальше?
● Тестирование верстки
● Ускорение тестов
● Переход на Gherkin (Cucumber) для
избежания дублирования коментари...
class InviteesSpec extends GebBaseSpec {
def "Invitee should get a free payment"() {
when: "registered with invite link us...
Вопросы?
Всего хорошего, и
спасибо за рыбу!
2014 Jeeconf - Geb Spock
Upcoming SlideShare
Loading in …5
×

2014 Jeeconf - Geb Spock

1,810 views

Published on

With application and team growth such questions as keeping documentation up to date, sharing of the knowledge, communication between stakeholder and development team became more and more actual. BDD as methodology is aimed to minimize negative impact of those issues. Spock and Geb frameworks will help us to illustrate BDD implementation on specific example.

Published in: Engineering, Technology, Design
  • Be the first to comment

2014 Jeeconf - Geb Spock

  1. 1. BDD and browser automation with Geb and Spock Данилюк Богдан Jeeconf | may 2014 | @bogdand
  2. 2. Главный вопрос жизни вселенной и всего такого
  3. 3. Главный вопрос жизни вселенной и всего такого Как в любой момент быть уверенным в качестве текущей версии вашего приложения и дать возможность людям понять что же оно делает?
  4. 4. Кто я? ● C Groovy / Grails начиная с 2008 года ● Успешно внедрили и используем Geb / Spock ● - Groovy / Grails курс ● Первый разработчик в TransferWise, сейчас 100+ человек
  5. 5. TransferWise
  6. 6. TransferWise
  7. 7. С чего все начиналось?
  8. 8. ≈ 0% покрытие тестами С чего все начиналось?
  9. 9. Smoke testing
  10. 10. ● Тестировалось <10 страниц ● Время прохождения тестов ≈1 минута ● Обнаруженных ошибок - мало =) Smoke testing
  11. 11. Geb very groovy browser automation
  12. 12. Geb ● Groovy обертка вокруг Selenium 2 ● Лицензия ASL2 ● Текщая версия: 0.9.2 ● Дружит с JUnit и Spock ● Groovy Page object ● http://gebish.org
  13. 13. Geb - WebDriver ● Мобильные браузеры: ○ iPad ○ iPhone ○ Android ○ Blackberry ● PhantomJS ● HtmlUnit
  14. 14. Простой Geb test go "http://www.google.com" $("input", name: "q").value("JeeConf") $("button", name: "btnG").click() waitFor { $("div", id: "search").displayed } assert $("div", id: "search").text() .contains("jeeconf.com")
  15. 15. Простой Geb test go "http://www.google.com" $("input", name: "q").value("JeeConf") $("button", name: "btnG").click() waitFor { $("div", id: "search").displayed } assert $("div", id: "search").text() .contains("jeeconf.com")
  16. 16. Простой Geb test go "http://www.google.com" $("input", name: "q").value("JeeConf") $("button", name: "btnG").click() waitFor { $("div", id: "search").displayed } assert $("div", id: "search").text() .contains("jeeconf.com")
  17. 17. Простой Geb test go "http://www.google.com" $("input", name: "q").value("JeeConf") $("button", name: "btnG").click() waitFor { $("div", id: "search").displayed } assert $("div", id: "search").text() .contains("jeeconf.com")
  18. 18. Простой Geb test go "http://www.google.com" $("input", name: "q").value("JeeConf") $("button", name: "btnG").click() waitFor { $("div", id: "search").displayed } assert $("div", id: "search").text() .contains("jeeconf.com")
  19. 19. Простой Geb test go "http://www.google.com" $("input", name: "q").value("JeeConf") $("button", name: "btnG").click() waitFor { $("div", id: "search").displayed } assert $("div", id: "search").text() .contains("jeeconf.com")
  20. 20. Метод $() $(«css selector», «index or range», «attribute / text matchers») Примеры $("div") // все div элементы $("div", 0) // первый div элемент $("div", 0..2) // первых три div элемента // Третий H2 элемент с текстом “Geb” $("h2", 2, text: "Geb")
  21. 21. CSS3 селекторы $("div.some-class p:first[title='some']") $("ul li a") $("table tr:nth-child(2n+1) td") $("div#content p:first-child::first-line")
  22. 22. Атрибуты Поиск по атрибутам //<div foo="bar"> $("div", foo: "bar")
  23. 23. Атрибуты Поиск по атрибутам //<div foo="bar"> $("div", foo: "bar") Поиск по тексту //<div>foo</div> $("div", text: "foo")
  24. 24. Навигация Поиск по атрибутам //<div foo="bar"> $("div", foo: "bar") Поиск по тексту //<div>foo</div> $("div", text: "foo") Поиск по регулярным выражениям //<div>foo</div> $("div", text: ~/f.+/)
  25. 25. Вспомогательные функции $("p", text: startsWith("p")) $("p", class: contains("section")) $("p", id: endsWith(~/d/)) Больше примеров в документации
  26. 26. Относительный поиск $("p").previous() $("p").prevAll() $("p").next() $("p").parent() $("p").siblings() $("div").children() $("p").nextAll(".listing")
  27. 27. Functional testing
  28. 28. Functional testing ● Время прохождения тестов - 20 минут ● Обнаруженных ошибок - уже больше =) ● Грядут редизайны веб страниц
  29. 29. Geb Страницы и модули
  30. 30. Page Object class WikipediaPage extends Page { static url = "https://www.wikipedia.org" static at = { title == "Wikipedia" } static content = { search { $("#searchInput") } } }
  31. 31. Page Object class WikipediaPage extends Page { static url = "https://www.wikipedia.org" static at = { title == "Wikipedia" } static content = { search { $("#searchInput") } } }
  32. 32. Page Object class WikipediaPage extends Page { static url = "https://www.wikipedia.org" static at = { title == "Wikipedia" } static content = { search { $("#searchInput") } } }
  33. 33. Page Object class WikipediaPage extends Page { static url = "https://www.wikipedia.org" static at = { title == "Wikipedia" } static content = { search { $("#searchInput") } } }
  34. 34. Page Object использование Browser.drive { to WikipediaPage assert at(WikipediaPage) search = "JeeConf" }
  35. 35. Опциональный контент class OptionalPage extends Page { static content = { errorMsg(required: false) { $("p.errorMsg") } } }
  36. 36. Динамический контент class DynamicPage extends Page { static content = { errorMsg(wait: true) { $("p.errorMsg") } } }
  37. 37. Наследование Page Object ● Блоки content будут слиты ● Методы унаследуются
  38. 38. Повторное использование ● Блоки, повторяющиеся на многих страницах ● Блоки, повторяющиеся на одной странице
  39. 39. Объявление модуля class NavBarModule extends Module { static content = { homePageLink(to: HomePage) { $("a#home") } profileLink(to: ProfilePage) { $("a#profile") } } }
  40. 40. Использование модулей class HomePage extends Page { static content = { navBarModule { module NavBarModule } } void goToProfilePage() { navBarModule.profileLink.click() } }
  41. 41. Пример moduleList class ProfilePage extends Page { static content = { paymentListTable {moduleList PaymentRow, $("table#paymentList tbody tr")} } } class PaymentRow extends Module { static content = { amount { $("td.amount") } status { $("td.status") } } }
  42. 42. Проблема поддержки to HomePage loginButton.click() username = "user" password = "password" loginButton.click()
  43. 43. Page Object builder ● Подход предложен Craig Atkinson ● По умолчанию Geb делегирует вызовы методов текущей странице ● Билдер делает эти вызовы явными
  44. 44. Page Object builder HomePage homePage = to HomePage LoginPage loginPage = homePage.clickLoginButton() DashboardPage dashboardPage = loginPage.login("user", "password") HomePage homePage = to HomePage DashboardPage dashboardPage = homePage.clickLoginButton(). login("user", "password") Вызов по цепочке
  45. 45. Реализация HomePage class HomePage extends Page { static content = { loginButton(to: LoginPage, wait: true) { $("#loginButton") } } LoginPage clickLoginButton() { loginButton.click() return browser.page } }
  46. 46. Geb + Spock
  47. 47. Архитектура App Browser WebDriverGeb geb-spock (Testing adapter) Spok
  48. 48. Spock тест class GoogleSpec extends GebReportingSpec { def "the first link should be wikipedia"() { when: to GoogleHomePage q = "wikipedia" then: at GoogleResultsPage firstResultLink.text() == "Wikipedia" when: firstResultLink.click() then: waitFor { at WikipediaPage } } }
  49. 49. Spock data-driven тест when: LoginPage loginPage = loginAsUser(username) then: assert loginPage.error == expectedErrorMessage where: username | expectedErrorMessage 'disabledUser' | 'Sorry, your account is disabled' 'lockedUser' | 'Sorry, your account is locked' 'invalidUser' | 'Sorry, we could not find that account'
  50. 50. Проблемы ● Тестировались в основном длительные цепочки страниц ● Вся подготовка данных делалась через веб интерфейс
  51. 51. Некрасивый тест class LoginSpec extends GebReportingSpec { def "login"() { given: "a valid user" go RegistrationPage register("user", "password") logout() when: "the user logins with valid credentials" go LoginPage login("user", "password") then: "the welcome page is displayed" at DashboardPage }
  52. 52. Некрасивый тест class LoginSpec extends GebReportingSpec { def "login"() { given: "a valid user" go RegistrationPage register("user", "password") logout() when: "the user logins with valid credentials" go LoginPage login("user", "password") then: "the welcome page is displayed" at DashboardPage }
  53. 53. Use case тестирование
  54. 54. Чего мы хотим? ● Результат тестов не булевое значение ● Экранирование сценариев ● Упор на документирование ● Привлечение нетехнических специалистов для работы с тестами
  55. 55. Экранирование сценариев Чего мы хотим достигнуть? ● Каждый тест готовит данные для себя ● Тест должен знать как можно меньше информации о внутренностях приложения ● Тест не должен ломаться при рефакторингах
  56. 56. Первая идея А давайте обновлять базу? Проблемы: ● Тесты знают много низкоуровневой информации о приложении ● Тысты очень чуствительны к изменениям в приложении
  57. 57. Remote control Groovy remote control “is a library for executing closures defined in one Groovy application to be executed in a different (possible remote) Groovy application.”
  58. 58. Remote control - сервер def receiver = new Receiver() def handler = new RemoteControlHttpHandler(receiver) def server = HttpServer.create(new InetSocketAddress(8080), 0) server.createContext("/groovy-rc", handler) server.start()
  59. 59. Remote control - сервер def receiver = new Receiver() def handler = new RemoteControlHttpHandler(receiver) def server = HttpServer.create(new InetSocketAddress(8080), 0) server.createContext("/groovy-rc", handler) server.start()
  60. 60. Remote control - сервер def receiver = new Receiver() def handler = new RemoteControlHttpHandler(receiver) def server = HttpServer.create(new InetSocketAddress(8080), 0) server.createContext("/groovy-rc", handler) server.start()
  61. 61. Remote control - клиент def transport = new HttpTransport("http://example.org:8080/groovy-rc") def remote = new RemoteControl(transport) def id = remote { def user = new User(name: "Me", password: "pwd") user.save() user.id }
  62. 62. Remote control - клиент def transport = new HttpTransport("http://example.org:8080/groovy-rc") def remote = new RemoteControl(transport) def id = remote { def user = new User(name: "Me", password: "pwd") user.save() user.id }
  63. 63. Remote control - клиент def transport = new HttpTransport("http://example.org:8080/groovy-rc") def remote = new RemoteControl(transport) def id = remote { def user = new User(name: "Me", password: "pwd") user.save() user.id }
  64. 64. Remote control - клиент def transport = new HttpTransport("http://example.org:8080/groovy-rc") def remote = new RemoteControl(transport) def id = remote { def user = new User(name: "Me", password: "pwd") user.save() user.id }
  65. 65. Некрасивый тест class LoginSpec extends GebReportingSpec { def "login"() { given: "a valid user" go RegistrationPage register("user", "password") logout() when: "the user logins with valid credentials" go LoginPage login("user", "password") then: "the welcome page is displayed" at DashboardPage }
  66. 66. Красивый тест class LoginSpec extends GebReportingSpec { def "login"() { given: "a valid user" remote { SpringUtils.getRegisterService() .register("user", "password") } when: "the user logins with valid credentials" go LoginPage login("user", "password") then: "the welcome page is displayed" at DashboardPage
  67. 67. Отчеты
  68. 68. Стандартный Grails отчет
  69. 69. Стандартный Grails отчет
  70. 70. Spock отчеты Требования ● Логическая групировка тестов ● Возможность запуска группы тестов ● Человекочитаемая документация
  71. 71. Пример отчета
  72. 72. Spock отчеты Решение ● Расширили Athaydes Spock Reports ○ Добавили группировку ○ Возможность запуска группы спецификаций
  73. 73. @Group('Invite program') @SpecName('Invitee') class InviteesSpec extends GebBaseSpec { def "Invitee should get a free payment"() { when: "user register with invite link" then: "user has one free payment" } }
  74. 74. Что дальше? ● Тестирование верстки ● Ускорение тестов ● Переход на Gherkin (Cucumber) для избежания дублирования коментариев и кода
  75. 75. class InviteesSpec extends GebBaseSpec { def "Invitee should get a free payment"() { when: "registered with invite link user" registeredWithInviteLinkUser() then: "user has one free payment" to AccountPage freePayments.text().contains("1") } }
  76. 76. Вопросы?
  77. 77. Всего хорошего, и спасибо за рыбу!

×