Slide 1

Slide 1 text

The Landscape of Front-End Testing Render Conf 2016 Alicia Sedlock | @aliciability | #frontendtesting

Slide 2

Slide 2 text

I’m not here to prescribe. #frontendtesting

Slide 3

Slide 3 text

#frontendtesting The Beginning • Origins on the server • Here comes the client-side! • Opportunity and (sometimes neglected) responsibility

Slide 4

Slide 4 text

A focus on outcome, not the input. #frontendtesting

Slide 5

Slide 5 text

Automation is your friend! #frontendtesting

Slide 6

Slide 6 text

#frontendtesting Unit tests are for ensuring small pieces of code work as expected.

Slide 7

Slide 7 text

#frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”, function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });

Slide 8

Slide 8 text

#frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”, function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });

Slide 9

Slide 9 text

#frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”, function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });

Slide 10

Slide 10 text

#frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”, function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });

Slide 11

Slide 11 text

#frontendtesting describe(“Calculator Operations”, function () { beforeEach(function () { Calculator.init(); }); ... afterEach(function () { Calculator.teardown(); }); });

Slide 12

Slide 12 text

#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); });

Slide 13

Slide 13 text

#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); });

Slide 14

Slide 14 text

#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); });

Slide 15

Slide 15 text

#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); });

Slide 16

Slide 16 text

#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); });

Slide 17

Slide 17 text

#frontendtesting Recap! • Test tiny pieces of code • Validations, calculations, etc. • Pairs well with functional requirements

Slide 18

Slide 18 text

#frontendtesting Some big players • Jasmine • Mocha • Chai • Webdriver.io • QUnit • Unit.js • Sinon

Slide 19

Slide 19 text

#frontendtesting Acceptance tests check that all the small pieces play well together in the bigger picture.

Slide 20

Slide 20 text

#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(); }); }); });

Slide 21

Slide 21 text

#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(); }); }); });

Slide 22

Slide 22 text

#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(); }); }); });

Slide 23

Slide 23 text

#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(); }); }); });

Slide 24

Slide 24 text

#frontendtesting Another recap! • Do units and other pieces play well together? • Line up well with user stories

Slide 25

Slide 25 text

#frontendtesting More tools! • jasmine-integration • Karma • Selenium • Nightwatch • Again, many others

Slide 26

Slide 26 text

#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

Slide 27

Slide 27 text

#frontendtesting Visual regression tests check for inconsistencies in the view.

Slide 28

Slide 28 text

#frontendtesting Before After Difference

Slide 29

Slide 29 text

#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: “[email protected]” }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });

Slide 30

Slide 30 text

#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: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });

Slide 31

Slide 31 text

#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: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });

Slide 32

Slide 32 text

#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: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });

Slide 33

Slide 33 text

#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: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });

Slide 34

Slide 34 text

#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: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });

Slide 35

Slide 35 text

#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: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });

Slide 36

Slide 36 text

#frontendtesting Visual regression tests full pages vs. components

Slide 37

Slide 37 text

#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”); });

Slide 38

Slide 38 text

#frontendtesting Visual regression tests and that responsive design thing…

Slide 39

Slide 39 text

#frontendtesting phantomcss: { options: { mismatchTolerance: 0.05, screenshots: ‘tests/visual/baselines’, results: ‘tests/visual/results’, viewportSize: [1280, 800] }, src: [‘tests/visual/tests.js’] }

Slide 40

Slide 40 text

#frontendtesting phantomcss: { big_size: { options: { ... viewportSize: [1280, 800] } }, small_size: { options: { ... viewportSize: [480, 320] } } }

Slide 41

Slide 41 text

#frontendtesting Recap! • Fairly young concept, but is growing quickly • Test components, not full pages • Great for responsive testing

Slide 42

Slide 42 text

#frontendtesting Visual regression tools • PhantomCSS/PhantomFlow • BackstopJS • Wraith • Webdriver.io • Percy.io

Slide 43

Slide 43 text

#frontendtesting Accessibility tests compare your site against accessibility standards

Slide 44

Slide 44 text

#frontendtesting

Slide 45

Slide 45 text

#frontendtesting Tools I’ve heard of • a11y • use grunt-a11y for build tool automation • Pa11y • …that’s it for now?

Slide 46

Slide 46 text

#frontendtesting Performance tests keep your project honest about performance

Slide 47

Slide 47 text

#frontendtesting perfbudget: { default: { options: { url: [your site’s URL], budget: { visualComplete: '3000', SpeedIndex: ‘1500’, render: ‘500’, fullyLoaded: ‘3000’, requests: ’25’, bytesInDoc: '100000' } } } }

Slide 48

Slide 48 text

#frontendtesting Tools for performance • grunt-perfbudget • gulp size • perf.js

Slide 49

Slide 49 text

So, you’re saying I should use all these tools, right? #frontendtesting

Slide 50

Slide 50 text

#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?

Slide 51

Slide 51 text

Yes, you can go overboard with tests. #frontendtesting

Slide 52

Slide 52 text

Yes, writing good tests takes practice. #frontendtesting

Slide 53

Slide 53 text

The Excuse Corner

Slide 54

Slide 54 text

This is more code to write. #frontendtesting

Slide 55

Slide 55 text

This is more code to write, and my code will still have bugs. #frontendtesting

Slide 56

Slide 56 text

I’m working with legacy code, and it’ll be impossible to test old features. #frontendtesting

Slide 57

Slide 57 text

#frontendtesting Build A Test-Conscience Team through education, experimentation, and practice

Slide 58

Slide 58 text

Thanks for listening!