Мой опыт тестирования в Ember
Андрей Михайлов (lolmaus)
Frotnend-разработчик в Perforce
https://lolma.us
https://github.com/lolmaus
https://perforce.com/products/helix-teamhub
Знакомство с методикой Cucumber
с помощью ember-cli-yadda
Пирамида потребностей
Пирамида тестов
Acceptance
Integration
Unit
Пирамида тестов
Acceptance
Integration
Unit
$$$
$
Пирамида тестов
Acceptance
Integration
Unit
$$$
$
Правильная пирамида тестов
Unit
Integration
Acceptance
Controller.extend({
spaceHorn: service(),
society: service(),
caramelize () {
const confusion = this.spaceHorn.honk()
return this.society.riot(confusion)
}
})
Цикл Red-Green-Refactor
Сначала пишем acceptance-тест
Пишем код,
чтобы тест
прошел
Рефакторим код
Цикл Red-Green-Refactor
Acceptance
тест
Код
Рефак-торинг
Unit
тест
Код
Рефак-торинг
Типичный acceptance test
test("apples should be sorted by id", function (assert) {
const user = server.create("user")
server.createList("apples", 20, {ownerId: user.id})
authenticateSession(user)
visit("/apples")
const apples = findAll(".apples .apple")
apples.forEach((currentApple, i) => {
if (i === 0) return
const currentValue = currentApple.querySelector(".apple-id").textContent
const previousApple = apples[i - 1]
const previousValue = previousApple.querySelector(".apple-id").textContent
assert.ok(
currentValue > previousValue,
`Apple ${i} id should be greater than apple ${i -1} id`
)
})
})
Применяем page object
test("apples should be sorted by id", function (assert) {
const user = server.create("user")
server.createList("apples", 20, {ownerId: user.id})
authenticateSession(user)
page.visit()
page.apples.forEach((currentApple, i) => {
if (i === 0) return
const previousApple = page.apples[i - 1]
assert.ok(
currentApple.id > previousApple.id,
`Apple ${i} id should be greater than apple ${i -1} id`
)
})
})
export default create({
visitable: "/apples",
apples: collection({
scope: ".apples",
itemScope: ".apple",
item: {
id: text('.apple-id')
}
})
})
Выносим логику в отдельные функции
let user, apples
function createUser () {
user = server.create("user")
}
function createApples () {
apples = server.createList("apples", 20, {ownerId: user.id})
}
function login () {
authenticateSession(user)
}
function visit () {
page.visit()
}
function applesShouldBeSortedById (assert) {
page.apples.forEach((currentApple, i) => {
if (i === 0) return
const previousApple = page.apples[i - 1]
assert.ok(
currentApple.id > previousApple.id,
`Apple ${i} id should be greater than apple ${i -1} id`
)
})
}
test("apples should be sorted by id", function (assert) {
createUser()
createApples()
login()
visit()
applesShouldBeSortedById(assert)
})
Переиспользуем функции
test("apples should be sorted by id (asc) by default", function (assert) {
createUser()
createApples()
login()
visit()
applesShouldBeSortedBy({column: "id", order: "asc", assert})
})
test("apples should be sorted by id (desc) when URL contains order=desc", function (assert) {
createUser()
createApples()
login()
visit({order: "desc"})
applesShouldBeSortedBy({column: "id", order: "desc", assert})
})
test("apples should be sorted by title (asc) when URL contains column=title", function (assert) {
createUser()
createApples()
login()
visit({column: "title"})
applesShouldBeSortedBy({column: "title", order: "asc", assert})
})
test("apples should be sorted by title (desc) when URL contains column=title and order=desc", function (assert) {
createUser()
createApples()
login()
visit({column: "title", order: "desc"})
applesShouldBeSortedBy({column: "title", order: "desc", assert})
})
DRY
const cases = [
{queryParams: { }, column: "id", order: "asc" },
{queryParams: { order: "asc" }, column: "id", order: "asc" },
{queryParams: { order: "desc"}, column: "id", order: "desc"},
{queryParams: {column: "title" }, column: "title", order: "asc" },
{queryParams: {column: "title", order: "asc" }, column: "title", order: "asc" },
{queryParams: {column: "title", order: "desc"}, column: "title", order: "desc"},
{queryParams: {column: "color" }, column: "color", order: "asc" },
{queryParams: {column: "color", order: "asc" }, column: "color", order: "asc" },
{queryParams: {column: "color", order: "desc"}, column: "color", order: "desc"},
]
cases.forEach(({queryParams, column, order}) => {
const params = JSON.stringify(queryParams)
test(`apples should be sorted by ${column} (${order}) when URL conains query params ${params}`, function (assert) {
createUser()
createApples()
login()
visit(queryParams)
applesShouldBeSortedBy({column, order, assert})
})
})
Cucumber, Gherkin, Yadda
Feature: Sorting apples
Scenario: Apples should be sorted by [Column] ([Order]) when URL contains QPs [QPs]
Given a user
And 20 apples
When user is authenticated
And user visits the apples page using QPs [QPs]
Then apples should be sorted by [Column] column in [Order] order
Where:
-----------------------------------------------------
| QPs | Column | Order |
| {} | id | asc |
| {order: "asc"} | id | asc |
| {order: "desc"} | id | asc |
| {column: "title"} | title | asc |
| {column: "title", order: "asc"} | title | asc |
| {column: "title", order: "desc"} | title | desc |
| {column: "color"} | color | asc |
| {column: "color", order: "asc"} | color | asc |
| {column: "color", order: "desc"} | color | desc |
-----------------------------------------------------
Мой способ
определения steps
{
"given a user" () {
this.ctx.user = server.create("user")
},
"given $count apples" (countStr) {
const count = parseInt(countStr, 10)
const onwerId = this.ctx.user.id
this.ctx.apples = server.createList("apple", count, {ownerId})
},
"when user is authenticated" () {
authenticateSession(this.ctx.user)
},
"when user visits the apples page using QPs $qps" (qpsStr) {
const qps = JSON.parse(qpsStr)
page.visit(qps)
},
"then apples should be sorted by $column column in $order order" (column, order, assert) {
page.apples.forEach((currentApple, i) => {
if (i === 0) return
const currentValue = currentApple[column].text
const previousApple = page.apples[i - 1]
const previousValue = previousApple[column].text
const result =
order === "asc" ? currentValue >= previousValue :
order === "desc" ? currentValue <= previousValue :
null
assert.ok(result, `Apple ${i} id should be ${order} than apple ${i - 1} id`)
})
}
}
Позьза от Cucumber для программиста
- Код тестов максимально легко читать, тесты не превращаются в балласт.
- Код эффективно структурирован, максимальное переиспользование кода (DRY).
- Переиспользование steps позволяет мгновенно создавать новые test cases.
- Максимальная самодисциплина: сначала расписываем все возможные user stories, только после этого приступаем к программированию.
- Работодатель счастлив.
Всем спасибо! ^_^
ember-cli-yadda
By Andrey Mikhaylov (lolmaus)
ember-cli-yadda
- 886