MANTENERE UNA DISTRIBUZIONE DRUPAL
ATTRAVERSO TEST COVERAGE
PADDLE CASE STUDY
https://github.com/brummbar/drupal-day-2015
ABOUT ME
FRANCESCO SARDARA
Backend e frontend
Email:
francesco.sardara@kanooh.be
D.o: Sardara
PER CHI LAVORO
COSA È PADDLE
FLEMISH GOVERNMENT
Framework contract
Unico referente per sito web, hosting e supporto;
frontend che segue le specifiche di branding del governo
Fiammingo;
focus sulla gestione dei contenuti (revisioni, workflow);
usabilità del backend per utenti non esperti;
commenti, multilingua, contatti, quiz, rss, newsletter,
protected pages.
WORKFLOW DI LAVORO
Sprint di 2 settimane
↓
Soft release
↓
Hard release
Rilascio in produzione
ORGANIZZAZIONE DELLO SPRINT
Open/Reopened
Development
Testing
QA
UAT
ANATOMIA DI UN TICKET
User story / descrizione bug
UAT steps
Definition of done
DEFINITION OF DONE
Functionally complete
Automated tests cover UAT instructions
Automated Simpletest tests passing within the last 24h
Automated Selenium tests passing within the last 24h
Working upgrade path from the last release in place
...
AUTOMATED TESTS
Simpletest e Selenium
Shoov.io / Webdrivercss
Eseguiti su server tramite Jenkins CI
Esecuzioni giornaliere automatiche
Esecuzioni manuali per ticket
Esecuzione remota su Browserstack
SELENIUM
automatizzazione di test su applicazioni web
ampiamente supportato in svariati linguaggi
Selenium IDE e Webdriver
COSA TESTIAMO IN SELENIUM
funzionalità personalizzate
integrazioni tra moduli
funzionalità Javascript
regressioni
QUALCHE NUMERO
746 test
10966 asserzioni
tempo di esecuzione di 18 ore
richiedono 2/3 del tempo di sviluppo
PAGE OBJECT PATTERN
PUNTI CHIAVE
riduzione della duplicazione del codice
facilità nell’aggiornare la relativa classe se gli elementi
della pagina cambiano
possibilità di sfruttare appieno i meccanismi di ereditarietà
REGOLE
solo la classe deve contenere XPath / selettori CSS
relativi all’elemento
nessuna assertion deve essere fatta all’interno della
classe
PAGE OBJECT PATTERN IN
PADDLE
STRUTTURA CARTELLE
ANATOMIA DI UNA CLASSE PAGE OBJECT
Il metodo costruttore contiene sempre una referenza a
Webdriver e all'oggetto Selenium.
/**
* Construct a new GlossaryDefinitionTableRow.
*
* @param WebDriverTestCase $webdriver
* The Selenium web driver test case.
* @param PHPUnit_Extensions_Selenium2TestCase_Element $element
* The webdriver element of the definition table row.
*/
public function __construct(WebDriverTestCase $webdriver, $element)
{
parent::__construct($webdriver);
$this->element = $element;
}
Tutte le proprietà che rappresentano altri Page Object sono
gestite tramite magic get.
/**
* {@inheritdoc}
*/
public function __get($name)
{
switch ($name) {
case 'saveButton':
return $this->element->byXPath('.//input[contains(@id, "edit-save")]'
break;
case 'definition':
return new Text($this->webdriver, $this->element->byName('definition'
break;
case 'description':
return new Wysiwyg($this->webdriver, 'edit-field-glossary-description
break;
}
throw new FormFieldNotDefinedException($name);
}
PERCHÉ USARE __GET() ?
Legacy code
Separazione dalle proprietà
Linearità di lettura
$this->configurePage->go();
$this->configurePage->contextualToolbar->buttonAdd->click();
$modal->form->definition->fill($title);
$modal->form->description->setBodyText($description);
$modal->form->saveButton->click();
$modal->waitUntilClosed();
ESEMPIO DI TEST CASE
GlossaryTest.php
testGlossaryDefinitionHighlighting
RISULTATO DEL TEST
SOPRAVVIVERE AI TEST
Approcci e best practices
RANDOM FAILING TESTS
Per RFT si indicano quei test che senza nessun
cambiamento nel codice o nei dati di test(*), il loro risultato
non è costante.
Cause comuni:
differenze hardware
dati di test
IMPLICIT E EXPLICIT WAITS
Il tempo di attesa implicito rappresenta per quanto tempo
Webdriver deve interrogare il DOM in attesa di trovare un
elemento se esso non è disponibile immediatamente.
Il tempo di attesa esplicito rappresenta per quanto tempo
Webdriver deve aspettare una certa condizione prima di
proseguire con l'esecuzione.
IMPLICIT WAIT
// Add a new definition.
$this->configurePage->contextualToolbar->buttonAdd->click();
$modal = new GlossaryDefinitionModal($this);
$modal->form->definition->fill('The awesome definition');
EXPLICIT WAIT
// Add a new definition.
$this->configurePage->contextualToolbar->buttonAdd->click();
$modal = new GlossaryDefinitionModal($this);
$modal->waitUntilOpened();
$modal->form->definition->fill('The awesome definition');
public function waitUntilOpened()
{
$callable = new SerializableClosure(...);
$this->webdriver->waitUntil($callable, $this->timeout);
}
RANDOMIZZARE I DATI DI TEST
Pro
Evitare parzialmente "collisioni"
tra contenuti
Trovare casi limite
Simulare un vero utilizzo del
sistema
Contro
La randomizzazione rende il
test non completamente
prevedibile
L'APPROCCIO DI PADDLE
La generazione dei contenuti è randomizzata.
I contenuti vengono lasciati intatti a meno che la loro
presenza non complichi la realizzazione del test.
SOPRAVVIVERE AI TEST
(REPRISE)
Approcci in Paddle
CKEDITOR
/**
* Sets the body text.
*
* @param string $text
* The text to enter in the body.
*/
public function setBodyText($text)
{
$this->webdriver->byId('cke_' . $this->editorId);
$this->webdriver->execute(
array(
'script' => "CKEDITOR.instances['{$this->editorId}'].setData('"
'args' => array(),
)
);
}
AJAX CALLBACKS
// ...
$form['list_id'] = array(
'#type' => 'select',
'#title' => t('List'),
'#required' => TRUE,
'#ajax' => array(
'callback' => 'mailchimp_campaign_list_segment_callback',
),
);
// ...
/**
* Marks the element as if it is waiting for AJAX callbacks.
*
* @see KanoohPaddlePagesElementFormAutoCompletedText::markAsWaitingForAuto
*/
public function markAsWaitingForAjaxCallback(PHPUnit_Extensions_Selenium2TestCas
{
$this->webdriver->execute(
array(
'script' => "arguments[0].className += ' progress-disabled';",
'args' => array($element->toWebDriverObject()),
)
);
}
MASTERING THE STALE
// ...
$form = $image_pane_type->getForm();
$form->showCaption->check();
// Submit the form and reload the modal.
$form->submit();
// Test something else.
$form->captionTextArea->fill('Oooops');
// ...
EXPLOITING THE STALE
// Waits until the current page is loaded.
public function waitUntilPageIsLoaded()
{
$body = $this->bodyElement;
$webdriver = $this->webdriver;
$callable = new SerializableClosure(
function () use ($body, $webdriver) {
try {
$body->click();
} catch (PHPUnit_Extensions_Selenium2TestCase_WebDriverException
return true;
}
}
);
$this->webdriver->waitUntil($callable, $this->webdriver->getTimeout());
$this->webdriver->waitUntilElementIsDisplayed('//body');
}
IMPROVING SELENIUM
RESULTS
RIPETIZIONI MULTIPLE PER EVITARE L'INTRODUZIONE DI RFT
$ base_url=http://paddle.dev ./vendor/bin/phpunit --repeat=3 
tests/Kanooh/Paddle/App/ContactPerson/EnableDisableTest.php
RICONOSCERE LE RFT
$ ./vendor/bin/phpunit --repeat=2 --only-repeat-failed tests/
VELOCIZZARE L'ESECUZIONE
esecuzione in parallelo dei test
sfruttare drupal/drupal-driver per rimuovere interazioni
browser non necessarie
VISUAL REGRESSION
WEBDRIVERCSS E SHOOV.IO
WEBDRIVERCSS
Tool di automatizzazione per test di visual regression per
WebdriverIO.
SHOOV.IO
Tool di visual regression e live monitoring.
Wrapper per Webdrivercss
Headless Drupal per lo storage degli screenshot
Angular.js web-app per il confronto visuale
Integrazione con Github (login, commits, pull request)
Gratuito (?)
L'APPROCCIO DI PADDLE AL VR
Configuration
Single test
https://github.com/brummbar/visual-regression
Q&A
Per domande o suggerimenti, scrivetemi
francesco.sardara@kanooh.be
GRAZIE PER
L'ATTENZIONE.
Mantenere una distribuzione Drupal attraverso test coverage: Paddle case study

Mantenere una distribuzione Drupal attraverso test coverage: Paddle case study

  • 2.
    MANTENERE UNA DISTRIBUZIONEDRUPAL ATTRAVERSO TEST COVERAGE PADDLE CASE STUDY https://github.com/brummbar/drupal-day-2015
  • 3.
  • 4.
    FRANCESCO SARDARA Backend efrontend Email: francesco.sardara@kanooh.be D.o: Sardara
  • 5.
  • 6.
  • 8.
    FLEMISH GOVERNMENT Framework contract Unicoreferente per sito web, hosting e supporto; frontend che segue le specifiche di branding del governo Fiammingo; focus sulla gestione dei contenuti (revisioni, workflow); usabilità del backend per utenti non esperti; commenti, multilingua, contatti, quiz, rss, newsletter, protected pages.
  • 9.
  • 10.
    Sprint di 2settimane ↓ Soft release ↓ Hard release Rilascio in produzione
  • 11.
  • 12.
    ANATOMIA DI UNTICKET User story / descrizione bug UAT steps Definition of done
  • 13.
    DEFINITION OF DONE Functionallycomplete Automated tests cover UAT instructions Automated Simpletest tests passing within the last 24h Automated Selenium tests passing within the last 24h Working upgrade path from the last release in place ...
  • 14.
    AUTOMATED TESTS Simpletest eSelenium Shoov.io / Webdrivercss Eseguiti su server tramite Jenkins CI Esecuzioni giornaliere automatiche Esecuzioni manuali per ticket Esecuzione remota su Browserstack
  • 16.
    SELENIUM automatizzazione di testsu applicazioni web ampiamente supportato in svariati linguaggi Selenium IDE e Webdriver
  • 17.
    COSA TESTIAMO INSELENIUM funzionalità personalizzate integrazioni tra moduli funzionalità Javascript regressioni
  • 18.
    QUALCHE NUMERO 746 test 10966asserzioni tempo di esecuzione di 18 ore richiedono 2/3 del tempo di sviluppo
  • 19.
  • 20.
    PUNTI CHIAVE riduzione delladuplicazione del codice facilità nell’aggiornare la relativa classe se gli elementi della pagina cambiano possibilità di sfruttare appieno i meccanismi di ereditarietà
  • 21.
    REGOLE solo la classedeve contenere XPath / selettori CSS relativi all’elemento nessuna assertion deve essere fatta all’interno della classe
  • 22.
  • 23.
  • 25.
    ANATOMIA DI UNACLASSE PAGE OBJECT
  • 26.
    Il metodo costruttorecontiene sempre una referenza a Webdriver e all'oggetto Selenium. /** * Construct a new GlossaryDefinitionTableRow. * * @param WebDriverTestCase $webdriver * The Selenium web driver test case. * @param PHPUnit_Extensions_Selenium2TestCase_Element $element * The webdriver element of the definition table row. */ public function __construct(WebDriverTestCase $webdriver, $element) { parent::__construct($webdriver); $this->element = $element; }
  • 27.
    Tutte le proprietàche rappresentano altri Page Object sono gestite tramite magic get. /** * {@inheritdoc} */ public function __get($name) { switch ($name) { case 'saveButton': return $this->element->byXPath('.//input[contains(@id, "edit-save")]' break; case 'definition': return new Text($this->webdriver, $this->element->byName('definition' break; case 'description': return new Wysiwyg($this->webdriver, 'edit-field-glossary-description break; } throw new FormFieldNotDefinedException($name); }
  • 28.
    PERCHÉ USARE __GET()? Legacy code Separazione dalle proprietà Linearità di lettura $this->configurePage->go(); $this->configurePage->contextualToolbar->buttonAdd->click(); $modal->form->definition->fill($title); $modal->form->description->setBodyText($description); $modal->form->saveButton->click(); $modal->waitUntilClosed();
  • 29.
    ESEMPIO DI TESTCASE GlossaryTest.php testGlossaryDefinitionHighlighting
  • 30.
  • 32.
  • 33.
    RANDOM FAILING TESTS PerRFT si indicano quei test che senza nessun cambiamento nel codice o nei dati di test(*), il loro risultato non è costante. Cause comuni: differenze hardware dati di test
  • 34.
    IMPLICIT E EXPLICITWAITS Il tempo di attesa implicito rappresenta per quanto tempo Webdriver deve interrogare il DOM in attesa di trovare un elemento se esso non è disponibile immediatamente. Il tempo di attesa esplicito rappresenta per quanto tempo Webdriver deve aspettare una certa condizione prima di proseguire con l'esecuzione.
  • 35.
    IMPLICIT WAIT // Adda new definition. $this->configurePage->contextualToolbar->buttonAdd->click(); $modal = new GlossaryDefinitionModal($this); $modal->form->definition->fill('The awesome definition');
  • 36.
    EXPLICIT WAIT // Adda new definition. $this->configurePage->contextualToolbar->buttonAdd->click(); $modal = new GlossaryDefinitionModal($this); $modal->waitUntilOpened(); $modal->form->definition->fill('The awesome definition'); public function waitUntilOpened() { $callable = new SerializableClosure(...); $this->webdriver->waitUntil($callable, $this->timeout); }
  • 37.
    RANDOMIZZARE I DATIDI TEST Pro Evitare parzialmente "collisioni" tra contenuti Trovare casi limite Simulare un vero utilizzo del sistema Contro La randomizzazione rende il test non completamente prevedibile
  • 38.
    L'APPROCCIO DI PADDLE Lagenerazione dei contenuti è randomizzata. I contenuti vengono lasciati intatti a meno che la loro presenza non complichi la realizzazione del test.
  • 39.
  • 40.
    CKEDITOR /** * Sets thebody text. * * @param string $text * The text to enter in the body. */ public function setBodyText($text) { $this->webdriver->byId('cke_' . $this->editorId); $this->webdriver->execute( array( 'script' => "CKEDITOR.instances['{$this->editorId}'].setData('" 'args' => array(), ) ); }
  • 41.
    AJAX CALLBACKS // ... $form['list_id']= array( '#type' => 'select', '#title' => t('List'), '#required' => TRUE, '#ajax' => array( 'callback' => 'mailchimp_campaign_list_segment_callback', ), ); // ...
  • 42.
    /** * Marks theelement as if it is waiting for AJAX callbacks. * * @see KanoohPaddlePagesElementFormAutoCompletedText::markAsWaitingForAuto */ public function markAsWaitingForAjaxCallback(PHPUnit_Extensions_Selenium2TestCas { $this->webdriver->execute( array( 'script' => "arguments[0].className += ' progress-disabled';", 'args' => array($element->toWebDriverObject()), ) ); }
  • 43.
    MASTERING THE STALE //... $form = $image_pane_type->getForm(); $form->showCaption->check(); // Submit the form and reload the modal. $form->submit(); // Test something else. $form->captionTextArea->fill('Oooops'); // ...
  • 44.
    EXPLOITING THE STALE //Waits until the current page is loaded. public function waitUntilPageIsLoaded() { $body = $this->bodyElement; $webdriver = $this->webdriver; $callable = new SerializableClosure( function () use ($body, $webdriver) { try { $body->click(); } catch (PHPUnit_Extensions_Selenium2TestCase_WebDriverException return true; } } ); $this->webdriver->waitUntil($callable, $this->webdriver->getTimeout()); $this->webdriver->waitUntilElementIsDisplayed('//body'); }
  • 45.
  • 46.
    RIPETIZIONI MULTIPLE PEREVITARE L'INTRODUZIONE DI RFT $ base_url=http://paddle.dev ./vendor/bin/phpunit --repeat=3 tests/Kanooh/Paddle/App/ContactPerson/EnableDisableTest.php
  • 47.
    RICONOSCERE LE RFT $./vendor/bin/phpunit --repeat=2 --only-repeat-failed tests/
  • 48.
    VELOCIZZARE L'ESECUZIONE esecuzione inparallelo dei test sfruttare drupal/drupal-driver per rimuovere interazioni browser non necessarie
  • 49.
  • 50.
    WEBDRIVERCSS Tool di automatizzazioneper test di visual regression per WebdriverIO.
  • 51.
    SHOOV.IO Tool di visualregression e live monitoring. Wrapper per Webdrivercss Headless Drupal per lo storage degli screenshot Angular.js web-app per il confronto visuale Integrazione con Github (login, commits, pull request) Gratuito (?)
  • 52.
    L'APPROCCIO DI PADDLEAL VR Configuration Single test https://github.com/brummbar/visual-regression
  • 53.
  • 54.
    Per domande osuggerimenti, scrivetemi francesco.sardara@kanooh.be
  • 55.