Automatiser les tests d’acceptation :
Comment s’y prendre ?
Vincent Tencé @testinfected
http://vtence.com http://github.com/testinfected
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
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
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 testassertThat(response).hasStatusCode(303);
}
Waits
WebDriver driver = new FirefoxDriver(); driver.get(“http://some_domain/url_that_delays_login”);
WebDriverWait wait = new WebDriverWait(driver, 2, 50);
WebElement display = wait.until(presenceOfElementLocated( By.id("some-dynamic-element"))); assertThat("display text", display.getText(), equalTo("Loaded"))
WebElement button = wait.until(elementToBeClickable( By.id(“some-button")));
button.click();
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
Manque d’abstraction
DesiredCapabilities capabilities = DesiredCapabilities.firefox();WebDriver driver = new FirefoxDriver(capabilities);// Enter username and passworddriver.findElement(By.id("username")).sendKeys("Bob");driver.findElement(By.id("password")).sendKeys("secret");// Click login buttondriver.findElement(By.id("login")).submit();// Wait for home page to loadWebDriverWait 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!"));
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!"
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!"));
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!"
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
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
Ne cherchez pas à être exhaustif
@Testpublic 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");}
Pensez comme des utilisateurs
• Rôles : Qui ?
• Objectifs : Pour quoi ?
• Activités et tâches : Quoi ?
• Actions : Comment ?
• Évaluations : Conséquences ?
Acteurs
// Plusieurs acteurs vont collaborer Actors actors = new Actors(config);
// Un acteur initialement anonyme, avec un rôle de visiteurUser anonymous = actors.visitor();
// Les systèmes externes aussi sont des acteurs importantsRemoteApplication api = actors.remoteApplication();
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() { … } }
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) { … } … }
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;
}
… }
É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; }}
Au final
@Testpublic 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");}
En incluant les acteurs externes
@Testpublic 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");}
À vous de jouer !
• Acceptez la nature asynchrone du Web
• Écrivez des tests atomiques
• Testez les parcours utilisateurs
• Pensez comme des utilisateurs