Automatização de testes no Front End

William Grasel

@willgmbr

Por que eu deveria escrever testes automatizados?

Mas isso não é trabalho de tester?

Tester faz testes manuais exploratórios, no máximo automatiza alguns casos de testes de aceitação

Existem diversos tipos de testes com objetivos diferentes!

Pirâmide de Testes

Sorvete da Alegria

Sorvete da Desgraça

Testes de unidade:

  • Testam módulos ou componentes isoladamente
  • Simulando o comportamento das dependências com mocks
  • Deve dar feedback rápido para refactoring
  • Podemos ter milhares em um projeto grande
  • Ou mesmo centenas para cada módulo
  • Devem ser estupidamente rápidos
  • Confiáveis e determinísticos

Não tenha mais medo do seu código! 

Mock, Spy, Stub

Que isso???

Do que eu preciso?

  • Um framework de teste: Jasmine, Mocha, Jest, etc
  • Ferramentas de mock, spy, assert: Chai, Sinon, etc
  • Algum lugar para executar seus testes:

NodeJS sem navegador

Diretamente no Navegador

Utilizando o KarmaJS

Evite o navegador se possível!

Mas como?


  describe('UserService suite', () => {

    it('should obtain the user token', () => {












    });

  });

  describe('UserService suite', () => {

    it('should obtain the user token', () => {
      // Preparando Mock
      const mockJWTService = jasmine
        .createSpyObj('jwt', ['createToken']);









    });

  });

  describe('UserService suite', () => {

    it('should obtain the user token', () => {
      // Preparando Mock
      const mockJWTService = jasmine
        .createSpyObj('jwt', ['createToken']);

      // Preparando alvo do meu teste
      const subject = new UserService(mockJWTService);






    });

  });

  describe('UserService suite', () => {

    it('should obtain the user token', () => {
      // Preparando Mock
      const mockJWTService = jasmine
        .createSpyObj('jwt', ['createToken']);

      // Preparando alvo do meu teste
      const subject = new UserService(mockJWTService);

      // Executando ação a ser testada
      const token = subject.getToken();



    });

  });

  describe('UserService suite', () => {

    it('should obtain the user token', () => {
      // Preparando Mock
      const mockJWTService = jasmine
        .createSpyObj('jwt', ['createToken']);

      // Preparando alvo do meu teste
      const subject = new UserService(mockJWTService);

      // Executando ação a ser testada
      const token = subject.getToken();

      // Assert: Verificando o resultado esperado
      expect(token).toBe('expected-token');
    });

  });

Não duplique sua lógica na suite de teste!

Deixe executando em background enquanto desenvolve!

Um teste nunca deve influenciar outro,

a ordem de execução não deve importar!

Testes de integração:

  • Testam a integração de módulos internos críticos
  • Ou com dependências externas mais importantes
  • Como por exemplo o DOM no FrontEnd
  • Ou o Banco de Dados no BackEnd
  • Costumam ser bem mais lentos que os unitários
  • Cuidado para não retestar a lógica interna de cada módulo
  • Não é necessário testar todas as integrações existentes

Sabe aquela dependência externa que quebra sempre?

Normalmente se usa as mesmas ferramentas dos testes de unidade

Que tal testar isoladamente

a lógica interna e a interação com o DOM?


  describe('MyComponent suite', () => {

    describe('Internal logic', () => {
      it('should change prop with method', () => {
        const subject = new MyComponent();
        subject.someMethod();
        expect(subject.someProp).toBe('some-value');
      });
    });











  });

  describe('MyComponent suite', () => {

    describe('Internal logic', () => {
      it('should change prop with method', () => {
        const subject = new MyComponent();
        subject.someMethod();
        expect(subject.someProp).toBe('some-value');
      });
    });

    describe('UI interactions', () => {
      it('should call method on button click', () => {
        const subject = new MyComponent();
        const container = $('<div>');
        container.append(subject.element);
        container.find('#button').click();
        expect(subject.method).toHaveBeenCalled();
      });
    });

  });

Mas eu posso colocar os testes de unidade e integração na mesma suite?

Depende...

É teste de integração com o que?

Esses testes de integração vão impactar muito a execução dos unitários?

Testes de aceitação:

  • Testam o sistema de ponta a ponta,  e2e: end to end
  • Também conhecido como teste caixa preta
  • Garante que todos os módulos funciona bem juntos
  • Simulando um usuário real usando a aplicação
  • Algumas dependências externas podem ser mocadas
  • Mas devemos estar o mais próximo possível de prod

Pq não usar apenas e2e?

  • Rodamos centenas de testes unidade por segundo
  • Uma suite de 100 testes e2e pode levar mais de meia hora
  • São frágeis e podem quebrar facilmente
  • É inviável testar todos os casos de uso desse modo...
  • Mas precisamos garantir pelo menos os caminhos mais críticos!
  • E ter certeza que esta tudo funcionando junto no final

Posso deixar os testes de aceitação para a galera de QA?

Ferramentas

  • Um navegador real
  • Web Browser Automation
  • Cypress, Selenium
  • Jasmine, Mocha, Jest, etc

Que tal um navegador sem cabeça?

Testando o Google


  describe('google search suite', () => {
    it('deve atualizar o título da página com a busca', () => {
      browser.get('http://www.google.com');
      element(by.name('q')).sendKeys('Julie');
      element(by.name('btnG')).click();
      expect(browser.getTitle()).toEqual('Julie - Google Search');
    });
  });

Abstraia a navegação

Page Objects ou App Actions

Refatorando:


  class GoogleSearchPage {
    navigateTo() {
      browser.get('http://www.google.com');
    }
    searchWith(query) {
      element(by.name('q')).sendKeys(query);
      element(by.name('btnG')).click();
    }
  }

  describe('google search test', () => {
    it('deve atualizar o título', () => {
      const page = new GoogleSearchPage;
      page.navigateTo();
      page.searchWith('Julie');
      expect(browser.getTitle())
        .toEqual('Julie - Google Search');
    });
  });

Lembra que um teste nunca deve influenciar os outros?

Precisamos limpar as alterações feitas na base de dados depois de cada teste!

Mas e esse tal de TDD?

Test Driven Development

Testar depois de escrever o código é tedioso!

Se seu código é difícil de testar... ele é ruim!

O teste será o primeiro usuário do seu código

Mas como faz isso ae?

  • Primeiro escreva o teste, antes mesmo do código a ser testado!
  • Veja esse teste falhar, verifique a mensagem de erro
  • Escreva apenas o código necessário para fazer esse teste passar
  • Escreva um novo teste repetindo os passos anteriores
  • Refatore o código escrito sem adicionar novas funcionalidades, até que todos os testes passem novamente

Como eu testo o meu teste?

Preciso usar TDD o tempo todo?

Vou demorar mais usando TDD?

Preciso ter 100% de cobertura de teste de unidade?

Referências:

Perguntas?

Obrigado! =)

@willgmbr

Automatização de testes no Front End

By William Grasel

Automatização de testes no Front End

TDD ainda é um grade tabu no desenvolvimento de UI e arquiteturas de Front End, isso quando não é um tema ainda completamente desconhecido pelos devs. A maioria das pessoas não sabe o que testar, quando testar e como testar cada coisa. Vamos entender todas as possibilidades de teste automatizados no Front End, desde os unitários, integrados e de aceitação, também conhecidos como e2e (de ponta a ponta), e quando utilizar cada um.

  • 3,099