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.
3. Главный вопрос жизни
вселенной и всего такого
Как в любой момент быть уверенным в
качестве текущей версии вашего
приложения и дать возможность людям
понять что же оно делает?
4. Кто я?
● C Groovy / Grails начиная с 2008 года
● Успешно внедрили и используем Geb /
Spock
● - Groovy / Grails курс
● Первый разработчик в TransferWise,
сейчас 100+ человек
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. Простой 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. Простой 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. Простой 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. Простой 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. Простой 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. Метод $()
$(«css selector», «index or range», «attribute / text matchers»)
Примеры
$("div") // все div элементы
$("div", 0) // первый div элемент
$("div", 0..2) // первых три div элемента
// Третий H2 элемент с текстом “Geb”
$("h2", 2, text: "Geb")
43. Page Object builder
● Подход предложен Craig Atkinson
● По умолчанию Geb делегирует вызовы
методов текущей странице
● Билдер делает эти вызовы явными
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. 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. Проблемы
● Тестировались в основном длительные
цепочки страниц
● Вся подготовка данных делалась через
веб интерфейс
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. Некрасивый тест
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
}
54. Чего мы хотим?
● Результат тестов не булевое значение
● Экранирование сценариев
● Упор на документирование
● Привлечение нетехнических
специалистов для работы с тестами
55. Экранирование сценариев
Чего мы хотим достигнуть?
● Каждый тест готовит данные для себя
● Тест должен знать как можно меньше
информации о внутренностях
приложения
● Тест не должен ломаться при
рефакторингах
56. Первая идея
А давайте обновлять базу?
Проблемы:
● Тесты знают много низкоуровневой
информации о приложении
● Тысты очень чуствительны к
изменениям в приложении
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. 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. 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. 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. 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. 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. 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. 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. Некрасивый тест
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. Красивый тест
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
75. Что дальше?
● Тестирование верстки
● Ускорение тестов
● Переход на Gherkin (Cucumber) для
избежания дублирования коментариев и
кода
76. 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")
}
}