The Landscape of Front-End Testing

The Landscape of Front-End Testing

Given @ Render Conf 2016
======================
You may have heard developers say that testing your code is a best practice. But what kinds of testing are they talking about? From unit and acceptance testings, to code linting and visual regression testing and more, it may be difficult to sort out which kind of testing is right for your team or project. In this talk, we will cover the breadth of testing frameworks available to front-end developers, and how to determine which ones will help your team produce a stable code base.

A5af583190a73a7d94255c6f79cde415?s=128

Alicia Sedlock

April 21, 2016
Tweet

Transcript

  1. 3.

    #frontendtesting The Beginning • Origins on the server • Here

    comes the client-side! • Opportunity and (sometimes neglected) responsibility
  2. 7.

    #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  3. 8.

    #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  4. 9.

    #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  5. 10.

    #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  6. 12.

    #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  7. 13.

    #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  8. 14.

    #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  9. 15.

    #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  10. 16.

    #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  11. 17.

    #frontendtesting Recap! • Test tiny pieces of code • Validations,

    calculations, etc. • Pairs well with functional requirements
  12. 18.

    #frontendtesting Some big players • Jasmine • Mocha • Chai

    • Webdriver.io • QUnit • Unit.js • Sinon
  13. 20.

    #frontendtesting describe(“Integration tests”, function () { var page; beforeEach(function (done)

    { page = visit(“/home”); page.ready(done); }); describe(“Sign Up Failure state”, function () { beforeEach(function (done) { page.fill_in(“input[name='email']", “Not An Email”); page.click(“button[type=submit]”); page.onBodyChange(done); }); it(“Shouldn’t allow signup with invalid information”, function () { expect(page.find(“#signupError”) .hasClass(“hidden”)).toBeFalsy(); }); }); });
  14. 21.

    #frontendtesting describe(“Integration tests”, function () { var page; beforeEach(function (done)

    { page = visit(“/home”); page.ready(done); }); describe(“Sign Up Failure State ”, function () { beforeEach(function (done) { page.fill_in(“input[name='email']", “Not An Email”); page.click(“button[type=submit]”); page.onBodyChange(done); }); it(“Shouldn’t allow signup with invalid information”, function () { expect(page.find(“#signupError”) .hasClass(“hidden”)).toBeFalsy(); }); }); });
  15. 22.

    #frontendtesting describe(“Integration tests”, function () { var page; beforeEach(function (done)

    { page = visit(“/home”); page.ready(done); }); describe(“Sign Up Failure state”, function () { beforeEach(function (done) { page.fill_in(“input[name='email']", “Not An Email”); page.click(“button[type=submit]”); page.onBodyChange(done); }); it(“Shouldn’t allow signup with invalid information”, function () { expect(page.find(“#signupError”) .hasClass(“hidden”)).toBeFalsy(); }); }); });
  16. 23.

    #frontendtesting describe(“Integration tests”, function () { var page; beforeEach(function (done)

    { page = visit(“/home”); page.ready(done); }); describe(“Sign Up Failure state”, function () { beforeEach(function (done) { page.fill_in(“input[name='email']", “Not An Email”); page.click(“button[type=submit]”); page.onBodyChange(done); }); it(“Shouldn’t allow signup with invalid information”, function () { expect(page.find(“#signupError”) .hasClass(“hidden”)).toBeFalsy(); }); }); });
  17. 24.

    #frontendtesting Another recap! • Do units and other pieces play

    well together? • Line up well with user stories
  18. 26.

    #frontendtesting The Evolution • Front-end development is more than just

    business logic • There are other parts of the code that can fail • Apply the tactic of testing expected outcomes
  19. 29.

    #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(“#signUpForm”, { name: “Alicia Sedlock”, email: “alicia@example.com” }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  20. 30.

    #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘alicia@example.com’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  21. 31.

    #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘alicia@example.com’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  22. 32.

    #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘alicia@example.com’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  23. 33.

    #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘alicia@example.com’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  24. 34.

    #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘alicia@example.com’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  25. 35.

    #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘alicia@example.com’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  26. 37.

    #frontendtesting casper.start(“/styleguide”).then(function(){ phantomcss .screenshot(“a.link”, “link example”); phantomcss .screenshot(“.btn-primary”, “primary button”);

    }).then(function() { this.mouse.move(“.btn-primary”'); phantomcss .screenshot(“.btn-primary”, “button hover”); });
  27. 39.

    #frontendtesting phantomcss: { options: { mismatchTolerance: 0.05, screenshots: ‘tests/visual/baselines’, results:

    ‘tests/visual/results’, viewportSize: [1280, 800] }, src: [‘tests/visual/tests.js’] }
  28. 40.

    #frontendtesting phantomcss: { big_size: { options: { ... viewportSize: [1280,

    800] } }, small_size: { options: { ... viewportSize: [480, 320] } } }
  29. 41.

    #frontendtesting Recap! • Fairly young concept, but is growing quickly

    • Test components, not full pages • Great for responsive testing
  30. 45.

    #frontendtesting Tools I’ve heard of • a11y • use grunt-a11y

    for build tool automation • Pa11y • …that’s it for now?
  31. 47.

    #frontendtesting perfbudget: { default: { options: { url: [your site’s

    URL], budget: { visualComplete: '3000', SpeedIndex: ‘1500’, render: ‘500’, fullyLoaded: ‘3000’, requests: ’25’, bytesInDoc: '100000' } } } }
  32. 50.

    #frontendtesting Assessment • How much business logic are we off-loading

    to the client-side? • What is our history with regressions? • Are we constantly fixing, and re-fixing, visual/UI issues? • In general, which piece of our stack is the most instable/Jenga-like?
  33. 55.

    This is more code to write, and my code will

    still have bugs. #frontendtesting
  34. 56.