Introdução aos testes
Um pouco da teoria sobre testes + Mocha e Chai + code coverage
Por que testar?
- Reduz o tempo gasto em análise e correção de bugs
- Facilita refatoração
- Gera documentação do seu código
- Melhora o design do código
- Garante que o trabalho tenha qualidade
Como funciona o fluxo do TDD?
TDD é o Desenvolvimento Orientado por Testes (Test Driven Development). Isso mesmo! Desenvolvemos o nosso software baseado em testes que são escritos ANTES do código!
Fluxo de Teste
- Escrever o teste
- Escrever o código para o teste passar
- Escrever um segundo caso de teste
- Corrigir o código para que passe
- Escrever um terceiro caso de teste (se necessário)
- Refatorar o código
Pensando como testar
- O que o código precisa fazer?
- Que dados ele recebe?
- Que dados ele precisa retornar?
- Que ações precisam acontecer para o código rodar?
Padrão do Teste
- "Ele deve fazer isso quando aquilo..." - "it should do that when this"
export const sum = (num1, num2) => num1 + num2
it('should return when receive 2,2') {
expect(sum(2, 2)).to.equal(4)
}
Tipos de Testes
Testes unitários
São aqueles testes de responsabilidade unica onde você testa como um método funciona de forma isolada, ou seja, esse teste unitário vai fazer os testes do método pra ver se retorna um valor, etc.
É um simples e pequeno teste automatizado que prova o comportamento de um único método. Tem como objetivo ser bem rápido e fazer o teste de um comportamento.
- Evite o ruído entre os testes (isolamento). Todos os testes precisam ser independentes, sem depender de um teste externo.
- Pra evitar isso é sempre legal usar beforeEach e afterEach para limpar as variáveis globais que você possa ter criado.
- Escolha os melhores asserts para cada momento
- Procure usar mocks para chamadas externas
- Utilize o teste unitário para organizar o design do seu código
Testes de serviço ou integração
Supomos que temos um componente no qual já fizemos todos os testes unitários, e este componente faz uma chamada ao banco, a api, e ai a gente busca alguns resultados e retorna outras coisas. No unitário esta parte de serviço a gente simplesmente faz um mock.... ou seja,fazemos uma chamada pra um valor que já sabemos qual vai ser, pois não queremos testar a api no unitario e sim como vai se comportar. No teste de integração precisa saber se de fato vai integrar e funcionar, então não da pra criar mais estes mocks.
É um teste para validar se os componentes estão funcionando em conjunto.
- Cuidado para não criar um teste inútil
- Isole o máximo possivel dos ambientes
Testes de UI/ e2e
Você ver a maquina mexendo na UI por você. Este teste é importante pra verificar se não tem um botão que esta sendo escondido onde não deveria, se conseguimos chamar um dropdown e ele ta funcionando, se ele pode ser clicável, enfim. Além de também termos os testes de regressão que é quando faz uma mudança ou outra de css ou js e você acaba quebrando uma integração.
O teste de aceitação é realizado com o proposito de avaliar a qualidade externa do produto e, na medida do possível, também a qualidade em uso.
- Valide apenas o fluxo de funcionamento do projeto, ou seja, você tem que validar a etapa indo de A pra B, indo de B para C. O usuário abriu a pagina, o usuário digitou no search, abriu a tela, ele clicou no link, etc.
Spies, Stubs e Mocks
SPIES
Como o nome sugere, spies são usados para vigiar informações sobre chamadas de funções (métodos). Um spy vai nos dizer se o método foi chamado, quantas vezes, quais argumentos foram passados, etc.
São muito uteis para testar callbacks e como métodos são usados dentro do sistema. Os spies te permitem verificar se um outro método foi chamado dentro do método que você está testando.
it('should inspect Jquery.getJSON usage of jQuery.ajax', function() {
sinon.spy(jQuery, 'ajax')
jquery.getJson('/some/resource')
assert(jquery.ajax.calledOnce)
})
STUBS
São como os spies, exceto por eles substituírem a função alvo. Podendo inclusive mudar o comportamento, assim como os valores e exceções levantadas.
Quando usar os stubs?
- Controlar comportamento de um teste: Ex: forçar uma exceção
- "Pular" uma parte não necessária do codigo Ex: execução de db
- Simplificar o teste de código assíncrono
MOCKS
São métodos falsos (similares aos spies) com comportamento pre-programado (similar ao stub) e respostas/expectations pre programados.
Mocks devem ser primariamente utilizados quando voce precisa de um stub, mas precisa verificar multiplos comportamentos num especifico ponto.
Conceitos importantes
Describe
Vai descrever os testes de uma certa função e afins. Ele serve pra gente conseguir criar um bloco onde a gente vai testar métodos de uma função, o describe vai ser o que vai iniciar o teste.
Usamos pra separar os metodos ou classes, podemos ter varios describe, pra minha classe Main, mas posso ter describe dentro de outros também.
describe('Main', function() {
describe('Method A', function() {
})
describe('Method B', function() {
})
})
Context
Serve pra gente separar os nossos casos de testes, usamos pra separar os casos que teremos...
describe('Method A', function() {
context('Case 1', function() {}
context('Case 2', function() {}
})
it
É o que de fato vai rodar primeiro comando do teste
it('should happen blabla', function() {
// espera que aconteça alguma coisa
// entrada de dados/ método sum(2, 2)
// espera retornar (4) => true | (3) => false Broken test! :P
}
Introdução aos hooks
Hooks
Códigos que são rodados a partir de alguma ação que foi executada. Serve por exemplo para criar métodos que são rodados antes de outros, ou seja, os hooks vão ajudar para que a gente consiga diminuir as nossas duplicatas
- before => roda uma vez antes do bloco
- after => roda uma vez depois do bloco
- beforeEach => roda todas as vezes antes de cada bloco
- afterEach => roda todas as vezes depois de cada bloco
describe('method B', function () {
before(function () {
// inicio de conexao com o banco
// criar um conjunto de dados
console.log('before')
})
after(function () {
// fecha conexao com o banco
// apagar esse conjunto de dados
console.log('after')
})
beforeEach(function () {
console.log('beforeEach')
arr = [1, 2, 3]
})
afterEach(function () {
console.log('afterEach')
})
// testar tipos ou se existe (smoke test)
it('should be an array', function () {
expect(arr).to.be.a('array')
})
it('should have a size of 4 when push another value to the array', function () {
arr.push(4)
expect(arr).to.have.lengthOf(4)
// console.log(arr.length) //4
})
it('should have a size of 2 when pop a value from the array', function () {
arr.pop()
expect(arr).to.have.lengthOf(2)
// console.log(arr.length) //2
})
it('should remove the value 3 when use pop in the array', function () {
arr.pop()
expect(arr).to.not.include(3)
// console.log(arr.pop() === 3) //true
})
it('should return true when pop 3 front the array', function () {
expect(arr.pop() === 3).to.be.true
})
})
})
//before
//beforeEach
//teste1
//afterEach
//beforeEach
//teste2
//afterEach
//after
Exemplificando com o Mocha e Chai
Como o próprio site do Mocha diz:
“Mocha é um framework Javascript que roda em aplicações Node.js e no browser para realizar testes assíncronos de uma maneira simples e fácil”.
Reporters do mocha
- É como informa como os testes estao passando de uma forma diferente, menor, enfim.
O seguinte comando mostra uma lista de reporters que você pode utilizar
npm test -- --reporters
Exemplo de reporter!!!
npm test -- --reporter=nyan
(muito fofinhooooo >u<)
- verde: Teste que está passando
- vermelho: erros
- azul: quantos testes voce falou pra nao ser rodado no momento
- bail: npm test -- --bail
No primeiro momento que encontrar um erro ele vai parar vai consertando por pedaço por pedaço e isso é bom :3
- only: roda somente o bloco especificado
context.only('...', function () {})
- skip: serve pra nao rodar o teste em especifico. Mostra na coloração azul
it.skip('should happen blablabla', function() {})
Chai - Uma biblioteca de Assertion
Nele, o desenvolvedor tem a opção de escolher a melhor interface que deseja para a realização de suas asserções nos seus testes: “should”, “expect” e “assert”.
Sem contar que, caso você esteja realizando um determinado teste de requisições via HTTP, o Chai te auxilia de maneira magistral a realizar as asserções necessárias.
chains
Palavrinhas que vão conectar nossos testes
Instalação do mocha e chai
npm install --save-dev mocha chai
Scripts do Package.json:
"test": "./node_modules/.bin/mocha tests/
/*.spec.js",
"test:tdd": "./node_modules/.bin/mocha tests/
Exemplos
fizzbuzz.js
const FizzBuzz = (num) => {
if (num === 0) {
return 0
}
if (num % 3 === 0 && num % 5 === 0) {
return 'FizzBuzz'
}
if (num % 3 === 0) {
return 'Fizz'
}
if (num % 5 == 0) {
return 'Buzz'
}
return num
}
export default FizzBuzz
fizzbuzz.spec.js
/*
Desafio FizzBuzz
Escreva uma lib que receba um número e:
Se o número for divisível por 3, no lugar do número escreva 'Fizz'
Se o número for divisível por 5, no lugar do número escreva 'Buzz'
Se o número for divisível por 3 e 5, no lugar do número escreva 'FizzBuzz'
Se não for múltiplo de nada, retorna o número
*/
import { expect } from 'chai'
import FizzBuzz from '../src/main'
describe('FizzBuzz', () => {
it('should return `fizz` when multiple of 3', () => {
expect(FizzBuzz(3)).to.be.equal('Fizz')
expect(FizzBuzz(6)).to.be.equal('Fizz')
})
it('should return `Buzz` when multiple of 5', () => {
expect(FizzBuzz(5)).to.be.equal('Buzz')
})
it('should return `FizzBuzz` when multiple of 3 and 5', () => {
expect(FizzBuzz(15)).to.be.equal('FizzBuzz')
})
it('should return the number when non-nultiple', () => {
expect(FizzBuzz(7)).to.be.equal(7)
})
it('should return 0 when 0', () => {
expect(FizzBuzz(0)).to.be.equal(0)
})
})
Outro exemplo:
calc.js
const sum = (num1, num2) => num1 + num2
const sub = (num1, num2) => num1 - num2
const mult = (num1, num2) => num1 * num2
const div = (num1, num2) => (num2 === 0) ? 'não é possivel divisão por zero' : num1 / num2
export { sum, sub, mult, div }
calc.spec.js
import { expect } from 'chai'
import { sum, sub, mult, div } from '../src/main'
describe('Calc', () => {
//smoke tests
describe('smoke tests', () => {
it('should exist the method `sum`', () => {
expect(sum).to.exist
expect(sum).to.be.a('function')
})
it('should exist the method `sub`', () => {
expect(sub).to.exist
expect(sub).to.be.a('function')
})
it('should exist the method `mult`', () => {
expect(mult).to.exist
expect(mult).to.be.a('function')
})
it('should exist the method `div`', () => {
expect(div).to.exist
expect(div).to.be.a('function')
})
})
// teste de soma
describe('Sum', () => {
it('should return 4 when `sum(2, 2)`', () => {
expect(sum(2, 2)).to.be.equal(4)
})
})
// testes de subtração
describe('Sub', () => {
it('should return -4 when `sub(6, 10)`', () => {
expect(sub(6, 10)).to.be.equal(-4)
})
})
// testes de multiplicação
describe('Mult', () => {
it('should return 12 when `mult(2, 6)`', () => {
expect(mult(2, 6)).to.be.equal(12)
})
})
// testes de divisão
describe('Div', () => {
it('should return 2 when `sum(12, 6)`', () => {
expect(div(12, 6)).to.be.equal(2)
})
it('should return `não é possivel divisão por zero` when divide by 0', function () {
expect(div(12, 0)).to.be.equal('não é possivel divisão por zero')
})
})
})
Code Coverage
É uma cobertura de codigo, ele vai analisar todos os pedaços do seu codigo que foram passados pelo testes, o interessante é que ele consegue analisar se alguma parte do seu codigo nao foi testada.
Vai analisar se aquele trecho do código foi rodado, isso nao significa que ele tenha sido testado corretamente, a unica coisa que o teste de cobertura faz é se algum teste rodou aquela linha.
Ele é muito importante e util, vejamos...
Biblioteca que vai permitir fazer esse code coverage:
npm install —save-dev nyc
adicione nos scrips do seu package.json:
"test:coverage": "nyc npm test"
Ele cria uma tabelinha e tem todos os arquivos que ele testou e até os que nao faz necessidade
- stmts ⇒ linhas que voce ta rodando e testando
- branchs => se tem o if else e se ta pegando
- funcs => funções
- lines => analisar a quantidade de todas as linhas que voce ta cobrindo se é 100% ou nao
Coverage nao é um significado de qualidade!
Supomos que eu quero que tenha pelo menos 80% de cobertura nas linhas, eu nao vou deixar que a pessoa suba o codigo que tenha pelo menos 80% de cobertura, ou seja, a pessoa vai ter que escrever os testes senão não vai subir
Configuração do package.json
Adicione depois do comando "scripts"
"nyc": {
"check-coverage": true,
"functions": 80,
"lines": 80,
"reporter": [
"text",
"html"
],
"exclude": [
"tests/**"
]
}
Nos scripts voce coloca isso:
"scripts": {
"lint": "./node_modules/.bin/eslint src/*.js"
"prepush": "npm run lint && npm run test:coverage"
}
Comentários
Prefere comentar em ânonimo? Siga os seguintes passos:
- Clique no campo "Nome"
- Marque os itens necessários, principalmente o último: "Prefiro publicar como um visitante"
- Adicione um email
- Agora só enviar seu comentário :)