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. O QUE É UM TESTE? É uma verificação feita sobre

    algo para garantir um determinado comportamento
  2. Custo de Mudança Tempo de Desenvolvimento Tradicional Com Testes Custo

    de uma mudança ao longo do projeto Source: http://goo.gl/EgYGh9
  3. 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
  4. 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.
  5. 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 (
  6. 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
  7. 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
  8. calc.spec.js alc', function() { // ./assets/scripts/calc.js function Calc() { }

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

    { } Calc.prototype.sum = function(n1, n2) { return n1 + n2; }; Jasmine
  10. // ./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
  11. // ./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
  12. // ./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
  13. $ 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
  14. 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 }) };
  15. // 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. É 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"
  24. É 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); }
  25. 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.
  26. É 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); } };
  27. 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
  28. 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); }
  29. 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(); }); });
  30. 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
  31. 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
  32. 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');
  33. 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
  34. ZombieJS // ... describe('tries to login', function() { describe('providing correct

    info', function() { before(function(done) { browser .fill('email', '[email protected]') .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', '[email protected]') .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'); }); }); });
  35. 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?