Session présentée à Agile Tour Montréal 2016
Si vous rencontrez des défis dans l’automatisation de vos tests d’acceptation ou si vous vous posez des questions sur la meilleure marche à suivre, cette séance est pour vous.
Découvrez comment mettre en place une batterie de tests d’acceptation utile et pérenne en ayant du plaisir à le faire. Attention, code en vue !
9. Tests instables
• Échouent de façon imprévisible
• Dépendent d’un jeu unique et vivant de données de test
• Dépendent de systèmes externes hors de notre contrôle
• Gèrent mal la nature asynchrone du Web
10. Visez l’atomicité
• Partez d’un état initial connu minimal
• Utilisez le jeu de données minimal suffisant pour le scénario décrit
• Nettoyez avant plutôt qu’après
• Remplacez les systèmes externes par des « faux » qui sont programmables et
que vous contrôlez
11. Utilisez vos API
public void registerUser(String email, String password) {
HttpResponse response =
request.content(Form.urlEncoded()
.addField("email", email)
.addField("password", password)
.addField("conditions", "on"))
.post("/accounts");
// Pour améliorer le diagnostique du test
assertThat(response).hasStatusCode(303);
}
13. Acceptez l’asynchronisme
BrowserDriver browser = new BrowserDriver(
new UnsynchronizedProber(2000, 50),
new FirefoxDriver());
browser.navigate().to(“http://somedomain/url_that_delays_loading");
browser.element(By.id(“some-dynamic-element")).hasText("Loaded");
browser.element(By.id(“some-button”)).click();
java.lang.AssertionError:
Tried to:
check that an element by id "some-button" is enabled
but:
it was disabled
14. Manque d’abstraction
DesiredCapabilities capabilities = DesiredCapabilities.firefox();
WebDriver driver = new FirefoxDriver(capabilities);
// Enter username and password
driver.findElement(By.id("username")).sendKeys("Bob");
driver.findElement(By.id("password")).sendKeys("secret");
// Click login button
driver.findElement(By.id("login")).submit();
// Wait for home page to load
WebDriverWait wait = new WebDriverWait(driver, 5000);
wait.until(ExpectedConditions.titleIs("Home"));
// Check the greeting message
String greeting = driver.findElement(By.id("greeting")).getText();
assertThat(greeting, equalTo("Welcome, Bob!"));
15. Trop de détails
Scenario: Successful login
Given a user "Bob" with password "secret"
And I am on the login page
# Ces lignes là vont toujours ensemble
And I fill in "Username" with "Bob"
And I fill in "Password" with "secret"
# J’ai vraiment besoin de connaître tous ces détails ?
When I press "Log In"
Then I should see "Welcome, Bob!"
16. Page Objects
DesiredCapabilities capabilities = DesiredCapabilities.firefox();
WebDriver driver = new FirefoxDriver(capabilities);
// Euh, vraiment ?
LogInPage loginPage = PageFactory.initElements(driver,
LogInPage.class);
// Voilà la partie intéressante
HomePage page = loginPage.loginAs("Bob", "secret");
// Et si l’affichage est asynchrone ?
assertThat(page.greetingMessage(), equalTo("Welcome, Bob!"));
17. Tests liés aux conditions d’acceptation
Scenario: Successful login
Given the user "Bob" has registered
When he logs in successfully
Then he should see "Welcome, Bob!"
18. Tests des récits utilisateurs
• Mènent à un trop grand nombre de tests
• Créent une batterie de tests difficile à maintenir
• Diminuent la valeurs des tests d’acceptation comme source de
documentation fonctionnelle
• Ne renseignent pas sur la valeur disponible aux utilisateurs
19. Testez les parcours utilisateurs
• Testez les interactions complètes d’un utilisateur avec le système
en vue de l’atteinte d’un objectif donné
• Utilisez un petit nombre de tests de parcours utilisateurs seulement
pour tester l’intégration de l’ensemble du système
20. Ne cherchez pas à être exhaustif
@Test
public void joinsToGetPremiumFeaturesBySelectingAPayingPlan() {
Join registration = anonymous.signUp().as(bob());
User bob = registration.selectPayingPlan("micro")
.enterBillingDetails("5555555555554444",
"12/18",
"999");
bob.manageAccount()
.showsCurrentlyOnPlan("micro")
.seesCreditCardDetails("**** **** **** 4444", "12/18");
}
21. Pensez comme des utilisateurs
• Rôles : Qui ?
• Objectifs : Pour quoi ?
• Activités et tâches : Quoi ?
• Actions : Comment ?
• Évaluations : Conséquences ?
22. Acteurs
// Plusieurs acteurs vont collaborer
Actors actors = new Actors(config);
// Un acteur initialement anonyme, avec un rôle de visiteur
User anonymous = actors.visitor();
// Les systèmes externes aussi sont des acteurs importants
RemoteApplication api = actors.remoteApplication();
23. Objectifs
// Les objectifs des utilisateurs s’expriment dans les noms des
// classes de test et des scénarios de test
public class JoiningTheCommunityTest {
@Test
public void joinsToLearnMoreBySelectingAFreePlan() {
…
}
@Test
public void joinsToGetPremiumFeaturesBySelectingAPayingPlan() {
…
}
}
24. Activités et tâches
// Les tâches sont groupées en activités auxquelles
// les acteurs participent
public class Join {
public Join signUp() { … }
public Join as(AccountDetails details) {
screen.enterEmail(details.email)
.enterPassword(details.password)
.acceptConditions()
.signUp();
}
public User chooseFreePlan() { … }
public Join selectPayingPlan(String name) { … }
…
}
25. Actions
// Les acteurs interagissent avec des éléments de l’interface
// utilisateur pour accomplir leurs tâches
public class SignUpScreen {
public SignUpScreen enterEmail(String email) {
browser.element(
id("sign-up")).element(id("email")).type(email);
return this;
}
public SignUpScreen enterPassword(String password) {
browser.element(
id("sign-up")).element(id("password")).type(password);
return this;
}
…
}
26. Évaluations
// Les interactions ont des conséquences que les acteurs
// vont évaluer en posant des questions
public class BillingScreen {
public BillingScreen showsCurrentPlan(String planName) {
browser.element(By.id("plan"))
.hasText(containsStringIgnoringCase(planName));
return this;
}
public BillingScreen showsCurrentCardDetails(String description,
String validity) {
browser.element(By.id("payment"))
.hasText(containsStringIgnoringCase(description));
browser.element(By.id("payment"))
.hasText(containsStringIgnoringCase(validity));
return this;
}
}
27. Au final
@Test
public void joinsToGetPremiumFeaturesBySelectingAPayingPlan() {
Join registration = anonymous.signUp().as(bob());
User bob = registration.selectPayingPlan("micro")
.enterBillingDetails("5555555555554444",
"12/18",
"999");
bob.manageAccount()
.showsCurrentlyOnPlan("micro")
.seesCreditCardDetails("**** **** **** 4444", "12/18");
}
28. En incluant les acteurs externes
@Test
public void joinsAndSelectsAPayingPlan() throws Exception {
Join registration = anonymous.signUp().as(bob());
paymentGateway.hasCreatedCustomerAccount(bob().email), "free");
mailServer.hasSentWelcomeEmailTo(bob().email);
User bob = registration.selectPayingPlan("micro")
.enterBillingDetails("5555555555554444",
"12/18", "999");
paymentGateway.hasCreatedCreditCard(bob().email,
"5555555555554444",
"12/18","999")
.hasUpgradedCustomerPlan(bob().email, "micro");
bob.manageAccount()
.showsCurrentlyOnPlan("micro")
.seesCreditCardDetails("**** **** **** 4444", "12/18");
}
29. À vous de jouer !
• Acceptez la nature asynchrone du Web
• Écrivez des tests atomiques
• Testez les parcours utilisateurs
• Pensez comme des utilisateurs