frotnend developer at Perforce
https://lolma.us
https://github.com/lolmaus
https://perforce.com/products/helix-teamhub
Acceptance
Integration
Unit
$$$
$
Unit
Integration
Acceptance
Controller.extend({
spaceHorn: service(),
society: service(),
caramelize () {
const confusion = this.spaceHorn.honk()
return this.society.riot(confusion)
}
})
Is a unit test needed here?
Test-driven development
Test
Code
Refactor
User story
Behavior-driven development
Page
Component
Model
Service
Network
adapter
Start by writing a full feature spec in form of user stories
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`
)
})
})
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')
}
})
})
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`
)
})
})
createUser()
createApples(amount)
login()
visit()
applesShouldBeSortedById(assert)
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})
})
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})
})
})
Scenario: Apples should be sorted by id
Given a user
And 20 apples
When user is authenticated
And user visits the apples page
Then apples should be sorted by id
test("apples should be sorted by id", function (assert) {
createUser()
createApples()
login()
visit()
applesShouldBeSortedById(assert)
})
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 |
-----------------------------------------------------
{
"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`)
})
}
}
let user, apples
function createUser () {
user = server.create("user")
}
function createApples (count) {
apples = server.createList("apples", count , {ownerId: user.id})
}
function login () {
authenticateSession(user)
}
function visit (queryParams = {}) {
page.visit(qps)
}
function applesShouldBeSortedBy (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`)
})
}
1. Complexity
Current setup
With Cucumber
2. Separation of concerns
Current setup
With Cucumber
Test cases are separated from technical implementation.
Technical implementation is broken into small, atomic steps.
Steps are composed naturally, so glue code almost doesn’t exist.
3. Code Reusability
Current setup
With Cucumber
We still keep writing duplicate test code every month
Steps are reusable.
4. Code Readability
Current setup
With Cucumber
Test cases are readable.
Easy to read through & validate.
5. Code Maitainability
Current setup
With Cucumber
Little maintenance needed.
Maintenance is easy.
6. Ease of validation
Current setup
With Cucumber
Easy to validate.
Pure English.
7. Workflow integration
Current setup
With Cucumber
Testers, designers, product owners can participate in writing features.
...and in validating features.
Feature creation workflow is continuous.
8. Discipline
Current setup
With Cucumber
Strict methodology and code style greatly contributes to code quality.
9. Coverage
Current setup
With Cucumber
Cucumber methodology imposes maximum coverage.
Developers are less likely to cut corners.
10. Code unification
Current setup
With Cucumber
Cucumber imposes a lot of structure.
It's harder to stray.
10. Speed of writing tests
Current setup
With Cucumber
Generally slower.
Pays off greatly later.
TL/DL
That's all! Thx everyone!