BDD post-mortem
Book: "The Inmates Are Running The Asylum" by Alan Cooper
- UX should be done ahead of development
-
App development is a conflict of interests between users and developers
- When developers get to decide the design of the app, they neglect users' problems and goals
Two things I want to improve about tests:
- ease of implementation
- ease of validation
How existing tests are organized
test("visiting new repository", withChai(async function (expect) {
login();
await page.visit({ projectId: this.project.shortName });
m = "current URL";
expect(currentURL(), m).equal("/hth/projects/platform/repositories/new");
m = "renders form";
expect(page.form.exists, m).true;
}));
tests/acceptance/new-repository-test.js
import { test } from "qunit";
import { withChai } from "ember-cli-chai/qunit";
import moduleForAcceptance from "sangria/tests/helpers/module-for-acceptance";
import page from "sangria/tests/pages/new-repository";
let m;
moduleForAcceptance("New Repository", {
beforeEach() {
this.project = server.create("project", {
shortName: "platform",
automaticDepotCreation: false,
});
},
});
test("visiting new repository", withChai(async function (expect) {
login();
await page.visit({ projectId: this.project.shortName });
m = "current URL";
expect(currentURL(), m).equal("/hth/projects/platform/repositories/new");
m = "renders form";
expect(page.form.exists, m).true;
}));
import { create, visitable } from "ember-cli-page-object";
import testSelector from "ember-test-selectors";
import { t } from "sangria/tests/pages/_helpers/component";
import hthSelect from "sangria/tests/pages/components/hth-select";
export default create({
visit: visitable("/hth/projects/:projectId/repositories/new"),
form: t("newRepository-form", {
repositoryTypeSelect: {
...hthSelect,
scope: testSelector("newRepository-repositoryTypeSelect"),
},
/* ... */
tests/pages/new-repository.js
{{! Render `helix_path` errors here if the actual input below is hidden}}
{{#if (and repository.errors.helix_path (not isHelixGitAndHasNoAutomaticDepotCreation))}}
{{helix-errors errors=repository.errors.helix_path}}
{{/if}}
{{#if hasEnabledRepositoryTypes}}
<form
data-test-newRepository-form
{{action "submit" on="submit"}}
>
{{hth-select
class="margin-bottom-15"
data-test-newRepository-repositoryTypeSelect=true
block=true
content=enabledRepositoryTypes
focusOnInsert=true
optionLabelIconName="content.value"
optionLabelPath="content.label"
optionValuePath="content.value"
triggerClass="block button"
triggerIconName=repository.type
app/components/repository-form/template.hbs
A typical acceptance test
["project", "repository"].forEach((type) => (
["admin", "master", "manager", "developer", "guest"].forEach((role) => (
test(`${type} role handling for role: ${role}`, withChai(async function (expect) {
_.times(1, createTag.bind(this));
const user = server.create("user", { companyAdmin: false });
if (type === "project") {
createProjectUserWithRole(this.project, user, role);
} else {
server.db.repositories.update(this.repository.id, { authorizationEnabled: true });
createRepositoryUserWithRole(this.repository, user, role);
}
login(user);
await visitRepository(this);
// Tags rendering
if (role === "manager" || role === "guest") {
m = "does not render delete tag link";
expect(page.docker.table.tags(0).deleteConfirmationTrigger.exists, m).false;
} else {
m = "renders delete tag link";
expect(page.docker.table.tags(0).deleteConfirmationTrigger.exists, m).true;
}
// Description rendering
if (type === "project" && role === "admin") {
m = "renders repository description as editable";
expect(page.docker.description.isEditable(), m).true;
} else {
m = "renders repository description as non-editable";
expect(page.docker.description.isEditable(), m).false;
}
}))
))
));
My new approach to Cucumber
"given a repository with $props"(props) {
const params = parseProps(props, {
id: { regex: /@(\w+)/ },
"short name": { regex: /`(\w+)`/ },
"type": { regex: /`(\w+)`/ },
"project": { regex: /@(\w+)/, type: "record" },
});
return server.create("repository", params);
}
Feature: Repository Tree - Docker
Background:
Given current user is logged in
And a project with id: @platform and short name: `platform`
And a repository with id: @docky, project: @platform, short name: `docky` and type: `docker`
And a tag with id: @tag-1, repository: @docky and size: 400000
And a user with id: @jd and company admin: false
Scenario: User with project role [Role] should see certain UI controls
Given a project role with id: @role, project: @project and role: `[Role]`
And a project user with project: @project, user: @jd and role: @role
When user visits `/hth/projects/platform/repositories/docky`
Then <Delete Tag Button> should be [Delete Button Visibility]
And <Toggle Editing Button> in <Repository Description> should be [Editing Button Visibility]
Where:
--------------------------------------------------------------------
| Role | Delete Button Visibility | Editing Button Visibility |
| admin | be visible | be visible |
| master | be visible | NOT be visible |
| manager | NOT be visible | NOT be visible |
| developer | be visible | NOT be visible |
| guest | NOT be visible | NOT be visible |
--------------------------------------------------------------------
Scenario: User with repository role [Role] should see certain UI controls
Given a repository role with id: @role, project: @project and role: `[Role]`
And repository @docky authorization enabled is true
When user visits `/hth/projects/platform/repositories/docky`
Then <Delete Tag Button> should [Delete Button Visibility]
And <Toggle Editing Button> in <Repository Description> should NOT be visible
Where:
----------------------------------------
| Role | Delete Button Visibility |
| admin | visible |
| master | visible |
| manager | NOT visible |
| developer | visible |
| guest | NOT visible |
----------------------------------------
{{#if isFileManagementEnabled}}
<div class="manage-files">
{{#confirm-inline
isConfirming=isConfirmingDelete
didConfirm=(action "deleteTag")
}}
{{hth-icon "trash-outline"
data-test-label="<Delete Tag Button>"
click=(toggle "isConfirmingDelete" this)
}}
{{/confirm-inline}}
</div>
{{/if}}
{{else}}
<div class="editable-content">
{{yield}}
<span class="editable-actions">
<a
href="#" {{action "setEditing" true}}
class="edit"
data-test-label="<Toggle Editing Button>"
>
{{hth-icon "edit-alt"}}
</a>
</span>
</div>
{{/if}}
Benefits
- Create a new test by simply writing a user story
- Can copy-paste steps from existing user stories
- Surprisingly versatile and full-featured
- Exposes the truth directly in user story text
- Does not prevent from using the old approach when absolutely necessary
Cucumber Recap
By Andrey Mikhaylov (lolmaus)
Cucumber Recap
- 616