Skip to main content

Command Palette

Search for a command to run...

TDD no frontend com Angular.

O TDD ajuda você a prestar atenção às questões certas no momento certo. - Kent Beck

Updated
TDD no frontend com Angular.
R

Hi, I'm Rafael, but you can call me Rafa! 😄 I'm a software engineer passionate about everything that involves technology, with more than 5 years of experience in the market. Throughout my career, I worked in banking institutions, developing innovative solutions and contributing to the digital transformation of the financial sector.

With a strong interest in software architecture and testing, I am constantly looking to specialize and improve my skills in this area.

Here, you will find tips, tutorials and discussions on the most varied topics related to architecture, good practices and software testing, always with the aim of sharing knowledge and learning from other developers. I value the exchange of experiences and I am always willing to listen and learn from my professional colleagues.

Feel free to get in contact, leave your comments and share your own experiences and knowledge. Together, we can build a stronger, more collaborative community in the software development world.

Testar é muito importante. Quando ouvimos sobre o Test Driven Development, associamos sempre ao backend, mas é possível também aplicar ao frontend. O objetivo do artigo é ajudar aqueles que desejam praticar esta técnica concebida por Kent Beck em suas aplicações. Para que este artigo não fique grande, não vou explicar o que alguns termos significam, poderá ler mais sobre TDD no artigo com o tema, o que é tdd e bdd?. Vamos focar na prática ao decorrer deste artigo, com um exemplo mão na massa de como realmente funciona o TDD. O exemplo é simples, mas para fins didáticos funciona muito bem!

Testando Serviços

Vamos iniciar o desenvolvimento orientado a testes em nossas services. Mas não vamos realizar requisições HTTP. Neste artigo vamos focar na lógica de negócios. No caso imagine que precisamos criar uma lógica que incrementa e decrementa valores. Então temos algumas regras:

  • O valor inicial do contador deve ser zero.
  • Deve permitir incrementar (adicionar).
  • Deve permitir decrementar (remover/diminuir).
  • Não é permitido ter itens menores que zero (número negativos não são permitidos).

Agora que definimos nossos passos, vamos iniciar com o teste, sem nada criado na service :

// exemplo.service.spec.ts
import { TestBed } from '@angular/core/testing';

import { ContadorService } from './contador.service';

describe('ContadorService', () => {
  let service: ContadorService;
  ContadorService
  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(ContadorService);
  });

/* Teste que garante que a classe foi criada corretamente. */
  it('should be created', () => {
    expect(service).toBeTruthy();
  });

/* O valor inicial do contador deve ser zero. */
 it('deve ter a propriedade contador com valor inicial 0', () => {
    expect(service.contador).toBe(0);
  })
});

Na classe contador, precisamos sempre garantir que a propriedade se inicie em 0. Por isso passamos expect(service.contador).toBe(0);. Se rodar o teste deve falhar e isso é importante para a prática do TDD. Para rodar o teste em um projeto recém criado, basta executar no terminal o comando npm start. Repare que vai receber uma falha no teste e no arquivo, pois não temos nada criado nem mesmo a propriedade:

image.png

Feito isso podemos adicionar a propriedade em nossa classe:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ContadorService {

  public contador: number = 0;

  constructor() { }
}

Ao executar novamente os testes vai reparar que tivemos sucesso para essa condição do teste:

image.png

Vamos avançar e a segunda regra é:

  • Deve permitir incrementar (adicionar).

Certo então precisamos de uma lógica para isso, mas seguindo o TDD garantimos que os testes sejam criados primeiro. Então vamos para o arquivo spec.ts :

import { TestBed } from '@angular/core/testing';

import { ContadorService } from './contador.service';

describe('ContadorService', () => {
  let service: ContadorService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(ContadorService);
  });

  it('deve ser criado a classe ContadorService', () => {
    expect(service).toBeTruthy();
  });

/* O valor inicial do contador deve ser zero. */
  it('deve ter a propriedade contador com valor inicial 0', () => {
    expect(service.contador).toBe(0);
  })

/* Deve permitir incrementar (adicionar). */
  it('deve incrementar a contagem', () => {
    expect(service.contador).toBe(0);
    service.adicionarContagem();
    expect(service.contador).toBe(1);
  });

});

Os testes devem novamente falhar e apontar em vermelho o erro. Vamos para a lógica de incrementar na service:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ContadorService {

  public contador: number = 0;

  constructor() { }

  public adicionarContagem() {
    this.contador++;
  }
}

O resultado deve ser sucesso já que estamos atendendo as condições do teste:

image.png

Agora vamos mudar um pequeno detalhe. Vamos adicionar na classe de serviço apenas o método de diminuirContagem sem lógica:

  public diminuirContagem() { }

Então agora voltamos nos testes e pensamos na lógica. Já que precisamos diminuir os números, podemos fazer dessa maneira:

 it('deve diminuir a contagem', () => {
    service.contador = 2;
    service.diminuirContagem();
    expect(service.contador).toBe(1);
  });

Execute o teste e ele deve falhar mostrando agora uma mensagem de erro específica para este teste:

image.png

Certo estamos avançando. Crie a lógica para atender a necessidade de cobrir este teste:

  public diminuirContagem() {
    this.contador--;
  }

Execute novamente os testes e vamos ter sucesso:

image.png

Finalizamos? Não! Ainda falta um detalhe importante. A nossa regra, que foi definida antes de iniciarmos o desenvolvimento orientado a testes, não permite que seja possível diminuir para valores menos que 0, não implementamos testes nem validações para evitar isso. Então temos que criar esse teste também:

it('não deve diminuir quando a contagem é 0', () => {
    expect(service.contador).toBe(0); // inicia o teste em 0
    service.diminuirContagem(); // chama a função que diminui o valor para -1
    expect(service.contador).toBe(0); // agora o contador recebe -1, mas é esperado 0
  });

Essa é uma maneira simples de garantir que a propriedade contador nunca caia abaixo de 0. Ao rodar os testes devem falhar:

image.png

O erro aponta que o expect recebeu -1 mas deveria ser 0. No nosso caso é porque estamos desenvolvendo primeiro os testes. Vamos criar a lógica. Mas pense sempre que devemos passar da maneira mais simples possível o teste para depois se necessário refatorar:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ContadorService {

  public contador: number = 0;

  constructor() { }

  public adicionarContagem() {
    this.contador++;
  }

  public diminuirContagem() {
    if (this.contador === 0) return;

    this.contador--;
  }
}

/* contador.spec.ts*/

import { TestBed } from '@angular/core/testing';

import { ContadorService } from './contador.service';

describe('ContadorService', () => {
  let service: ContadorService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(ContadorService);
  });

  it('deve ser criado a classe ContadorService', () => {
    expect(service).toBeTruthy();
  });

  it('deve ter a propriedade contador com valor inicial 0', () => {
    expect(service.contador).toBe(0);
  })

  it('deve incrementar a contagem', () => {
    expect(service.contador).toBe(0);
    service.adicionarContagem();
    expect(service.contador).toBe(1);
  });

  it('deve diminuir a contagem', () => {
    service.contador = 2;
    service.diminuirContagem();
    expect(service.contador).toBe(1);
  });

  it('deve impedir diminuir quando a contagem está no 0', () => {
    expect(service.contador).toBe(0);
    service.diminuirContagem();
    expect(service.contador).toBe(0);
  });

});

Agora sim nossos testes passam com sucesso e finalizamos essa parte de criar a lógica no serviço da nossa aplicação do lado do frontend:

image.png

E esse passo a passo foi muito bem descrito por Kent Beck:

O TDD ajuda você a prestar atenção às questões certas no momento certo, para que você possa tornar seus projetos mais limpos e refinar seus projetos à medida que aprende. - Kent Beck, Desenvolvimento Orientado a Testes: Com Exemplo

Conclusão

O que vimos ao decorrer deste artigo foi uma simples demonstração de como colocar o Test Driven Development em prática. Ao iniciar na criação dos testes o desenvolvimento, pensamos com clareza no que a função deve fazer e em todos os cenários que devemos testar. Isso nos auxilia muito a ser objetivos no desenvolvimento de funcionalidades. Espero que este conteúdo tenha sido útil e qualquer crítica construtiva fiquem à vontade para deixar nos comentários! Até o próximo post!