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

Teste seu Javascript

Matheus Azzi
December 06, 2015

Teste seu Javascript

Matheus Azzi

December 06, 2015
Tweet

More Decks by Matheus Azzi

Other Decks in Programming

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. 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
  9. None
  10. 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.
  11. 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 (
  12. 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
  13. 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
  14. Testes unitários

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

    Calc.prototype.sum = function(n1, n2) { return n1 + n2; }; Jasmine
  16. // ./specs/calc.spec.js describe('Calc', 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() { var subject, result; beforeEach(function() {

    subject = new Calc(); }); }); // ./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(); }); 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
  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); }); 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
  20. TEST RUNNER

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

    }, ... } Karma Setup
  22. $ 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
  23. 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 }) };
  24. Karma Setup

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

    .toThrow() {...}
  26. // 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
  27. Testes unitários são executados isoladamente, portando utilizamos artifícios para simular

    todas as dependências que o teste precisar.
  28. 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
  29. 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
  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 é 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
  31. 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
  32. 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; {...} }; MyComponent.prototype.addListeners = function() { this.$element.on('click', this.doSomething.bind(this)); }; MyComponent.prototype.doSomething = function() { return '123'; }; Spy
  33. function MyComponent($element) { this.$element = $element; {...} }; MyComponent.prototype.addListeners =

    function() { this.$element.on('click', this.doSomething.bind(this)); }; MyComponent.prototype.doSomething = function() { return '123'; }; 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
  34. 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
  35. É 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"
  36. É 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); }
  37. Mock É um objeto com implementações que imitam o componente

    que o mock está substituindo. Isso é útil se você está testando o objeto A que interage com o objeto B, mas você quer escrever testes para a implementação de A.
  38. É um objeto com implementações que imitam o componente que

    o mock está substituindo. Isso é útil se você está testando o objeto A que interage com o objeto B, mas você quer escrever testes para a implementação de A. Mock var _ = require('underscore'); var Component = { foo: function(a, b) { /* ... */ }, bar: function(n) { /* ... */ }, calc: function(collection) { collection = _.map(collection, function(n) { return n * 2; }); collection = _.reduce(collection, this.foo, 0); collection = _.sortBy(collection, this.bar); return _.last(collection); } };
  39. describe('Mocking Underscore', function() { var _, collection; beforeEach(function() { _

    = jasmine.createSpyObj('_', ['map', 'reduce', 'sortBy', 'last']); collection = [1,2,3]; Component.calc(collection); }); it('call underscore methods', function() { expect(_.map).toHaveBeenCalledWith(collection, jasmine.any(Function)); expect(_.reduce).toHaveBeenCalledWith(collection, Component.foo, 0); expect(_.sortBy).toHaveBeenCalledWith(collection, Component.bar); expect(_.last).toHaveBeenCalledWith(collection); }); }); Mock
  40. 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); }
  41. 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(); }); });
  42. 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
  43. Código Testável Qualquer componente deve ser como uma API

  44. 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
  45. 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');
  46. Testes de Aceitação ZombieJS CasperJS Nightwatch.js Capybara Cucumber Codeception Behat

    Splinter Behave
  47. 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
  48. 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'); }); }); });
  49. Code Coverage with Istanbul

  50. Gremlins

  51. Sem Testes Pull Request

  52. Integração Contínua

  53. Integração Contínua

  54. 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?