Upgrade to Pro — share decks privately, control downloads, hide ads and more …

We Are All 
Testers Now: 
The Testing Pyramid and Front-End Development

vjwilson
October 23, 2017

We Are All 
Testers Now: 
The Testing Pyramid and Front-End Development

Doing Test-Driven Development (TDD) with Vanilla JavaScript. Presented at the All Things Open conference in Raleigh, NC, on Oct. 23, 2017.

vjwilson

October 23, 2017
Tweet

More Decks by vjwilson

Other Decks in Programming

Transcript

  1. We Are All 
 Testers Now: 
 The Testing Pyramid

    and Front-End Development Van Wilson @vanwilson
  2. “A good presentation follows a story…” — John Papa, The

    Art of Public Speaking and Effective Presentations
  3. Things that Increase Code Quality Hiring competent employees Providing employee

    training Empowering employees to make decisions Code reviews Continuous Integration (short feedback loops) Test-Driven Development
  4. If you’ve looked at testing before… Most material about developers

    writing tests falls into one of two categories. “Here’s how to test a function that add two numbers. Now you can test anything. Good luck.” “You really should not be mocking your flux capacitor, but instead link the flibitz to the your cross-channel bit splicer.”
  5. Definition of Test-Driven Development Write a test for the next

    bit of functionality you want to add. Write the functional code until the test passes. Refactor both new and old code to make it well structured. - Martin Fowler, “TestDrivenDevelopment”, 
 https://martinfowler.com/bliki/TestDrivenDevelopment.html
  6. 1. Write Tests 2. Run Test (TEST FAILS) 3. Write

    Code to Make the Test to Pass 4. Run Test (TEST PASS) 5. Refactor 
 (MAKE CODE CLEANER AND FASTER) 6. Repeat FAIL PASS REFACTOR
  7. Benefits of TDD 1. Writing tests first really helps you

    avoid a lot of bad designs. 
 It makes you think about the code you need to write, BEFORE you write it. 2. Once you have tests, they help you avoid introducing subtle bugs when you have to change the code later. 
 Existing tests help prevent regressions in your code, where adding or changing one thing accidentally breaks another thing.
  8. But if these two things are true, why doesn’t every

    developer write tests first, all the time?
  9. Exercising Gives you more energy and other immediate health benefits.

    Helps you live longer, and with better quality of life, down the road.
  10. Saving money Gives you a better credit rating, and an

    emergency reserve, in the short term. Allows you to retire earlier, and do more things in retirement, in the future.
  11. Let’s assume writing tests first has those benefits… … how

    do you get started, and how do you keep going?
  12. Getting started with any testing really is the hardest part…

    and JavaScript testing is no exception.
  13. 1. Management mandates Test-Driven Development 2. Developers TDD all the

    new things (YEAH! TESTS!) 3. Velocity slows because TDD does take more time up-front 4. “We’re not going to meet this deadline ?!?” 
 (STOP DOING TDD) 5. As tests age, skip them or ignore failing tests
 (PEOPLE IGNORE TESTS) Start TDD Deadlines ?!? Graveyard of
 Tests
  14. Can’t we just tests everything with functional tests? With end-to-end

    tests, you have to wait: first for the entire product to be built, then for it to be deployed, and finally for all end-to-end tests to run. When the tests do run, flaky tests tend to be a fact of life. And even if a test finds a bug, that bug could be anywhere in the product. Although end-to-end tests do a better job of simulating real user scenarios, this advantage quickly becomes outweighed by all the disadvantages of the end-to-end feedback loop. — https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html Unit vs. Functional Tests
  15. Anatomy of Any Test Given - setup the environment When

    - act on the System Under Test (SUT) in that environment Then - test what happened
  16. TodoMVC example code (function (window) { 'use strict'; function Model(storage)

    { this.storage = storage; } Model.prototype.create = function (title, callback) { … window.app = window.app || {}; window.app.Model = Model; })(window);
  17. 3 Parts of Every Test Arrange
 “Given a number 10,

    and another number 2” Act
 “When I raise the first number to the power of the second number, 102 Assert
 “I get 100 as an answer”
  18. Testing a pure function it('should return a singular string when

    passed count of 1', function() { // arrange // set some variable // act // call some function // assert // }); Given a certain value, it always returns the same value
  19. Testing a pure function it('should return a singular string when

    passed count of 1', function() { // arrange var numActiveTodos = 1; var template = new Template(); // … }); An example from our Template module, part 1
  20. Testing a pure function it('should return a singular string when

    passed count of 1', function() { // … // act var counterView = template.itemCounter(numActiveTodos); // … }); An example from our Template module, part 2
  21. Testing a pure function it('should return a singular string when

    passed count of 1', function() { // … // assert expect(counterView).toBe('<strong>1</ strong> item left'); }); An example from our Template module, part 3
  22. Testing a pure function it('should return a plural string when

    passed count of more than 1', function() { var numActiveTodos = 2; var template = new Template(); var counterView = template.itemCounter(numActiveTodos); expect(counterView).toBe('<strong>2</ strong> items left'); }); Testing the other path through the code
  23. Testing a pure function it('should return the correct grammar when

    passed count of 0', function() { var numActiveTodos = 0; var template = new Template(); var counterView = template.itemCounter(numActiveTodos); expect(counterView).toBe('<strong>0</ strong> items left'); }); Testing an edge case
  24. The building blocks of tests test functions: 
 test(), or

    it() test suites: 
 describe() if you need to run the same setup for multiple tests: 
 beforeEach()
  25. Jest matchers Based on the popular Expect style of test

    matchers • .toBeTruthy() • .toBeFalsy() • .toBeGreaterThan(number) • .toBeGreaterThanOrEqual(number) • .toBeLessThan(number) • .toBeLessThanOrEqual(number) • .toContain(item) • .toEqual(value)
 • .toHaveLength(number) • .toMatch(regexpOrString) • .toMatchObject(object) • .toHaveProperty(keyPath, value) • .toHaveBeenCalled() • .toHaveBeenCalledTimes(number) • .toHaveBeenCalledWith(arg1, …) • … and many more
  26. Testing side effects it('should instantiate a store', function() { //

    arrange // set some variable // act // call some function // assert ? }); Function interacts with API or the DOM
  27. Peek at the global it('should make a store in localStorage',

    function() { var storageName = 'shaggy'; var storage = new Store(storageName); var dataStore = JSON.parse(localStorage.getItem(stora geName)); expect(typeof dataStore).toEqual('object'); });
  28. If you don’t have a return value, you’ve got to

    find something else to test Here’s where having a test-driven mindset encourages you to have a more modular architecture. Callbacks/promises Mocking: jest.fn()
  29. Testing a callback it('should call the given callback when instantiated',

    function() { var storageName = 'scooby'; var callback = jest.fn(); var storage = new Store(storageName, callback); var dataStoreShape = { todos: [] }; expect(callback).toHaveBeenCalledWith(
 dataStoreShape); });
  30. Testing a function with dependencies it('should get counts by item

    status', function() { var exampleTodos = [{…}, {…}, {…}]; var mockStore = { findAll: jest.fn().mockImplementation(
 function(callback) { callback.call(null, exampleTodos); }) }; … }); Option 1, Using a mock
  31. Testing a function with dependencies it('should get counts by item

    status', function() { … var model = new Model(mockStore); var getCountCallback = jest.fn(); model.getCount(getCountCallback); var expectedResult = { active: 2, completed: 1, total: 3 }; expect(getCountCallback).toHaveBeenCall edWith(expectedResult); }); Option 1, Using a mock (continued)
  32. Testing a function with dependencies it('should get counts by item

    status', function() { var Store = require('../Store/Store.js'); var exampleTodos = [{…}, {…}, {…}]; var realStore = new Store(‘yamato'); exampleTodos.forEach(function(todo) { realStore.save(todo) }); … }); Option 2, Just use the dependency
  33. Testing a function with dependencies it('should get counts by item

    status', function() { … var model = new Model(realStore); var getCountCallback = jest.fn(); model.getCount(getCountCallback); var expectedResult = { active: 2, completed: 1, total: 3 }; expect(getCountCallback).toHaveBeenCall edWith(expectedResult); }); Option 2, Just use the dependency (continued)
  34. # add libraries npm install --save-dev chromedriver cucumber selenium-webdriver #

    add support file touch features/support/world.js # add hooks file touch features/step_definitions/hooks.js # add feature file touch features/documentation.feature # add step definitions touch features/step_definitions/browser_steps.js Setting up Cucumber JS https://github.com/cucumber/cucumber-js
  35. Example of functional testing todo_input.feature Feature: Todo input feature As

    a user of Todos with TDD I want to be able to enter a new todo item So that I can add to my list of things to do Scenario: See the todo input Given I am on the Todos with TDD page When I look for an element with class "new-todo" Then I should see a placeholder of "What needs to be done?" Scenario: Enter a new todo Given I am on the Todos with TDD page When I look for an element with class "new-todo" And I enter text in the input field Then I should see my new item in the Todo list
  36. Testing the Cucumber.js site var seleniumWebdriver = require('selenium-webdriver'); var {defineSupportCode}

    = require('cucumber'); var assert = require('assert'); defineSupportCode(function({Given, When, Then}) { var todoInput; Given('I am on the Todos with TDD page', function() { return this.driver.get('http://localhost:8080'); }); … todo_input_steps.js (part 1)
  37. Testing the Cucumber.js site … When('I look for an element

    with class {string}', function (className) { return this.driver.findElement({ className: className }).then(function(element) { todoInput = element; return element; }); }); Then('I should see a placeholder of {string}', function (expectedPlaceholder) { return 
 todoInput.getAttribute('placeholder').then(
 function(placeholder) { assert.equal(placeholder, expectedPlaceholder); }); }); }); todo_input_steps.js (part 2)
  38. Is it possible to apply test-driven development principles with snapshot

    testing?
 Although it is possible to write snapshot files manually, that is usually not approachable. Snapshots help figuring out whether the output of the modules covered by tests is changed, rather than giving guidance to design the code in the first place. — https://facebook.github.io/jest/docs/snapshot-testing.html A Note about Snapshot Testing
  39. More about functional testing with JS “Cucumber.js”, https://github.com/cucumber/ cucumber-js “End

    to End testing of React apps with Nightwatch”,
 https://blog.syncano.io/testing-syncano/ “Intern”, https://github.com/theintern/intern (a new framework for managing unit and functional tests, thanks to Jaydeep Parmar, @jaydeep98a, for this reference)