Desenvolvimento

13 nov, 2018

Como aplicar duas abordagens de testes unitários

Publicidade

Este artigo foi publicado originalmente em: https://www.concrete.com.br/2018/10/31/isolated-unit-tests-e-shallow-unit-tests/

***

Este artigo foi escrito a partir de uma talk que fiz na Concrete. Nos preocupamos muito com a qualidade do software e com os testes unitários. As melhores técnicas para escrever testes são sempre temas de talks e conversas entre nós, devs.

Além disso, tivemos problemas recentes em um projeto ao testar componentes do Angular que, aparentemente, é um problema que transborda para outros frameworks, já que os conceitos não são muito claros.

Para quem não conhece testes unitários, uma unidade é a porção de código que você escolhe testar, e durante esse teste você não pode ter dependências de outros trechos de código, componentes, serviços, rede e etc. Para simular algumas dependências, você deve usar spy, stubs e mocks.

Ter um componente como unidade, envolve escrever testes unitários das entradas, saídas e comportamentos desse componente. Boa parte dos frameworks e bibliotecas atuais fornecem formas de criar testes unitários para eles.

Para esses testes temos dois tipos de abordagem que trataremos nesse artigo. Elas são conhecidas como shallow rendering unit tests e isolated unit tests (testes unitários de renderização superficial e testes unitários isolados).

Isolated Unit Tests

O que são os Isolated Unit Tests? De forma simples, em testes unitários isolados você importa sua classe e chama os métodos dela sem olhar para o seu template e o comportamento do mesmo.

Você escreve seus it e expects para métodos da classe, informa os parâmetros e observa a saída ou modificação de comportamento das propriedades ao chamar esses métodos.

Então, se tivermos o seguinte componente (o exemplo foi tirado o site oficial do Angular.io):

export class HeroesComponent implements OnInit {
  heroes = HEROES;  selectedHero: Hero;
// métodos de constructor e onInit omitidos p/ facilitar a leitura do exemplo 
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

Nosso teste seria escrito da seguinte forma:

// não adicionei os imports para facilitar a leitura
describe('HeroComponent test', () => {
  let heroComponent;
  beforeEach(() => {
    heroComponent = new HeroComponent();
  });
  
  it('expect selectedHero has a hero name after call', () => {
    cons hero = { id: 0, name: 'Black Panther' };
    
    heroComponent.onSelect(hero);
    
    expect(component.selectedHero).toEqual(hero); // Simplificado p/ o exemplo.
  });
});

Agora veremos um exemplo de teste de renderização superficial ou shallow test.

Shallow Rendering Test

São testes unitários que renderizam parte do seu template sem precisar ou depender de outros componentes, e com isso fazer as suposições. Esse tipo de teste requer algumas coisas além do teste isolado, mas vamos aos exemplos antes de falar mais sobre isso.

Imagine que temos o seguinte template como parte do nosso HeroComponent já mencionado na parte dos testes isolados:

<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<label>name:
  <input [(ngModel)]="hero.name" placeholder="name">
</label>

Como testar se houve uma modificação do nome do herói em nosso componente, já que isso acontece no template e não em nossa classe? Para isso, faremos o seguinte teste:

describe('HeroComponent test', () => {
  let fixture, heroComponent;
  beforeEach(() => {
    fixture = TestBed.createComponent(HeroComponent);
    heroComponent = fixture.componentInstance;
    fixture.detectChanges();
  });
  it('expect hero name be upperCase after render', () => {
    heroComponent.selectedHero = { id: 0, name: 'Black Panther' };
    fixture.detectChanges();
    const heroName = heroComponent.debugElement.nativeElement.querySelector('');
    expect(heroName.textContent).toBeEqual('Black Panther');
  });
});

Esse é um exemplo simples de como acontece o shallow unit test. Ele “renderiza” parte do seu template criando elementos do DOM e observando eles ao longo do teste.

Problemas com Shallow Unit Test

Ao comparar os testes, notamos como a estratégia de shallow é maior e mais verbosa do que a forma isolada. Além disso ele também traz alguns outros problemas e dificuldades para testar.

Alguns problemas que temos é que demora um pouco mais para executar os testes, porque algumas interações e comportamentos dos componentes, ao serem testados exigem uma declaração ainda maior. Quando se trata de eventos que acontecem no DOM, dependendo da experiência da pessoa ou do time com testes, pode ser algo que tire a velocidade de desenvolvimento e em alguns momentos parece que escrevemos testes integrados, já que existe a dependência de outras interações do browser.

Resolvendo o problema de eventos ao usar Shallow Unit Test

Para um ponto específico quero deixar uma solução, já que foi algo pelo qual tive certos problemas com eventos disparados com clicks e eventos subsequentes que trazem verdadeiros problemas nos testes shallow.

Por exemplo: quando clicamos em alguns elementos do browser, outros eventos são disparados, mas quando isso acontece através de um script, não temos o disparo desses eventos, então o que fazer para testar?

Temos que criar um dispatchEvent especifico para vermos o resultado e chamadas da função que emitem, como podemos ver no exemplo abaixo:

const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'focus here!';
input.addEventListener('focus', function(event) {
  console.log('element:', this);
  console.log('event:', event);
})
input.dispatchEvent(new Event('focus', { bubbles: true, cancelable: false }))

Com isso conseguimos chamar algumas funções dentro da classe do nosso componente. Para essa solução fica o agradecimento ao Marcus Ortense.

Qual das duas abordagens escolher?

Demonstrar essas duas abordagens de testes é importante, especificamente na documentação do Angular. Só temos a abordagem por meio de shallow rendering e podemos usar outras formas e padrões para ter uma boa cobertura de teste e não depender das ações do template.

Agradecimentos

Obrigado, Emerson de Faria Batista pela ajuda na revisão e também Marcus Ortense, já mencionado.

Referências

Ficou com alguma dúvida? Deixe nos comentários.