Teste seu Javascript - Front in SM

Teste seu Javascript - Front in SM

8920246f63abbfdeb84bb1bfb2d2e4fd?s=128

Matheus Azzi

July 02, 2016
Tweet

Transcript

  1. Matheus Azzi Teste seu JAVASCRIPT

  2. Matheus Azzi www.matheusazzi.com matheusazzi fb.com/matheusazzi Codeminer 42 speakerdeck.com/matheusazzi matheus.azzi@codeminer42.com

  3. O QUE É UM TESTE? É uma verificação feita sobre

    algo para garantir um determinado comportamento
  4. Testes são perda de TEMPO/DINHEIRO?

  5. 10 historical software bugs with extreme consequences http://goo.gl/0zLmrt Testes são

    perda de TEMPO/DINHEIRO?
  6. Manutenção & Qualidade POR QUE TESTAR Front-end?

  7. Custo de Mudança Tempo de Desenvolvimento Tradicional Com Testes Custo

    de uma mudança ao longo do projeto Source: http://goo.gl/EgYGh9
  8. Podem reduzir a quantidade de bugs em 40% ~ 90%

  9. 1 Escreva um teste para um pedaço de funcionalidade O

    teste vai falhar, pois não há nada implementado ainda 2 Escreva o código para fazer o teste passar 3 Com o teste passando refatore seu código Repita esse processo continuamente WORKFLOW
  10. None
  11. São metodologias de desenvolvimento ágil. TDD E BDD No TDD

    (Test Driven Development) o desenvolvimento deve ser guiado por testes, onde um teste unitário deve ser escrito antes de uma funcionalidade do sistema. O objetivo, então, é fazer com que o teste passe com sucesso. No BDD (Behaviour Driven Development) o desenvolvimento deve ser guiado por comportamentos do sistema. Desta forma, um comportamento é priorizado em relação ao teste unitário, o que não exclui a execução do fluxo do TDD nesse processo.
  12. Não se apegue, são formas de se pensar em testes

    partindo de premissas ligeiramente diferentes mas chegando ao mesmo resultado prático. TDD E BDD http://goo.gl/kHA8q (
  13. Descobriu um Bug? Conserte o bug e escreva um teste

    para garantir que o erro não se repita Testar também pode evitar arrumar um erro em algum lugar e acabar causando outro
  14. Unitário Integração Aceitação Testa isoladamente uma responsabilidade do sistema Testa

    um "componente" e sua comunicação com o resto da aplicação Testa uma funcionalidade da aplicação Valida retornos, propriedades e execução de métodos Ajuda na qualidade do código Testa o componente em seu cenário e como se conecta com as outras responsabilidades Utiliza a interface da aplicação testando a funcionalidade Ajuda na verificação Tipos de Teste
  15. Testes unitários

  16. calc.spec.js alc', function() { // ./assets/scripts/calc.js function Calc() { }

    Calc.prototype.sum = function(n1, n2) { return n1 + n2; }; Jasmine
  17. // ./specs/calc.spec.js describe('Calc', function() { }); // ./assets/scripts/calc.js function Calc()

    { } Calc.prototype.sum = function(n1, n2) { return n1 + n2; }; Jasmine
  18. // ./specs/calc.spec.js describe('Calc', function() { var subject, result; beforeEach(function() {

    subject = new Calc(); }); }); // ./assets/scripts/calc.js function Calc() { } Calc.prototype.sum = function(n1, n2) { return n1 + n2; }; Jasmine
  19. // ./specs/calc.spec.js describe('Calc', function() { var subject, result; beforeEach(function() {

    subject = new Calc(); }); describe('#sum', function() { beforeEach(function() { result = subject.sum(3, 5); }); }); }); // ./assets/scripts/calc.js function Calc() { } Calc.prototype.sum = function(n1, n2) { return n1 + n2; }; Jasmine
  20. // ./specs/calc.spec.js describe('Calc', function() { var subject, result; beforeEach(function() {

    subject = new Calc(); }); describe('#sum', function() { beforeEach(function() { result = subject.sum(3, 5); }); it('sums two numbers', function() { expect(result).toEqual(8); }); // ... }); }); // ./assets/scripts/calc.js function Calc() { } Calc.prototype.sum = function(n1, n2) { return n1 + n2; }; Jasmine
  21. TEST RUNNER

  22. // package.json { "name": "teste-seu-javascript", "scripts": { "test": "./node_modules/karma/bin/karma start"

    }, ... } Karma Setup
  23. $ karma init Which testing framework do you want to

    use ? Press tab to list possible options. Enter to move to the next question. > jasmine Do you want to use Require.js ? This will add Require.js plugin. Press tab to list possible options. Enter to move to the next question. > no Do you want to capture any browsers automatically ? Press tab to list possible options. Enter empty string to move to the next question. > Chrome What is the location of your source and test files ? You can use glob patterns, eg. "js/*.js" or "test/**/ *Spec.js". Enter empty string to move to the next question. > app/assets/scripts/**/*.js > specs/**/*.spec.js Should any of the files included by the previous patterns be excluded ? You can use glob patterns, eg. "**/*.swp". Enter empty string to move to the next question. > Do you want Karma to watch all the files and run the tests on change ? Press tab to list possible options. > no Config file generated Karma Setup
  24. Karma Setup // karma.config.js module.exports = function(config) { config.set({ basePath:

    '', frameworks: ['jasmine'], files: [ 'app/assets/scripts/**/*.js', 'specs/**/*.spec.js' ], exclude: [ 'node_modules' ], preprocessors: {}, reporters: ['progress'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: false, browsers: ['Chrome'], singleRun: false }) };
  25. Karma Setup

  26. Matchers // Jasmine .toBe() .toContain() .toBeDefined() .toBeFalsy() .toBeTruthy() .toEqual() .toHaveBeenCalled()

    .toThrow() {...}
  27. // Jasmine .toBe() .toContain() .toBeDefined() .toBeFalsy() .toBeTruthy() .toEqual() .toHaveBeenCalled() .toThrow()

    {...} // jasmine-jquery .toHaveClass() .toBeChecked() .toBeDisabled() .toBeEmpty() .toContainElement() .toHaveCss() .toBeHidden() .toBeFocused() {...} Matchers
  28. // Jasmine .toBe() .toContain() .toBeDefined() .toBeFalsy() .toBeTruthy() .toEqual() .toHaveBeenCalled() .toThrow()

    {...} // jasmine-jquery .toHaveClass() .toBeChecked() .toBeDisabled() .toBeEmpty() .toContainElement() .toHaveCss() .toBeHidden() .toBeFocused() {...} // jasmine-matchers .toHaveMethod() .toBeEmptyArray() .toBeAfter(date) .toBeArray() .toBeBoolean() .toHaveMember() {...} Matchers
  29. Testes unitários são executados isoladamente, portando utilizamos artifícios para simular

    todas as dependências que o teste precisar.
  30. Testar o DOM é difícil, mas a maior parte do

    Javascript no Front-end se refere a UI. Porém, ficar injetando templates nos seus testes pode ser uma má prática. Fixture
  31. Testar o DOM é difícil, mas a maior parte do

    Javascript no Front-end se refere a UI. Porém, ficar injetando templates nos seus testes é uma má prática. beforeEach(function() { $('<div id="foo">' + '<div class="bar baz">' + '<form>' + '<input type="text" name="name" value="Azzi">' + '</form>' + '</div>' + '</div>').appendTo('body'); }); afterEach(function() { $('#foo').remove(); }); var content = require('./app/views/home'); var _ = require('underscore'); beforeEach(function() { $('body').append( _.template(content) ); }); afterEach(function() { $('body').empty(); }); ou Fixture
  32. Testar o DOM é difícil, mas a maior parte do

    Javascript no Front-end se refere a UI. Porém, ficar injetando templates nos seus testes é uma má prática. beforeEach(function() { $('<div id="foo">' + '<div class="bar baz">' + '<form>' + '<input type="text" name="name" value="Azzi">' + '</form>' + '</div>' + '</div>').appendTo('body'); }); afterEach(function() { $('#foo').remove(); }); var content = require('./app/views/home'); var _ = require('underscore'); beforeEach(function() { $('body').append( _.template(content) ); }); afterEach(function() { $('body').empty(); }); ou beforeEach(function() { affix('#foo .bar.baz form input[name="name"][value="Azzi"]'); }); <div id="foo"> <div class="bar baz"> <form> <input type="text" name="name" value="Azzi"> </form> </div> </div> // jasmine-fixture Fixture
  33. Pode interceptar chamadas de função: • Verifica se a função

    foi chamada • Verifica os parâmetros no qual foi executada Espia as funções do seu código Spy
  34. Pode interceptar chamadas de função: • Verifica se a função

    foi chamada • Verifica os parâmetros no qual foi executada Espia as funções do seu código function MyComponent($element) { this.$element = $element; this.addListeners(); }; MyComponent.prototype.addListeners = function() { this.$element.on('click', this.doSomething.bind(this)); }; MyComponent.prototype.doSomething = function() { {...} }; Spy
  35. function MyComponent($element) { this.$element = $element; this.addListeners(); }; MyComponent.prototype.addListeners =

    function() { this.$element.on('click', this.doSomething.bind(this)); }; MyComponent.prototype.doSomething = function() { {...} }; describe('MyComponent', function() { var $element, subject, result; beforeEach(function() { $element = affix('button'); subject = new MyComponent($element); }); describe('when click happens', function() { beforeEach(function() { result = spyOn(subject, 'doSomething'); subject.addListeners(); subject.$element.trigger('click'); }); it('call #doSomething', function() { expect(result).toHaveBeenCalled(); }); }); }); Spy
  36. Stub É um objeto com um comportamento pré-programado Pode ser

    configurado para: • Executar a função original • Retornar um resultado pré-definido • Chamar uma função fake • Lançar um erro
  37. É um objeto com um comportamento pré-programado Pode ser configurado

    para: • Executar a função original • Retornar um resultado pré-definido • Chamar uma função fake • Lançar um erro Stub var Foo = { bar: function() { return 'Real function'; } }; spyOn(Foo, 'bar'); Foo.bar(); // spyOn(Foo, 'bar').and.callThrough(); Foo.bar(); // "Real function" spyOn(Foo, 'bar').and.returnValue('Abc'); Foo.bar(); // "Abc" spyOn(Foo, 'bar').and.callFake(function() { return 'Replacing real fn'; }); Foo.bar(); // "Replacing real fn"
  38. É um objeto com um comportamento pré-programado Pode ser configurado

    para: • Executar a função original • Retornar um resultado pré-definido • Chamar uma função fake • Lançar um erro Stub describe('Sample', function() { var myCallback; beforeEach(function() { myCallback = jasmine.createSpy(); Sample(myCallback); }); it('exec callback argument', function() { expect(myCallback).toHaveBeenCalled(); }); it('exec callback passing 123 argument', function() { expect(myCallback).toHaveBeenCalledWith(123); }); it('exec callback once', function() { expect(myCallback.calls.count()).toEqual(1); }); }); function Sample(callback) { callback(123); }
  39. Timers describe('Sample', function() { var result; beforeEach(function() { result =

    jasmine.createSpy(); jasmine.clock().install(); Sample(result); }); afterEach(function() { jasmine.clock().uninstall(); }); it('exec callback after 2 seconds', function() { expect(result).not.toHaveBeenCalled(); jasmine.clock().tick(2001); expect(result).toHaveBeenCalled(); }); }); function Sample(callback) { setTimeout(callback, 2000); }
  40. Focused Specs // fdescribe // fit describe('Rodar 1 único teste',

    function() { fit('esse teste é focado e vai rodar', function() { expect(true).toBeTruthy(); }); it('esse teste não vai rodar', function(){ expect(true).toBeFalsy(); }); });
  41. Código Testável Organize seu código - Apresentação e interação -

    Gerenciamento de dados e persistência - Estados gerais do componente - Configuração e inicialização
  42. Código Testável Qualquer componente deve ser como uma API

  43. Código Testável Tente evitar: - Componentes mal estruturados - Métodos

    grandes e complexos - Métodos muito acoplados - Funções anônimas - Estados não visíveis
  44. function Component() { var isLoading = true; } function Component()

    { this.isLoading = true; } $('.foo').on('click', function() { $(this).append('<h1>Hello</h1>'); }); function showHello() { $(this).append('<h1>Hello</h1>'); } $('.foo').on('click', showHello); init: function() { this.$elem = $('.elem'); } init: function(options) { this.$elem = options.$elem || $('.elem'); } this.$element.fadeIn(300); this.$element.slideUp(300); this.$element.addClass('is-visible'); this.$element.addClass('is-collapsed');
  45. Testes de Aceitação ZombieJS CasperJS Nightwatch.js Capybara Cucumber Codeception Behat

    Splinter Behave
  46. var Browser = require('zombie'); Browser.localhost('example.com', 3000); describe('User visits home page',

    function() { var browser = new Browser(); before(function(done) { browser.visit('/', done); }); it('renders with login area', function() { browser.assert.success(); browser.assert.element('form[action="/login"]'); }); // ... }); ZombieJS
  47. ZombieJS // ... describe('tries to login', function() { describe('providing correct

    info', function() { before(function(done) { browser .fill('email', 'matheuslazzi@gmail.com') .fill('password', '123123') .pressButton('Login', done); }); it('See a welcome page', function() { browser.assert.text('h1', 'Bem-vindo, Matheus'); }); }); describe('providing incorrect info', function() { before(function(done) { browser .fill('email', 'foo@bar.com') .fill('password', 'foobar') .pressButton('Login', done); }); it('show input errors', function() { browser.assert.className('input[name=email]', 'has-error'); browser.assert.className('input[name=password]', 'has-error'); }); it('doesn\'t login', function() { browser.assert.text('.errors', 'Dados inválidos'); }); }); });
  48. Code Coverage with Istanbul

  49. Gremlins

  50. Integração Contínua

  51. Integração Contínua

  52. Sem Testes Pull Request

  53. I get paid for code that works, not for tests,

    so my philosophy is to test as little as possible to reach a given level of confidence [...] Kent Beck on StackOverflow Até onde devo testar?