Front-end Automated Testing #drupal-fat
Ruben Teijeiro I don't know what I like more: Drupal or Beer @rteijeiro
Based on a true history...
Web Development
In collaboration with
Developers I'm ready for website development!
DevOps Almost finished setting up your server. Just one minute...
WTF!!
Designers Just redesigned the website. Now it's shinny, edgy and it pops!!
So, what?
Users In-place Content Authoring
Holy shit!!
Clients Just something And kitten like Facebook! pics. Everyone We need it loves kittens! yesterday... Better in Comic Sans Should work also in IE7
OMG!!
Browsers
Result
Front-end I said: {float: left;}!!
Solution
Refactoring Fixed Fixed Fixed Fixed Fixed Fixed Fixed
Oh man!
DEMO
BONUS!
And now I will show you how it looks like in Internet Explorer...
Now what?
FAT
Front-end Automated Testing
Because people like code that works
Continuous Integration
Push Button Receive Bacon
Unit Test
Take one tablet every git push
Automated Repeteable Easy to understand Incremental Easy to run Fast Unit Test
Testing Tools BA-K-47
Drupal 8 Modules
Drupal Modules TestSwarm https://drupal.org/project/testswarm FAT https://drupal.org/project/fat
Testing Frameworks
QUnit CasperJS PhantomJS Jasmine Testing Frameworks
TestSwarm module QUnit Tests FAT module
Bacon Module
bacon.module /** * Implements hook_testswarm_tests(). */ function bacon_testswarm_tests() { 'bacon_test' => array( 'module' => 'bacon', 'description' => 'Testing bacon.', 'js' => array( $path. '/tests/bacon.tests.js' => array(), ), 'dependencies' => array( array('testswarm', 'jquery.simulate'), ), 'path' => 'admin/bacon/test', 'permissions' => array('test bacon') ), }
tests/bacon.tests.js /*global Drupal: true, jquery: true, QUnit:true*/ (function ($, Drupal, window, document, undefined) { "use strict"; Drupal.tests.bacon = { getinfo: function() { return { name: 'Bacon test', description: 'Testing bacon.', group: 'Bacon' }; }, tests: function() { [Insert your QUnit tests here] }, }; })(jquery, Drupal, this, this.document);
Setup
tests/bacon.tests.js Drupal.tests.bacon = { getinfo: function() { return { name: 'Bacon test', description: 'Testing bacon.', group: 'Bacon' }; }, setup: function() { [Insert your setup code here] }, teardown: function() { [Insert your teardown code here] }, tests: function() { [Insert your QUnit tests here] }, };
QUnit
Assert
Assert ok(state, message) Passes if the first argument is truthy. var bbq_ready = true; QUnit.ok(bbq_ready, 'Barbecue ready!.'); var bbq_ready = false; QUnit.ok(bbq_ready, 'Barbecue ready!.');
Assert equal(actual, expected, message) Simple comparison operator (==) to compare the actual and expected arguments. var bbq = 'Bacon'; QUnit.equal(bbq, 'Bacon', 'Bacon barbecue!'); QUnit.equal(bbq, 'Chicken', 'Chicken barbecue!');
Assert notequal(actual, expected, message) Simple inverted comparison operator (!=) to compare the actual and expected arguments. var bbq = 'Bacon'; QUnit.notEqual(bbq, 'Salad', 'No salad!'); var bbq = 'Salad'; QUnit.notEqual(bbq, 'Salad', 'No salad!');
Assert deepequal(actual, expected, message) Just like equal() when comparing objects, such that { key: value } is equal to { key: value }. var bbq = {meat: 'Bacon'}; QUnit.deepEqual(bbq, {meat: 'Bacon'}, 'Bacon barbecue!'); var bbq = {meat: 'Chicken'}; QUnit.deepEqual(bbq, {meat: 'Bacon'}, 'Bacon barbecue!');
Assert notdeepequal(actual, expected, message) Just like notequal() when comparing objects, such that { key: value } is not equal to { key: value }. var bbq = {food: 'Bacon'}; QUnit.notDeepEqual(bbq, {food: 'Salad'}, 'No salad!'); var bbq = {food: 'Salad'}; QUnit.notDeepEqual(bbq, {food: 'Salad'}, 'No salad!');
Assert strictequal(actual, expected, message) Most rigid comparison of type and value with the strict equality operator (===). var bacon = '1'; QUnit.strictEqual(bacon, '1', 'Bacon!'); QUnit.strictEqual(bacon, 1, 'Bacon!');
Assert notstrictequal(actual, expected, message) Most rigid comparison of type and value with the strict inverted equality operator (!==). var bacon = '1'; QUnit.notStrictEqual(bacon, 1, 'No Bacon!'); QUnit.notStrictEqual(bacon, '1', 'No Bacon!');
Expect
Expect expect(amount) Specify how many assertions are expected to run within a test. If the number of assertions run does not match the expected count, the test will fail. var bbq = 'Bacon'; // Good QUnit.expect(1); QUnit.equal(bbq, 'Bacon', 'Bacon barbecue!'); // Wrong QUnit.expect(1); QUnit.equal(bbq, 'Bacon', 'Bacon barbecue!'); QUnit.notEqual(bbq, 'Chicken', 'Chicken barbecue!');
Synchronous Testing
Synchronous Testing // Number of assertions. QUnit.expect(3); var bbq_ready = true, bbq = 'Bacon'; // Assertions. QUnit.ok(bbq_ready, 'Barbacue is ready!'); QUnit.equal(bbq, 'Bacon', 'Bacon barbecue!'); QUnit.notEqual(bbq, 'Salad', 'No salad!');
Asynchronous Testing
Asynchronous Testing QUnit.expect(2); var bbq_ready = false, bbq = 'Bacon', time = 36000; // Miliseconds. QUnit.stop(); settimeout(function() { bbq_ready = true; QUnit.ok(bbq_ready, 'Barbacue is ready!'); QUnit.start(); }, time); QUnit.equal(bbq, 'Bacon', 'Bacon barbecue!');
Testing User Actions
Testing User Actions /** * Implements hook_testswarm_tests(). */ function bacon_testswarm_tests() { 'bacon_test' => array( 'module' => 'bacon', 'description' => 'Testing bacon.', 'js' => array( $path. '/tests/bacon.tests.js' => array(), ), 'dependencies' => array( array('testswarm', 'jquery.simulate'), ), 'path' => 'admin/bacon/test', 'permissions' => array('test bacon') ), }
Testing User Actions https://github.com/jquery/jquery-simulate QUnit.expect(1); var bbq_ready = false, bbq = 'Bacon'; bbq_ready.trigger('change'); QUnit.ok(bbq_ready, 'Barbecue ready!');
DEMO
Questions? rteijeiro@drewpull.com