Desenvolvimento através de testes: aprimorando habilidades

No artigo anterior, examinamos aspectos teóricos. É hora de começar a praticar.

imagem

Vamos fazer uma implementação simples da pilha em JavaScript usando desenvolvimento por meio de testes.

Stack - uma estrutura de dados organizada de acordo com o princípio do LIFO: Last In, First Out. Existem três operações principais na pilha:

push : adicionar item
pop : excluir um item
peek : adicionar elemento principal

Crie uma classe e chame-a de pilha. Para complicar a tarefa, suponha que a pilha tenha uma capacidade fixa. Aqui estão as propriedades e funções de implementação da nossa pilha:

items : itens na pilha. Usaremos uma matriz para implementar a pilha.
capacidade : capacidade da pilha.
isEmpty () : retorna true se a pilha estiver vazia, caso contrário, false.
isFull () : retorna true se a pilha atingir a capacidade máxima, ou seja, quando você não puder adicionar outro elemento. Caso contrário, retorna false.
push (elemento) : adiciona um elemento. Retorna Full se a pilha estiver cheia.
pop : remove o item. Retorna Vazio se a pilha estiver vazia.
peek () : adiciona um elemento principal.

Vamos criar dois arquivos stack.js e stack.spec.js . Usei a extensão .spec.js porque estou acostumada, mas você pode usar .test.js ou dar um nome diferente e movê-lo para __tests__ .

Como praticamos o desenvolvimento por meio de testes, escreveremos um teste com falha.

Primeiro, verifique o construtor. Para testar o arquivo, você precisa importar o arquivo de pilha:

const Stack = require('./stack') 

Para aqueles que estão interessados ​​em saber por que eu não usei a importação aqui, a versão estável mais recente do Node.js não suporta esse recurso hoje. Eu poderia adicionar Babel, mas não quero sobrecarregá-lo.

Ao testar uma classe ou função, execute um teste e descreva qual arquivo ou classe você está testando. Isto é sobre a pilha:

 describe('Stack', () => { }) 

Então, precisamos verificar se, quando a pilha é inicializada, uma matriz vazia é criada e configuramos a capacidade correta. Então, escrevemos o seguinte teste:

 it('Should constructs the stack with a given capacity', () => { let stack = new Stack(3) expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) 

Observe que usamos toEqual e não usamos toBe para stack.items porque eles não fazem referência à mesma matriz.

Agora execute o teste de fios stack.spec.js . Executamos o Jest em um arquivo específico porque não queremos que outros testes sejam corrompidos. Aqui está o resultado:

imagem

Pilha não é um construtor . Claro. Ainda não criamos nossa pilha e não criamos um construtor.

No stack.js, crie sua classe, construtor e exporte a classe:

 class Stack { constructor() { } } module.exports = Stack 

Execute o teste novamente:

imagem

Como não definimos os elementos no construtor, Jest esperava que os elementos na matriz fossem iguais a [], mas eles não foram definidos. Então você deve inicializar os elementos:

 constructor() { this.items = [] } 

Se você executar o teste novamente, receberá o mesmo erro de capacidade , portanto, também será necessário definir a capacidade:

 constructor(capacity) { this.items = [] this.capacity = capacity } 

Execute o teste:

imagem

Sim Teste aprovado . Aqui está o que é TDD. Espero que agora os testes façam mais sentido para você! Continuar?

isEmpty


Para testar isEmpty , vamos inicializar uma pilha vazia, testar se isEmpty retorna true, adicionar um elemento e verificar novamente.

 it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { let stack = new Stack(3) expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) 

Ao executar o teste, você deve receber o seguinte erro:

 TypeError: stack.isEmpty is not a function 

Para resolver esse problema, precisamos criar isEmpty dentro da classe Stack :

 isEmpty () { } 

Se você executar o teste, deverá receber outro erro:

 Expected: true Received: undefined 

Nada é adicionado ao isEmpty . A pilha está vazia se não houver elementos nela:

 isEmpty () { return this.items.length === 0 } 

isFull


É o mesmo que isEmpty , pois este exercício testa essa função usando TDD. Você encontrará a solução no final do artigo.

Push


Aqui precisamos testar três coisas:

  • Um novo item deve ser adicionado ao topo da pilha.
  • push retorna “Full” se a pilha estiver cheia;
  • Um item que foi adicionado recentemente deve ser devolvido.

Crie outro bloco usando a descrição para envio . Colocamos esse bloco dentro do bloco principal.

 describe('Stack.push', () => { }) 

Adicionar item


Crie uma nova pilha e adicione um elemento. O último item na matriz de itens deve ser o item que você acabou de adicionar.

 describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { let stack = new Stack(3) stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) }) 

Se você executar o teste, verá que o push não está definido e isso é normal. push precisará de um parâmetro para adicionar algo à pilha:

 push (element) { this.items.push(element) } 

Teste passou novamente. Você notou alguma coisa? Mantemos uma cópia desta linha:

 let stack = new Stack(3) 

Isso é muito chato. Felizmente, temos um método beforeEach que permite fazer algumas customizações antes de cada teste. Por que não tirar proveito disso?

 let stack beforeEach(() => { stack = new Stack(3) }) 

Importante:



a pilha deve ser declarada antes de beforeEach . De fato, se você o definir no método beforeEach , a variável da pilha não será definida em todos os testes porque não está na área correta.

Mais importante do que importante: também devemos criar um método afterEach . A instância da pilha agora será usada para todos os testes. Isso pode causar algumas dificuldades. Imediatamente após beforeEach, adicione este método:

 afterEach(() => { stack.items = [] }) 

Agora você pode remover a inicialização da pilha em todos os testes. Aqui está o arquivo de teste completo:

 const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should constructs the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) }) }) 

Teste de valor de retorno


Existe um teste:

 it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) }) 

Quando você executa o teste, você obtém:

 Expected: 2 Received: undefined 

Nada retorna dentro do impulso ! Devemos corrigir isso:

 push (element) { this.items.push(element) return element } 

Retornar cheio se a pilha estiver cheia


Neste teste, primeiro precisamos preencher a pilha, adicionar um elemento, garantir que nada de supérfluo tenha sido adicionado à pilha e que o valor de retorno seja Cheio .

 it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') }) 

Você verá este erro ao executar o teste:

 Expected: 3 Received: 4 

Então o item é adicionado. Isto é o que queríamos. Primeiro você precisa verificar se a pilha está cheia antes de adicionar algo:

 push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element } 

Teste aprovado.

Exercício: pop e peek

É hora de praticar. Teste e implemente pop and peek .

Dicas:

  • pop é muito semelhante ao push
  • espiar também é como empurrar
  • Até o momento, não refatoramos o código, porque isso não era necessário. Pode haver uma maneira nessas funções de refatorar seu código após escrever os testes e passá-los. Não se preocupe, alterando o código, testes são necessários para isso.

Não olhe para a solução abaixo sem tentar fazer isso sozinho. A única maneira de progredir é tentar, experimentar e praticar.

Solução


Bem, como está o exercício? Você fez isso? Caso contrário, não desanime. Testar leva tempo e esforço.

 class Stack { constructor (capacity) { this.items = [] this.capacity = capacity } isEmpty () { return this.items.length === 0 } isFull () { return this.items.length === this.capacity } push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element } pop () { return this.isEmpty() ? 'Empty' : this.items.pop() } peek () { return this.isEmpty() ? 'Empty' : this.items[this.items.length - 1] } } module.exports = Stack const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should construct the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) it('Should have an isFull function that returns true if the stack is full and false otherwise', () => { expect(stack.isFull()).toBe(false) stack.items = [4, 5, 6] expect(stack.isFull()).toBe(true) }) describe('Push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) }) it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') }) }) describe('Pop', () => { it('Should removes the last element at the top of a stack', () => { stack.items = [1, 2, 3] stack.pop() expect(stack.items).toEqual([1, 2]) }) it('Should returns the element that have been just removed', () => { stack.items = [1, 2, 3] let element = stack.pop() expect(element).toBe(3) }) it('Should return Empty if one tries to pop an empty stack', () => { // By default, the stack is empty expect(stack.pop()).toBe('Empty') }) }) describe('Peek', () => { it('Should returns the element at the top of the stack', () => { stack.items = [1, 2, 3] let element = stack.peek() expect(element).toBe(3) }) it('Should return Empty if one tries to peek an empty stack', () => { // By default, the stack is empty expect(stack.peek()).toBe('Empty') }) }) }) 

Se você olhar para os arquivos, verá que eu usei a condição tripla no pop e peek. Foi isso que refatorei. A implementação antiga era assim:

 if (this.isEmpty()) { return 'Empty' } return this.items.pop() 

O desenvolvimento por meio de testes nos permite refatorar o código após a gravação dos testes. Encontrei uma implementação mais curta sem me preocupar com o comportamento dos meus testes.

Execute o teste novamente:


imagem

Os testes melhoram a qualidade do código. Espero que agora você entenda todos os benefícios dos testes e use o TDD com mais frequência.

Nós convencemos você de que o teste é crucial e melhora a qualidade do código? Em vez disso, leia a parte 2 do artigo sobre desenvolvimento através de testes. E quem não leu o primeiro, siga o link .

Source: https://habr.com/ru/post/pt430876/


All Articles