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

    View Slide

  2. Matheus Azzi
    www.matheusazzi.com
    matheusazzi
    fb.com/matheusazzi
    Codeminer 42
    speakerdeck.com/matheusazzi
    [email protected]

    View Slide

  3. O QUE É
    UM TESTE?
    É uma verificação
    feita sobre algo
    para garantir um
    determinado
    comportamento

    View Slide

  4. Testes são perda de
    TEMPO/DINHEIRO?

    View Slide

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

    View Slide

  6. Manutenção
    &
    Qualidade
    POR QUE TESTAR
    Front-end?

    View Slide

  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

    View Slide

  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

    View Slide

  9. View Slide

  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.

    View Slide

  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
    (

    View Slide

  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

    View Slide

  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

    View Slide

  14. Testes unitários

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  20. TEST RUNNER

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  24. Karma Setup

    View Slide

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

    View Slide

  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

    View Slide

  27. Testes unitários são
    executados isoladamente,
    portando utilizamos artifícios
    para simular todas as
    dependências que o teste
    precisar.

    View Slide

  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

    View Slide

  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() {
    $('' +
    '' +
    '' +
    'name="name" value="Azzi">' +
    '' +
    '' +
    '').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

    View Slide

  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() {
    $('' +
    '' +
    '' +
    'name="name" value="Azzi">' +
    '' +
    '' +
    '').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"]');
    });







    // jasmine-fixture
    Fixture

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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"

    View Slide

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

    View Slide

  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.

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  43. Código Testável
    Qualquer componente
    deve ser como
    uma API

    View Slide

  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

    View Slide

  45. function Component() {
    var isLoading = true;
    }
    function Component() {
    this.isLoading = true;
    }
    $('.foo').on('click', function() {
    $(this).append('Hello');
    });
    function showHello() {
    $(this).append('Hello');
    }
    $('.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');

    View Slide

  46. Testes de Aceitação
    ZombieJS
    CasperJS
    Nightwatch.js
    Capybara
    Cucumber
    Codeception
    Behat
    Splinter
    Behave

    View Slide

  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

    View Slide

  48. 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');
    });
    });
    });

    View Slide

  49. Code Coverage
    with Istanbul

    View Slide

  50. Gremlins

    View Slide

  51. Sem Testes
    Pull Request

    View Slide

  52. Integração Contínua

    View Slide

  53. Integração Contínua

    View Slide

  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?

    View Slide