Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Testes unitários de JS com Jasmine e Karma

Testes unitários de JS com Jasmine e Karma

  • Be the first to comment

Testes unitários de JS com Jasmine e Karma

  1. 1. Testes Unitários de JS com Jasmine e Karma Douglas Matoso
  2. 2. Por que fazer testes unitários no frontend?
  3. 3. ● Dá confiança em refatorações. ● Identifica bugs mais cedo. ● Ajuda a documentar o código. ● Ajuda a melhorar o design. ● É divertido :-D Por quê?
  4. 4. Tools of the trade
  5. 5. ● Rodar os testes em diferentes navegadores (inclusive headless, como PhantomJS) com apenas um comando. ● Rodar os testes rapidamente (em segundos) e frequentemente. ● Rodar os testes em modo watch, onde alterações no código disparam os testes automaticamente. ● Poder gerar relatórios diversos (resumo dos testes, cobertura do código testado, etc.) ● Isolar o frontend do backend, evitando interferências da rede, do banco de dados, etc. Objetivos
  6. 6. ● Roda os testes em diferentes navegadores com um comando. ● Modo watch. ● Permite gerar relatórios através de plugins. ● Fácil integração com Gulp, Grunt e pipelines de CI (como Jenkins). Karma: o executador http://karma-runner.github.io
  7. 7. Karma: config e resultado // karma.conf.js . . . frameworks: [‘jasmine’], files: [ ‘app/vendor/**/*.js’, ‘app/js/**/*.js’, ‘test/specs/**/*.js’ ], reporters: [ ‘progress’, ‘html’, ‘coverage’ ], browsers: [ ‘Chrome’, ‘PhantomJS’ ] . . .
  8. 8. Karma e Gulp var KarmaServer = require('karma').Server, KARMA_CONF_PATH = __dirname + '/karma.conf.js'; gulp.task('test', function (done) { var server = new KarmaServer({ configFile: KARMA_CONF_PATH, browsers: ['Chrome'], singleRun: false }, done); server.start(); }); Sem Gulp > karma start > karma start --single-run Com Gulp > gulp test > gulp tdd > gulp test-ci
  9. 9. describe(‘What is being tested’, function () { describe(‘context’, function () { beforeEach(function () { ... }); it(‘should do something’, function () { ... }); it(‘should do something else’, function () { ... }); }); }); Jasmine: o framework http://jasmine.github.io
  10. 10. it(‘should sum the numbers’, function () { var result = sum(1,2,3); expect(result).toEqual(6); }); it(‘should return something’, function () { var result = foo(); expect(result).not.toBeNull(); }); Jasmine: Matchers .toEqual(value) .toBeNull() .toBeDefined() .toMatch(regex ou string) .toBeTruthy(value) // cast para boolean .toBeFalsy(value) // cast para boolean .toContain(value) // array contém .toBeGreaterThan(value) .toBeLessThan(value) .toThrow()
  11. 11. // esta suíte não vai ser executada xdescribe(‘Suite’, function () { it(‘should do something’, function () { ... }); it(‘should do something else’, function () { ... }); }); Jasmine: skip e focus describe(‘Suite’, function () { // apenas este teste será executado fit(‘should do something’, function () { ... }); it(‘should do something else’, function () { ... }); });
  12. 12. describe(‘Product model’, function () { beforeEach(function () { this.model = new Product({ id: 123 }); this.server = sinon.fakeServer.create(); this.server.respondWith(‘GET’, ‘/products/123’, JSON.stringify({ description: ‘JavaScript Book’, price: 19.99 })]); }); afterEach(function () { this.server.restore(); }); it(‘should fetch product data’, function () { this.model.fetch(); this.server.respond(); expect(this.model.get(‘description’)) .toEqual(‘Javascript Book’); }); }); Sinon: o servidor (fake) http://sinonjs.org
  13. 13. var TestRouter = function () { init: function () { this.server = sinon.fakeServer.create({ respondImmediately: true }); }, enableRoute: function (route) { this.server.respondWith(route.method, route.url, route.response); } })(); ------------------------------------------ var ProductRoute = { main: { method: ‘GET’, url: /products/(d+)/, response: JSON.stringify({ description: ‘JavaScript Book’, price: 19.99 }) } }; Sinon: classe de rotas describe(‘Product model’, function () { beforeEach(function () { this.model = new Product({ id: 123 }); TestRouter.init(); TestRouter.enableRoute(ProductRoute.main); afterEach(function () { TestRouter.restore(); }); it(‘should fetch product data’, function () { this.model.fetch(); expect(this.model.get(‘description’)) .toEqual(‘Javascript Book’); }); });
  14. 14. Melhorando a escrita dos testes
  15. 15. // O que é mais fácil escrever? // Assim: response: JSON.stringify({ id: 1, description: ‘Product Test’, price: 9.99, active: true, stock: 10 }) // Ou assim: response: JSON.stringify(Factory.create(‘product’)) // Mais opções Factory.create(‘product’, { stock: 0 }); Factory.create(‘inactive-product’); Factory.createList(10, ‘product’); Fabricando dados com js-factories // Definição da factory Factory.define(‘product’, function (attrs) { return _.extend({ id: this.sequence(‘id’), description: ‘Product Test’, price: 9.99, active: !this.is(‘inactive’), stock: 10 }, attrs); }); https://github.com/matthijsgroen/js-factories
  16. 16. describe(‘Form validations’, function () { beforeEach(function () { this.view = new AppView(); $(‘body’).prepend(this.view.render().el); }); afterEach(function () { this.view.remove(); }); Testando a view it(‘should validate phone number’, function () { var phoneInput = $(‘#phoneInput’), submitBtn = $(‘#form input:submit’), alert; phoneInput.val(‘abc’); submitBtn.click(); alert = $(‘.alert-danger’); expect(alert.is(‘:visible’)).toBeTruthy(); expect(alert.text()).toMatch(‘phone is invalid’); });
  17. 17. describe(‘Form validations’, function () { beforeEach(function () { loadFixtures(‘signup-form.html’); }); Custom matchers e HTML fixtures com jasmine-jquery it(‘should validate phone number’, function () { var phoneInput = $(‘#phoneInput’), submitBtn = $(‘#form input:submit’), alert; phoneInput.val(‘abc’); submitBtn.click(); alert = $(‘.alert-danger’); expect(alert).toBeVisible(); expect(alert).toHaveText(‘phone is invalid’); }); https://github.com/velesin/jasmine-jquery // Alguns novos matchers toBeChecked() toBeDisabled() toBeHidden() toBeVisible() toContainElement(jQuerySelector) toContainHtml(string) toContainText(string) toHandle(eventName) toHaveAttr(attributeName, attributeValue) toHaveClass(className) toHaveCss(css) toHaveText(string) toHaveHtml(string)
  18. 18. describe(‘Form validations’, function () { beforeEach(function () { this.page = new SignupPage(); }); afterEach(function () { this.page.destroy(); }); Testes mais elegantes com Page Objects it(‘should validate phone number’, function () { var alert; this.page.setPhoneNumber(‘abc’); this.page.submitForm(); alert = this.page.getAlert(); expect(alert).toBeVisible(); expect(alert).toHaveText(‘phone is invalid’); });
  19. 19. var SignupPage = Class.extend({ init: function () { this.view = new AppView(); $(‘body’).prepend(this.view.render().el); }, destroy: function () { this.view.remove(); }, setPhoneNumber: function (value) { $(‘#phoneInput’).val(value); }, submitForm: function () { $(‘#form input:submit’).click(); }, getAlert: function () { return $(‘.alert-danger’); } }); Testes mais elegantes com Page Objects http://martinfowler.com/bliki/PageObject.html
  20. 20. Show me the code! https://github.com/doug2k1/karma-jasmine-example
  21. 21. THANK YOU FOR YOUR TIME!

×