Download pdf - 2014 Jeeconf - Geb Spock

Transcript
Page 1: 2014 Jeeconf - Geb Spock

BDD and browser automation with Geb and Spock

Данилюк Богдан

Jeeconf | may 2014 | @bogdand

Page 2: 2014 Jeeconf - Geb Spock

Главный вопрос жизни вселенной и всего такого

Page 3: 2014 Jeeconf - Geb Spock

Главный вопрос жизни вселенной и всего такого

Как в любой момент быть уверенным в качестве текущей версии вашего

приложения и дать возможность людям понять что же оно делает?

Page 4: 2014 Jeeconf - Geb Spock

Кто я?

● C Groovy / Grails начиная с 2008 года

● Успешно внедрили и используем Geb /

Spock

● - Groovy / Grails курс

● Первый разработчик в TransferWise,

сейчас 100+ человек

Page 5: 2014 Jeeconf - Geb Spock

TransferWise

Page 6: 2014 Jeeconf - Geb Spock

TransferWise

Page 7: 2014 Jeeconf - Geb Spock

С чего все начиналось?

Page 8: 2014 Jeeconf - Geb Spock

≈ 0% покрытие тестами

С чего все начиналось?

Page 9: 2014 Jeeconf - Geb Spock

Smoke testing

Page 10: 2014 Jeeconf - Geb Spock

● Тестировалось <10 страниц

● Время прохождения тестов ≈1 минута

● Обнаруженных ошибок - мало =)

Smoke testing

Page 11: 2014 Jeeconf - Geb Spock

Gebvery groovy browser automation

Page 12: 2014 Jeeconf - Geb Spock

Geb

● Groovy обертка вокруг Selenium 2

● Лицензия ASL2

● Текщая версия: 0.9.2

● Дружит с JUnit и Spock

● Groovy Page object

● http://gebish.org

Page 13: 2014 Jeeconf - Geb Spock

Geb - WebDriver

● Мобильные браузеры:○ iPad○ iPhone○ Android○ Blackberry

● PhantomJS● HtmlUnit

Page 14: 2014 Jeeconf - Geb Spock

Простой 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")

Page 15: 2014 Jeeconf - Geb Spock

Простой 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")

Page 16: 2014 Jeeconf - Geb Spock

Простой 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")

Page 17: 2014 Jeeconf - Geb Spock

Простой 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")

Page 18: 2014 Jeeconf - Geb Spock

Простой 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")

Page 19: 2014 Jeeconf - Geb Spock

Простой 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")

Page 20: 2014 Jeeconf - Geb Spock

Метод $()

$(«css selector», «index or range», «attribute / text matchers»)

Примеры

$("div") // все div элементы

$("div", 0) // первый div элемент

$("div", 0..2) // первых три div элемента

// Третий H2 элемент с текстом “Geb”

$("h2", 2, text: "Geb")

Page 21: 2014 Jeeconf - Geb Spock

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")

Page 22: 2014 Jeeconf - Geb Spock

Атрибуты

Поиск по атрибутам//<div foo="bar">

$("div", foo: "bar")

Page 23: 2014 Jeeconf - Geb Spock

Атрибуты

Поиск по атрибутам//<div foo="bar">

$("div", foo: "bar")

Поиск по тексту//<div>foo</div>

$("div", text: "foo")

Page 24: 2014 Jeeconf - Geb Spock

Навигация

Поиск по атрибутам//<div foo="bar">

$("div", foo: "bar")

Поиск по тексту//<div>foo</div>

$("div", text: "foo")

Поиск по регулярным выражениям//<div>foo</div>

$("div", text: ~/f.+/)

Page 25: 2014 Jeeconf - Geb Spock

Вспомогательные функции

$("p", text: startsWith("p"))

$("p", class: contains("section"))

$("p", id: endsWith(~/\d/))

Больше примеров в документации

Page 26: 2014 Jeeconf - Geb Spock

Относительный поиск

$("p").previous()

$("p").prevAll()

$("p").next()

$("p").parent()

$("p").siblings()

$("div").children()

$("p").nextAll(".listing")

Page 27: 2014 Jeeconf - Geb Spock

Functional testing

Page 28: 2014 Jeeconf - Geb Spock

Functional testing

● Время прохождения тестов - 20 минут

● Обнаруженных ошибок - уже больше =)

● Грядут редизайны веб страниц

Page 29: 2014 Jeeconf - Geb Spock

GebСтраницы и модули

Page 30: 2014 Jeeconf - Geb Spock

Page Object

class WikipediaPage extends Page {

static url = "https://www.wikipedia.org"

static at = { title == "Wikipedia" }

static content = {

search { $("#searchInput") }

}

}

Page 31: 2014 Jeeconf - Geb Spock

Page Object

class WikipediaPage extends Page {

static url = "https://www.wikipedia.org"

static at = { title == "Wikipedia" }

static content = {

search { $("#searchInput") }

}

}

Page 32: 2014 Jeeconf - Geb Spock

Page Object

class WikipediaPage extends Page {

static url = "https://www.wikipedia.org"

static at = { title == "Wikipedia" }

static content = {

search { $("#searchInput") }

}

}

Page 33: 2014 Jeeconf - Geb Spock

Page Object

class WikipediaPage extends Page {

static url = "https://www.wikipedia.org"

static at = { title == "Wikipedia" }

static content = {

search { $("#searchInput") }

}

}

Page 34: 2014 Jeeconf - Geb Spock

Page Object использование

Browser.drive {

to WikipediaPage

assert at(WikipediaPage)

search = "JeeConf"

}

Page 35: 2014 Jeeconf - Geb Spock

Опциональный контент

class OptionalPage extends Page {

static content = {

errorMsg(required: false) { $("p.errorMsg") }

}

}

Page 36: 2014 Jeeconf - Geb Spock

Динамический контент

class DynamicPage extends Page { static content = { errorMsg(wait: true) { $("p.errorMsg") } }}

Page 37: 2014 Jeeconf - Geb Spock

Наследование Page Object

● Блоки content будут слиты

● Методы унаследуются

Page 38: 2014 Jeeconf - Geb Spock

Повторное использование

● Блоки, повторяющиеся на многих страницах

● Блоки, повторяющиеся на одной странице

Page 39: 2014 Jeeconf - Geb Spock

Объявление модуля

class NavBarModule extends Module {

static content = {

homePageLink(to: HomePage) { $("a#home") }

profileLink(to: ProfilePage) { $("a#profile") }

}

}

Page 40: 2014 Jeeconf - Geb Spock

Использование модулей

class HomePage extends Page {

static content = {

navBarModule { module NavBarModule }

}

void goToProfilePage() {

navBarModule.profileLink.click()

}

}

Page 41: 2014 Jeeconf - Geb Spock

Пример 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") }

}

}

Page 42: 2014 Jeeconf - Geb Spock

Проблема поддержки

to HomePage

loginButton.click()

username = "user"

password = "password"

loginButton.click()

Page 43: 2014 Jeeconf - Geb Spock

Page Object builder

● Подход предложен Craig Atkinson

● По умолчанию Geb делегирует вызовы методов текущей странице

● Билдер делает эти вызовы явными

Page 44: 2014 Jeeconf - Geb Spock

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")

Вызов по цепочке

Page 45: 2014 Jeeconf - Geb Spock

Реализация HomePage

class HomePage extends Page {

static content = {

loginButton(to: LoginPage, wait: true) {

$("#loginButton")

}

}

LoginPage clickLoginButton() {

loginButton.click()

return browser.page

}

}

Page 46: 2014 Jeeconf - Geb Spock

Geb + Spock

Page 47: 2014 Jeeconf - Geb Spock

Архитектура

App

Browser

WebDriverGebgeb-spock(Testing adapter)

Spok

Page 48: 2014 Jeeconf - Geb Spock

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 }

}

}

Page 49: 2014 Jeeconf - Geb Spock

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'

Page 50: 2014 Jeeconf - Geb Spock

Проблемы

● Тестировались в основном длительные цепочки страниц

● Вся подготовка данных делалась через веб интерфейс

Page 51: 2014 Jeeconf - Geb Spock

Некрасивый тест

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

}

Page 52: 2014 Jeeconf - Geb Spock

Некрасивый тест

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

}

Page 53: 2014 Jeeconf - Geb Spock

Use case тестирование

Page 54: 2014 Jeeconf - Geb Spock

Чего мы хотим?

● Результат тестов не булевое значение

● Экранирование сценариев

● Упор на документирование

● Привлечение нетехнических специалистов для работы с тестами

Page 55: 2014 Jeeconf - Geb Spock

Экранирование сценариев

Чего мы хотим достигнуть?

● Каждый тест готовит данные для себя

● Тест должен знать как можно меньше информации о внутренностях приложения

● Тест не должен ломаться при рефакторингах

Page 56: 2014 Jeeconf - Geb Spock

Первая идея

А давайте обновлять базу?

Проблемы:

● Тесты знают много низкоуровневой информации о приложении

● Тысты очень чуствительны к изменениям в приложении

Page 57: 2014 Jeeconf - Geb Spock

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.”

Page 58: 2014 Jeeconf - Geb Spock

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()

Page 59: 2014 Jeeconf - Geb Spock

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()

Page 60: 2014 Jeeconf - Geb Spock

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()

Page 61: 2014 Jeeconf - Geb Spock

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

}

Page 62: 2014 Jeeconf - Geb Spock

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

}

Page 63: 2014 Jeeconf - Geb Spock

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

}

Page 64: 2014 Jeeconf - Geb Spock

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

}

Page 65: 2014 Jeeconf - Geb Spock

Некрасивый тест

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

}

Page 66: 2014 Jeeconf - Geb Spock

Красивый тест

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

Page 67: 2014 Jeeconf - Geb Spock

Отчеты

Page 68: 2014 Jeeconf - Geb Spock

Стандартный Grails отчет

Page 69: 2014 Jeeconf - Geb Spock

Стандартный Grails отчет

Page 70: 2014 Jeeconf - Geb Spock

Spock отчеты

Требования

● Логическая групировка тестов

● Возможность запуска группы тестов

● Человекочитаемая документация

Page 71: 2014 Jeeconf - Geb Spock

Пример отчета

Page 72: 2014 Jeeconf - Geb Spock
Page 73: 2014 Jeeconf - Geb Spock

Spock отчеты

Решение

● Расширили Athaydes Spock Reports

○ Добавили группировку

○ Возможность запуска группы спецификаций

Page 74: 2014 Jeeconf - Geb Spock

@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"

}

}

Page 75: 2014 Jeeconf - Geb Spock

Что дальше?

● Тестирование верстки

● Ускорение тестов

● Переход на Gherkin (Cucumber) для избежания дублирования коментариев и кода

Page 76: 2014 Jeeconf - Geb Spock

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")

}

}

Page 77: 2014 Jeeconf - Geb Spock

Вопросы?

Page 78: 2014 Jeeconf - Geb Spock

Всего хорошего, и спасибо за рыбу!


Recommended