View
267
Download
0
Category
Preview:
Citation preview
@juliaviluhina @yashaka
Better Bullshit Driven Development
@juliaviluhina @yashaka
automician.com seleniumcourses.com
About
Plan
Preface: Classic BDD & xUnit examples
BDD Intro
Better BDD with xUnit + Allure
Q&A
BDD?
xUnit stylepublic class DiasporaTest extends BaseTest { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Test public void shareMessage() { loginPage.open(); loginPage.signIn(Users.Selenide.username, Users.Selenide.password); newPost.start(); newPost.write("Selenide 4.2 released!"); newPost.share(); stream.shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
xUnit stylepublic class DiasporaTest extends BaseTest { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Test public void shareMessage() { loginPage.open(); loginPage.signIn(Users.Selenide.username, Users.Selenide.password); newPost.start(); newPost.write("Selenide 4.2 released!"); newPost.share(); stream.shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
BDD style (option 1)@defaultsFeature: Diaspora Scenario: Share message Given I open login page And I do sign in with credentials: 'selenide', 'xxxxxxxx' When I start new post And write new post message: 'Selenide 4.2 is released!' And share new post Then stream should have post with index '1' of text 'Selenide 4.2 is released!'
BDD style (option 1)public class DiasporaStepdefs { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Given("^I open login page$") public void I_open_login_page(){ loginPage.open(); } @And("^I do sign in with credentials: '(.+)', '(.+)'$") public void I_do_sign_in_with_credentials_selenide_yagniyagni(String username, String password) { loginPage.signIn(username, password); } ...
BDD style (option 1)
...@When("^I start new post$")public void I_start_new_post(){ newPost.start();}@And("^write new post message: '(.+)'$")public void write_new_post_message_Selenide_is_released_(String text){ newPost.write(text);} ...
BDD style (option 1)
... @And("^share new post$") public void share_new_post() { newPost.share(); } @Then("^stream should have post with index '( \\d+)' of text '(.+)'$") public void stream_should_have_post_with_index_of_text_Selenide_is_released_(int index, String text) { stream.shouldHavePostWithText(index-1, text); }}
BDD style (option 2)
@defaultsFeature: Diaspora Scenario: Share message Given I signed in from login page with credentials: 'selenide', 'xxxx' When I publish new post: 'Selenide 4.2 is released!' Then I should see new post 'Selenide 4.2 is released!' in the top of stream
BDD style (option 2)
public class DiasporaStepdefs { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Given("^I signed in from login page with credentials: '(.+)', '(.+)'$") public void I_signed_in_from_login_page_with_credentials_selenide_yagniyagni(String username, String password) { loginPage.open(); loginPage.signIn(username, password); } ...
BDD style (option 2)...
@When("^I publish new post: '(.+)'$") public void I_publish_new_post_Selenide_is_released_(String text) { newPost.start(); newPost.write(text); newPost.share(); } @Then("^I should see new post '(.+)' in the top of stream$") public void I_should_see_new_post_Selenide_is_released_in_the_top_of_stream(String text) { stream.shouldHavePostWithText(0, text); }}
BDD Intro
= ?BDD
= ? human readable
scenarios
…
BDD
= ? human readable
scenarios
pretty reports
BDD
= ... human readable
scenarios
pretty reports
BDD
= OptimisedATDD BDD
=BDD OptimisedATDD
giving as a bonus: readable scenarios and
reports
= OptimisedATDD
=
?
BDD
= OptimisedATDD
=
Devs write tests first
BDD
= OptimisedATDD
=
Devs write acceptance tests first
BDD
= OptimisedATDD
=
Devs write acceptance tests first
based on scenarios already written by BA/PO
BDD
= OptimisedATDD
=
Devs write acceptance tests first
based on scenarios already written by BA/PO
BDD
=BDD OptimisedATDD
=
Devs write acceptance tests first
based on scenarios already written by BA/PO
unbiased thinking of end product
doing things once (reqs are 2 in 1 - are already automatable)
=BDD OptimisedATDD
=
Devs write acceptance tests first
based on scenarios already written by BA/PO
unbiased thinking of end product
doing things once (reqs are 2 in 1 - are already automatable)
OptimisedATDD
=
Devs write acceptance tests first
based on scenarios already written by BA/PO
unbiased thinking of end product
doing things once (reqs are 2 in 1 - are already automatable)
Behaviour Driven Development
OptimisedATDD
=
Devs write acceptance tests first
based on scenarios already written by BA/PO
unbiased thinking of end product
doing things once (reqs are 2 in 1 - are already automatable)
Beh?viour Driven Development
OptimisedATDD
=
Devs write acceptance tests first
based on scenarios already written by BA/PO
Automation engineers write tests after.
adding extra “readable scenarios” layer for “good
reports”
Beh?viour Driven Development
vs
vs
OptimisedATDD
=
Devs write acceptance tests first
based on scenarios already written by BA/PO
Automation engineers write tests after.
adding extra “readable scenarios” layer for “good
reports”
Bullshit Driven Development
vs
vs
WTF?
adding additional limited pseudo-programming layer just to make tests readable
and with good reports?
WTF?
adding additional limited pseudo-programming layer just to make tests readable
and with good reports?
Better “Bullshit” Driven Development
without additional limited pseudo programming layers ;)
Readable xUnit
pom.xml with just xUnit + Selenide<dependencies> <dependency> <groupId>com.codeborne </groupId> <artifactId>selenide </artifactId> <version>4.2 </version> <scope>test </scope> </dependency> <dependency> <groupId>junit </groupId> <artifactId>junit </artifactId> <version>4.12 </version> </dependency></dependencies>
xUnit + PageObject with readable stepspublic class DiasporaTest extends BaseTest { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Test public void shareMessage() { loginPage.open(); loginPage.signIn(Users.Selenide.username, Users.Selenide.password); newPost.start(); newPost.write("Selenide 4.2 released!"); newPost.share(); stream.shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
xUnit + PageObject with readable stepspublic class DiasporaTest extends BaseTest { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Test public void shareMessage() { loginPage.open(); loginPage.signIn(Users.Selenide.username, Users.Selenide.password); newPost.start(); newPost.write("Selenide 4.2 released!"); newPost.share(); stream.shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
xUnit + PageObject with readable stepspublic class DiasporaTest extends BaseTest { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Test public void shareMessage() { loginPage.open(); loginPage.signIn(Users.Selenide.username, Users.Selenide.password); newPost.start(); newPost.write("Selenide 4.2 released!"); newPost.share(); stream.shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
xUnit + PageObject with readable steps
public class LoginPage { public void open() { Selenide.open("/users/sign_in"); } public void signIn(String username, String password) { Form form = new Form($("#new_user")); form.set("USERNAME", username); form.set("PASSWORD", password); form.submit(); }}
xUnit + PageObject with readable steps
Report :(
Better xUnit reports with Allure
<dependencies> ... <dependency> <groupId>ru.yandex.qatools.allure </groupId> <artifactId>allure-junit-adaptor </artifactId> <version>${allure.version} </version> </dependency></dependencies>
pom.xml with Allure configuration
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins </groupId> <artifactId>maven-surefire-plugin </artifactId> <version>2.19.1 </version> <configuration> <testFailureIgnore>false </testFailureIgnore> <argLine> -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" </argLine> <properties> <property> <name>listener </name> <value>ru.yandex.qatools.allure.junit.AllureRunListener </value> </property> </properties> </configuration> <dependencies> <dependency> <groupId>org.aspectj </groupId> <artifactId>aspectjweaver </artifactId> <version>${aspectj.version} </version> </dependency> </dependencies> </plugin> ... </plugins></build>
<build> <plugins> ... <plugin> <groupId>org.mortbay.jetty </groupId> <artifactId>jetty-maven-plugin </artifactId> <configuration> <webAppSourceDirectory>${project.build.directory}/site/allure-maven-plugin </webAppSourceDirectory> <stopKey /> <stopPort /> </configuration> </plugin> </plugins></build>
<reporting> <excludeDefaults>true </excludeDefaults> <plugins> <plugin> <groupId>ru.yandex.qatools.allure </groupId> <artifactId>allure-maven-plugin </artifactId> <version>2.5 </version> </plugin> </plugins></reporting>
xUnit + PageObject with reportable @Steps :)
public class DiasporaTest extends BaseTest { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Test public void shareMessage() { loginPage.open(); loginPage.signIn(Users.Selenide.username, Users.Selenide.password); newPost.start(); newPost.write("Selenide 4.2 released!"); newPost.share(); stream.shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
xUnit + PageObject with reportable @Steps :)public class LoginPage { @Step public void open() { Selenide.open("/users/sign_in"); } @Step public void signIn(String username, String password) { Form form = new Form($("#new_user")); form.set("USERNAME", username); form.set("PASSWORD", password); form.submit(); }}
xUnit + PageObject with reportable @Steps :)
Report
xUnit + PageObject with reportable @Steps :)
Report
xUnit + PageObject with reportable @Steps :)
Report
qwerty1234
xUnit + PageObject with reportable @Steps :)
Report
qwerty1234
qwerty1234
Missed “objects info” in reports :(public class DiasporaTest extends BaseTest { LoginPage loginPage = new LoginPage(); NewPost newPost = new NewPost(); Stream stream = new Stream(); @Test public void shareMessage() { loginPage.open(); loginPage.signIn(Users.Selenide.username, Users.Selenide.password); newPost.start(); newPost.write("Selenide 4.2 released!"); newPost.share(); stream.shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
Report :(
Missed “objects info” in reports :(
What is opened?Where do we do signin?
What do we start?
Where?
Fixing “missed objects” with One Entry Point to PageObjects :)
public class DiasporaTest extends BaseTest { Diaspora diaspora = new Diaspora(); @Test public void shareMessage() { diaspora.loginPage().open(); diaspora.loginPage() .signIn(Users.Selenide.username, Users.Selenide.password); diaspora.newPost().start(); diaspora.newPost().write("Selenide 4.2 released!"); diaspora.newPost().share(); diaspora.stream().shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
Fixing “missed objects” with One Entry Point to PageObjects :)public class Diaspora { @Step public LoginPage loginPage() { return new LoginPage(); } @Step public NewPost newPost() { return new NewPost(); } @Step public Stream stream() { return new Stream(); } ...}
Fixing “missed objects” with One Entry Point to PageObjects :)public class Diaspora { @Step public LoginPage loginPage() { return new LoginPage(); } @Step public NewPost newPost() { return new NewPost(); } @Step public Stream stream() { return new Stream(); } ...}
The simplest impl.with
redundant objects, not actually influencing
performance much
Fixing “missed objects” with One Entry Point to PageObjects :)
public class Diaspora { private final LoginPage loginPage; private final NewPost newPost; private final Stream stream; public Diaspora() { this.loginPage = new LoginPage(); this.newPost = new NewPost(); this.stream = new Stream(); } @Step public LoginPage loginPage() { return this.loginPage; } ...}
but in case you bother…
Fixing “missed objects” with One Entry Point to PageObjects :)
public class Application { private final LoginPage loginPage; private final NewPost newPost; private final Stream stream; public Application() { this.loginPage = new LoginPage(); this.newPost = new NewPost(); this.stream = new Stream(); } @Step public LoginPage loginPage() { return this.loginPage; } ...}
aka Application Manager
Fixing “missed objects” with One Entry Point to PageObjects :)
public class DiasporaTest extends BaseTest { Application app = new Application(); @Test public void shareMessage() { app.loginPage().open(); app.loginPage() .signIn(Users.Selenide.username, Users.Selenide.password); app.newPost().start(); app.newPost().write("Selenide 4.2 released!"); app.newPost().share(); app.stream().shouldHavePostWithText(0, "Selenide 4.2 released!"); }}
aka Application Manager
Fixing “missed objects” with One Entry Point to PageObjects :)
Report
Fixing “missed objects” with One Entry Point to PageObjects :)
Report
May be a bit “too long” while reading to find
“logical code sentences”…
Fixing “missed objects” with One Entry Point to PageObjects :)
Report
But if considerthat “each sentence” is two lines
it becomes to make sense:)
Applying Fluent PageObject for easier scenarios …
public class DiasporaTest extends BaseTest { Diaspora diaspora = new Diaspora(); @Test public void shareMessage() { diaspora.loginPage().open().signIn( Users.Selenide.username, Users.Selenide.password); diaspora.newPost().start().write("Selenide 4.2 released!").share(); diaspora.stream().post(0).shouldBe("Selenide 4.2 released!"); }}
Applying Fluent PageObject for easier scenarios …
public class DiasporaTest extends BaseTest { Diaspora diaspora = new Diaspora(); @Test public void shareMessage() {
diaspora.loginPage().open().signIn( Users.Selenide.username, Users.Selenide.password);
diaspora.newPost().start().write("Selenide 4.2 released!").share(); diaspora.stream().post(0).shouldBe("Selenide 4.2 released!"); }}
Applying Fluent PageObject for easier scenarios …
public class DiasporaTest extends BaseTest { Diaspora diaspora = new Diaspora(); @Test public void shareMessage() {
diaspora.loginPage().open().signIn( Users.Selenide.username, Users.Selenide.password);
diaspora.newPost().start().write("Selenide 4.2 released!").share(); diaspora.stream().post(0).shouldBe("Selenide 4.2 released!"); }}
Applying Fluent PageObject for easier scenarios …public class NewPost { ... @Step public NewPost start() { this.textArea.click(); return this; } @Step public NewPost write(String text) { this.textArea.setValue(text); return this; } @Step public void share(){ this.container.find("#submit").click(); }}
Applying Fluent PageObject for easier scenarios …public class NewPost { ... @Step public NewPost start() { this.textArea.click(); return this; } @Step public NewPost write(String text) { this.textArea.setValue(text); return this; } @Step public void share(){ this.container.find("#submit").click(); }}
no need to be “fluent” here, as we do not know, where user should proceed a flow
… may lead to “missing flow” in reports :(
Report :(
Where does the sentence start and finish?
… may lead to “missing flow” in reports :(
Report :(
Where does the sentence start and finish?
Fixing “missed flow” with “gherkin” pseudo-steps…
public class DiasporaTest extends BaseTest { Diaspora diaspora = new Diaspora(); @Test public void shareMessage() { GIVEN("Logged in from login page"); diaspora.loginPage().open().signIn( Users.Selenide.username, Users.Selenide.password); WHEN("Posted new message"); diaspora.newPost().start().write("Selenide 4.2 released!").share(); THEN("Message should appear in the stream"); diaspora.stream().post(0).shouldBe("Selenide 4.2 released!"); }}
Fixing “missed flow” with “gherkin” pseudo-steps…public class Gherkin { @Step public static void GIVEN(String step) {} @Step public static void AND(String step) {} @Step public static void WHEN(String step) {} @Step public static void THEN(String step) {}}
Fixing “missed flow” with “gherkin” pseudo-steps…
Report :)
Fixing “missed flow” with “gherkin” pseudo-steps…
Report :)
Each new “code sentence”starts with a “gherkin” keyword
Fixing “missed flow” with “gherkin” pseudo-steps…
Report (more complicated example)
Fixing “missed flow” with “gherkin” pseudo-steps…
Report (more complicated example)
Illustration (more complicated example)
Fixing “missed flow” with “gherkin” pseudo-steps…
… leads sometimes to even more to readable code
THEN("Connect first cell of this column to first row from data storage");app.testTable().row(0).cell(0).fill("1").hover();EXPECT("Connection works by showing connected data in a cell tooltip (on hover)");app.testTable().toolTip() .shouldHaveKeyRowCells("", "user", "password") .shouldHaveValueRowCells("1", "vasya", "pupkin1234");
Implementation (more complicated example)
… leads sometimes to not much more readable
public class DiasporaTest extends BaseTest { Diaspora diaspora = new Diaspora(); @Test public void shareMessage() { GIVEN("Logged in from login page"); diaspora.loginPage().open().signIn( Users.Selenide.username, Users.Selenide.password); WHEN("Posted new message"); diaspora.newPost().start().write("Selenide 4.2 released!").share(); THEN("Message should appear in the stream"); diaspora.stream().post(0).shouldBe("Selenide 4.2 released!"); }}
… and often redundant code :|
public class DiasporaTest extends BaseTest { Diaspora diaspora = new Diaspora(); @Test public void shareMessage() { GIVEN("Logged in from login page"); diaspora.loginPage().open().signIn( Users.Selenide.username, Users.Selenide.password); WHEN("Posted new message"); diaspora.newPost().start().write("Selenide 4.2 released!").share(); THEN("Message should appear in the stream"); diaspora.stream().post(0).shouldBe("Selenide 4.2 released!"); }}
just repeats already readable “code”
Fixing everything:) with “gherkin” style of One Entry Point
public class DiasporaTest extends GherkinTest { @Test public void shareMessage() { GIVEN().loginPage().open().signIn( Users.Selenide.username, Users.Selenide.password); WHEN().newPost().start().write("Selenide 4.2 released!").share(); THEN().stream().post(0).shouldBe("Selenide 4.2 released!"); }}
Fixing everything:) with “gherkin” style of One Entry Point
public class GherkinTest { ... Diaspora diaspora = new Diaspora(); @Step public Diaspora GIVEN(String ... comments) { return this.diaspora; } @Step public Diaspora AND(String ... comments) { return this.diaspora; } @Step public Diaspora WHEN(String ... comments) { return this.diaspora; } @Step public Diaspora THEN(String ... comments) { return this.diaspora; }}
Fixing everything:) with “gherkin” style of One Entry Point
Report :D
Each new “code sentence”starts with a “gherkin” keyword
Fixing everything:) with “gherkin” style of One Entry Point
THEN("Connect first cell of this column to first row from data storage") .testTable().row(0).cell(0).fill("1").hover();EXPECT("Connection works by showing connected data in a cell tooltip (on hover)") .testTable().toolTip() .shouldHaveKeyRowCells("", "user", "password") .shouldHaveValueRowCells("1", "vasya", "pupkin1234");
Implementation (more complicated example)
with additional step comments when needed
Fixing everything:) with “gherkin” style of One Entry Point
Report (more complicated example)
SummaryGIVEN
no BA writing Gherkin
AND automation engineers writing tests after
WHEN
xUnit
AND Fluent PageObjects with Allure readable @Steps
AND Gherkin style of One Entry Point (aka Application Manager)
THEN
Bullshit Driven Development is Better
Better Bullshit Driven Development withxUnit, Fluent PageObjects, Allure @Steps, “Gherkined" One Entry Point
• all-powerful programming language over textual-pseudo-language
• full IDE support (autocompletion, auto-generate, etc.)
• 1 abstraction layer less => simpler and faster in implementation
• still fully readable and with pretty good reports
• newcomers friendly
• Just One Entry Point to all Application Model
• structured with granular Fluent PageObjects => easier to learn&use via autocompletion
Better Bullshit Driven Development withxUnit, Fluent PageObjects, Allure @Steps, “Gherkined" One Entry Point
• all-powerful programming language over textual-pseudo-language
• full IDE support (autocompletion, auto-generate, etc.)
• 1 abstraction layer less => simpler and faster in implementation
• still fully readable and with pretty good reports
• newcomers friendly
• Just One Entry Point to all Application Model
• structured with granular Fluent PageObjects => easier to learn&use via autocompletion
Better Bullshit Driven Development withxUnit, Fluent PageObjects, Allure @Steps, “Gherkined" One Entry Point
• all-powerful programming language over textual-pseudo-language
• full IDE support (autocompletion, auto-generate, etc.)
• 1 abstraction layer less => simpler and faster in implementation
• still fully readable and with pretty good reports
• newcomers friendly
• Just One Entry Point to all Application Model
• structured with granular Fluent PageObjects => easier to learn&use via autocompletion
Better Bullshit Driven Development withxUnit, Fluent PageObjects, Allure @Steps, “Gherkined" One Entry Point
• all-powerful programming language over textual-pseudo-language
• full IDE support (autocompletion, auto-generate, etc.)
• 1 abstraction layer less => simpler and faster in implementation
• still fully readable and with pretty good reports
• newcomers friendly
• Just One Entry Point to all Application Model
• structured with granular Fluent PageObjects => easier to learn&use via autocompletion
Better Bullshit Driven Development withxUnit, Fluent PageObjects, Allure @Steps, “Gherkined" One Entry Point
• all-powerful programming language over textual-pseudo-language
• full IDE support (autocompletion, auto-generate, etc.)
• 1 abstraction layer less => simpler and faster in implementation
• still fully readable and with pretty good reports
• newcomers friendly
• Just One Entry Point to all Application Model
• structured with granular Fluent PageObjects => easier to learn&use via autocompletion
Better Bullshit Driven Development ;)public class DiasporaTest extends GherkinTest { @Test public void shareMessage() { GIVEN().loginPage().open().signIn( Users.Selenide.username, Users.Selenide.password); WHEN().newPost().start().write("Selenide 4.2 released!").share(); THEN().stream().post(0).shouldBe("Selenide 4.2 released!"); }}
Better Bullshit Driven Development ;)
More ideas• tune Allure to report objects (via calling their toString() method)
• already possible?
• contribute?
• new Allure Feature: log object classes
• contribute?
AfterwordsThere are good practices in context,
but there are no best practices.
(c) Cem Kaner, James Bach
Q&A
Thank you!
github.com/automician automician.com
seleniumcourses.com
juliaviluhina @
@juliaviluhina @yashaka
You are welcome;)Selene in Python
NSelene in C#
Selenide snippets in Java
Yashaka talks src code
Selenide User Guide
@juliaviluhina @yashaka
Recommended