Slide 1

Slide 1 text

Matheus Azzi Teste seu JAVASCRIPT

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Testes são perda de TEMPO/DINHEIRO?

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Custo de Mudança Tempo de Desenvolvimento Tradicional Com Testes Custo de uma mudança ao longo do projeto Source: http://goo.gl/EgYGh9

Slide 8

Slide 8 text

Podem reduzir a quantidade de bugs em 40% ~ 90%

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

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.

Slide 12

Slide 12 text

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 (

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Testes unitários

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

// ./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

Slide 19

Slide 19 text

// ./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

Slide 20

Slide 20 text

// ./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

Slide 21

Slide 21 text

TEST RUNNER

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

$ 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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Karma Setup

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

// 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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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() { $('
' + '
' + '' + '' + '' + '
' + '
').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

Slide 32

Slide 32 text

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() { $('
' + '
' + '' + '' + '' + '
' + '
').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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

É 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"

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Code Coverage with Istanbul

Slide 49

Slide 49 text

Gremlins

Slide 50

Slide 50 text

Integração Contínua

Slide 51

Slide 51 text

Integração Contínua

Slide 52

Slide 52 text

Sem Testes Pull Request

Slide 53

Slide 53 text

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?