Skip to content

Instantly share code, notes, and snippets.

@reginadiana
Last active September 27, 2023 23:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reginadiana/707cce1ea35635c20ec991bf1f4e7232 to your computer and use it in GitHub Desktop.
Save reginadiana/707cce1ea35635c20ec991bf1f4e7232 to your computer and use it in GitHub Desktop.
Anotações sobre testes

Tipos

Testes unitários

Testam, via código, a menor parte da aplicação, como módulos, fragmentos de código e funções de forma isolada.

Testes de integração

Testam, via código, a integração entre diferentes modulos e partes do sistema. Em aplicações web, podem estar a comunicação entre componentes em uma mesma página ou uma feature espalhada em diferentes seções.

Testes de aceitação

Usados principalmente em ambientes ageis, testam os critérios de aceite de uma estória/feature através de ferramentas como o Cucumber, envolvendo o time não técnico como stakholders primários (PO).

Testes de sistema

São testes de ponta a ponta, como o procedimento de buscar um produto, adicioná-lo ao carrinho e finalizar compra.


Spec

É a menor unidade do teste e é importante que ela não seja generica para não gerar muitos assets (expectativas).

Suit

É um grupo de specs

No BDD, os testes definem o comportamento do nosso código, e no TDD, os testes definem a estrutura exata do nosso código.

Patterns

  1. Use o test-id para capturar elementos durante o teste.

  2. O atributo test-id deve obedecer a ordem: [page-name||component-name]__[element-type]--[type]

  3. Quando um bug ocorrer, tente simular este cenário via teste e vá implementado a solução até que ele seja resolvido.

Escrevendo testes

Como não escrever os seus testes, Camila Campos

Teste é um código que só faz 3 coisas:

  1. Ajeita o que precisa para o teste funcionar (Arrange - Setup)
  2. Executa a ação que voce quer testar (Act - Exercise)
  3. Verifica se aconteceu o que voce esperava (Assert - Verify)

Por isso, cada teste é dividido em 3 bloquinhos. É bom que cada bloquinho tenha uma linha separando-os.

Testes UI - Ponta a ponta

⚠️ Não usar mocks, stubs, fakes, etc, pois a intenção é testar um cenário mais realista possivel

Unit - Teste de unidade

Pega cada pedaço pequeno, como uma classe, um model, etc. Agora vamos usar mocks, stubs, fakes, etc para que cada teste não precise de outros para funcionar e não batermos em nada externo. Além disso, esses testes tem como função guiar o design do código.

Testes de Integração

Estão no meio dos dois tipos de teste citados acima. Nós batemos em ambientes externos uma vez, salvamos a resposta e usamos ela pra sempre.

É coerente ter muitos testes de unidade e pouco de integração pois são mais rápidos de escrever e executar.

Command

  1. Não retorna nada
  2. Muda alguma coisa
  3. Se preocupa com mensagens passadas
  4. Usa spies

Query

  1. Retorna alguma coisa
  2. Nao muda nada
  3. Se preocupa com o retorno
  4. Usa doubles

⚠️ Cuidado com testes frágeis. Tente rodar testes sozinhos pois eles não devem depender de outros para funcionar.

⚠️ Evite usar lógica nos testes. Exemplo: repetições, condições, etc.

⚠️ Cuidado ao descrever a descrição do teste. A descrição deve fazer sentido com o teste que foi escrito.

⚠️ Não escreva testes com multiplas verificações.

⚠️ Cuidado com setups grandes. Para resolver, use Design Patterns e conceitos de Orientação a Objetos

⚠️ Testes repetitivos

⚠️ Cuidado ao deixar os seus testes muito dry usando before/after, pois se torna dificil de ler e não sabemos de onde as coisas estão vindo. Podemos resolver isso deixando o código duplicado mesmo ou usando metodos.

É melhor não ter testes do ter testes ruins - Camila Campos

Quando escrevemos testes ruins, confiamos nele cegamente acreditando que a aplicação está funcionando quando isso pode não ser verdade. Mas quando não há testes, nada pode cogitar a garantia de que a aplicação funciona.

Better Specs

Material Original

Descreva seus metodos

Se claro sobre o que o seu metodo está descrevendo.

Use contextos

Contextos são ótimos para fazer testes mais claros e organizados (os deixa mais fáceis de ler). Quando descrevemos um contexto, comece com "quando", "com" ou "sem".

Deixe sua descrição pequena

Uma descrição pequena nunca deve ser maior que 40 caracters. Se isso acontecer voce deve separar usando um contexto.

Unica expectativa para o teste

A 'unica expectativa' tem mais a ver com 'cada teste deve fazer uma unica afirmação'. Isso vai ajudar voce a encontrar possíveis erros, indo diretamente no teste que está falhando e deixar seu código mais legivel. Isolando em pedacinhos, voce quer que cada exemplo especifique um (apenas um) comportamento. Muitas expectativas no mesmo exemplo é um sinal de que voce pode estar especificando muitos comportamentos esperados.

Teste todos os casos possíveis

Testar é uma boa prática, mas se voce não testar cada caso, não vai ser util. Teste validações, casos extremos e invalidações como "not found", "inexistente", "formatação errada", "presença", "tamanho inválido", etc

Usar mock ou não

Geralemnte a regra diz para não usar e testar casos reais quando possível. Mockar faz seus testes serem mais rápidos mas são dificeis de usar. Voce precisa entender e usá-los muito bem.

Crie apenas os dados que precisa

Se voce já trabalhou em um projeto médio (ou pequeno), deve saber que testes podem ser dificieis de rodar. Para resolver esse problema, é importante não carregar mais dados do que o necessário. Além disso, se voce acha que precisa de deznas de records, voce provavelmente está enganado.

Use FactoryBot

Este é um tópico antigo, mas é bom lembrar. Não use fixtures porque eles não dificeis de controlar, use instancias do FactoryBot. Use eles para reduzir a verbosidade na criação de novos dados.

Teste o que voce ve

⚠️ Testar controllers?

Não use "deve" no começo da descrição dos testes

Guard

Podemos usar o guard para rodar apenas os testes que foram atualizados

Stubbing HTTP requests

As vezes voce precisa acesar serviços externos. Nesses casos, voce não pode realmente testar o serviço de verdade, mas voce deve usar stub com soluções como webmock.

Jest

É um framework do Facebook construído em javascript, desenvolvido inicialmente para testar aplicações em React (também do Facebook), mas hoje também está presente em outros projetos no frontend como Vue, Node, Angular, etc. Mas neste conteúdo, o abordaremos em relação ao Vue.

Instalando

Para instalar, basta rodar o script:

yarn add --dev jest

Para usar junto com o Vue, rode mais os seguintes comandos:

yarn global add @vue/cli
yarn add @vue/cli-plugin-unit-jest

Configurando

É possível configurar os seguintes parâmetros dentro do arquivo jest.config.js presente na raíz do projeto, junto com package.json:

Snapshots: são capturas de tela. No Rails, usamos o screenshot no meio do teste para ver a imagem da tela naquele ponto. No Jest é diferente: ele faz a captura a partir de um wrapper, retornando apenas o documento html mostrando alguns estilos css inline.

module.exports = {
  snapshotSerializers: ["jest-serializer-vue"]
}

Coverage: é o que chamamos de cobertura de testes. No Rails usamos gems como o SimpleCov, passando por todos os controllers, models, etc. No Jest, ele irá passar por todos os componentes.

Para definir se essa cobertura será feita automaticamente ao rodar os testes usamos o parâmetro collectCoverage, que recebe um dado do tipo boolean que é false por padrão. Ao marcar essa opção como true, toda vez que rodarmos os testes a cobertura sempre será feita.

module.exports = {
  collectCoverage: true
}

Nem sempre é necessário testar todos os arquivos do projeto como a pasta de dependências do node_modules, arquivos css, imagens, etc. Então, podemos determinar quais arquivos e pastas serão cobertos ou não usando o parâmetro collectCoverageFrom. Exemplo:

module.exports = {
  collectCoverageFrom: [
  "**/*.{js,vue}", // considera todos os arquivos do tipo js e vue
  "!**/node_modules/**", // ignora as dependencias
  "!**/test_helpers/**", // ignora essa pasta
  ],
}

Alias: a partir do parâmetro moduleNameMapper, podemos criar atalhos para importar uma pasta ou arquivo. Com ele, podemos usar como referencia a raiz do projeto e atrelar esse caminho a um nome ou simbolo. Então toda vez que formos importar algo em um arquivo, não importa onde o arquivo a ser importado esteja em relação a ele, pois podemos sempre usar o mesmo atalho. Exemplo:

  // Importando sem alias
  import api from "../../../services/requests.js";
// Definindo um alias dentro do regex
module.exports = {
  moduleNameMapper: [
    "^@/(.*)$": "<rootDir>/js/services/$1"
  ],
}
  // Importando com alias definido como: @
  import api from "@/requests.js";

Transform: o Jest só entende código em javascript. Então se houver algum código diferente (vindo do VueJS, TypeScript, etc) nos testes ele vai emitir algum erro. Então o transform faz justamente isso: transforma todo o código em javascript para que o Jest entenda. Para saber mais, recomendo um artigo sobre ele na doc do Jest.

module.exports = {
  transform: {
    // process `*.vue` files with `vue-jest`
    ".*\\.(vue)$": "vue-jest",
  },
}

Babel: Assim como o transform, o Babel também tem a ver com conversão de código. Neste caso, a sua principal função é converter o código ECMAScript 2015 em uma versão compatível com versões antigas do JS presentes em navegadores e browsers mais antigos. Para saber mais, recomendo a documentação do Babel

// .babelrc
{
    "presets": [
      [ "@babel/preset-env",
        { "useBuiltIns": "entry",
          "corejs": { "version": "3"}
        }
      ]
    ]
}

moduleFileExtensions: recebe um array com tipos de arquivos que o Jest interpretará automaticamente. Exemplo:

// Importando arquivo sem o module
import imagem from "./image.png"; // Sem o module
// Configurando o module
module.exports = {
  moduleFileExtensions: ["js", "json", "vue", "png"]
}
// Importando arquivo com o module
import imagem from "./image"; // Com o module

preset: precisamos configurar esse parâmetro para que o Jest reconheça os arquivos que estamos importando.

module.exports = {  
  preset: "@vue/cli-plugin-unit-jest"
}

Para rodar os testes, também precisamos adicionar o script no arquivo package.json:

  "scripts": {
    "test": "jest"
  },

Assim, podemos rodar os testes tanto com yarn run test como yarn run jest (se estiver usando yarn)

Criando um componente no Vue

<template>
  <form @submit.prevent="login">
    <span v-if="submit">Login realizado com sucesso</span>

    <label for="email">Email</label>
    <input id="email" type="email" v-model="form.email" />
    <strong v-if="!valid.email">Campo Obrigatório</strong>

    <label for="password">Senha</label>
    <input id="password" type="password" v-model="form.password" />
    <strong v-if="!valid.password">Campo Obrigatório</strong>

    <button type="submit">Login</button>
  </form>
</template>

<script>
export default {
  name: "AppForm",

  data: function() {
    return {
      form: {
        email: "",
        password: "",
      },
      valid: {
        email: true,
        password: true,
      },
      submit: false,
    };
  },

  methods: {
    login() {
      // Verifica o preenchimento dos campos
      this.valid.email = this.form.email.length > 0;
      this.valid.password = this.form.password.length > 0;

      // O formulário é submetido se todos os campos forem validos
      this.submit = this.valid.email && this.valid.password;
    },
  },
};
</script>

Montamos um componente para um formulário que valida o preenchimento dos campos de email e senha. Ao submetê-lo, poderá ser apresentado uma mensagem de erro (caso não forem preenchidos) ou de sucesso (caso contrário).

Pode parecer simples, mas já podemos testar:

  • Renderização de elementos: verificando se o formulário está sendo mostrado corretamente;

  • Valores de entrada: preenchendo os campos;

  • Chamada de ações: ao submeter o formulário;

  • Resultados: ao verificar quais mensagens foram mostradas.

Vale lembrar, que ações são eventos assíncronos como o clicar de um botão, preenchimento dos campos ou retorno de uma API, portanto, é recomendado o uso de async await nesses casos. Para usá-los, precisamos do regenerator-runtime

Criando arquivos de teste

Para que o Jest entenda que um dado arquivo se trata de um teste, ele precisa:

  1. Estar dentro de uma pasta chamada __tests__.
  2. Estar com o prefixo final .test.js ou .spec.js. Exemplo: meu_teste_maroto.test.js
  3. Ter pelo menos um teste, se não um alerta será emitido.

Testando

import AppForm from "js/components/AppForm";
import { mount } from "@vue/test-utils";
// Usaremos para os testes assincronos
import "regenerator-runtime/runtime";

// Construção do wrapper
const form = mount(AppForm);

describe("user", () => {
  it("access form without errors", () => {
    // Mensagem de sucesso não deve aparecer
    expect(form.find("span").exists()).toBe(false);

    // Mensagens de erro não devem aparecer
    const errors = form.findAll("strong");
    expect(errors.length).toBe(0);

    // Formulário
    expect(form.find("label").text()).toBe("Email");
    expect(form.findAll("label").at(1).text()).toBe("Senha");
    expect(form.find("input").element.value).toBe("");
    expect(form.findAll("input").at(1).element.value).toBe("");
    expect(form.find("button").text()).toBe("Login");
  });

  it("must filled fields", async () => {
    await form.find("form").trigger('submit.prevent');

    // Mensagens de erro devem aparecer
    const errors = form.findAll("strong");
    expect(errors.length).toBe(2);
    expect(errors.at(0).text()).toBe("Campo Obrigatório");
    expect(errors.at(1).text()).toBe("Campo Obrigatório");

    // Mensagem de sucesso não deve aparecer
    expect(form.find("span").exists()).toBe(false);
  });

  it("submit form successfully", async () => {
    // Preenche os campos e submete o form novamente
    await form.find("input").setValue("email@email.com");
    await form.findAll("input").at(1).setValue("senha123");
    await form.find("form").trigger('submit.prevent');

    // Mensagens de erro desaparecem
    const errors = form.findAll("strong");
    expect(errors.length).toBe(0);

    // Mensagem de sucesso aparece
    expect(form.find("span").exists()).toBe(true);
    expect(form.find("span").text()).toBe("Login realizado com sucesso");
  })
});

A partir desses testes, podemos analisar alguns pontos:

Setup: no caso apresentado, não foi necessário fazer muitas configurações antes de iniciar os testes, apenas a construção do componente em si. Na verdade, isso vai depender muito do grau de complexidade do próprio componente, isto é, se ele depende de props, API's, funções externas, vuex ou outras dependências que podem ser estranhos para o Jest, sendo necessário mocá-los.

Wrapper: de acordo com a documentação, "wrapper" é o objeto que contém o componente que foi montado. No nosso exemplo, o montamos com o mount. Também podemos chamá-lo simplesmente de "component" ou do que ele representa como "form", deixando o teste mais legível. Pode ser apenas uma questão de nomenclatura, mas é importante citar pois nas documentações ele sempre será chamado de wrapper.

Variáveis: construímos o wrapper e o definimos como uma variável global, portanto, foi utilizado em todos os testes. Já as mensagens de erro foram declaradas localmente, logo, só podem ser acessadas dentro de cada teste onde foram declaradas.

find: é possível encontrar um wrapper a partir outro usando como referencia um seletor (seja ele uma tag html, classe ou id), retornando sempre o primeiro resultado.

findAll: é possível encontrar vários wrappers a partir de um, retornando um array com todos os resultados. Por isso, o Jest o chama de WrapperArray. Para acessar cada resultado, podemos usar o at() em seguida, levando a posição que pode ir de 0 ao tamanho do array menos um.

trigger: chama uma determinada ação, que pode um 'click' ou 'submit.prevent' para o caso do formulário.

setValue: atribui um valor a um elemento de entrada, no nosso caso, o input.

exists: retorna um boolean, indicando se aquele wrapper existe ou não.

toBe: apresenta o resultado do teste, como o próprio nome diz, "deve ser", ou seja, "deve ser verdadeiro", "deve ser falso", "deve ser 0", etc.

Metodos do Wrapper

Propriedade Descrição
attributes Retorna os atributos do DOM
classes Retorna um array de classes
contains Verifica se aquele wrapper da macth com algum seletor ou outro componente
destroy Destroi o wrapper
emitted Retorna um objeto com os eventos emitidos por aquele wrapper
exists Retorna un boolen, indicando se aquele elemento existe
find Retorna um selector ao buscar por um elemento no wrapper que pode ou não existir
findComponent Ideal para buscar componentes dentro de componentes
findAll Retorna um array com todos os seletores encontrados na busca
findAllComponentes Retorna um array com todos os componentes encontrados
html
get Funciona quase como o find, mas vai emitir um erro o elemento buscado não for encontrado
setProps ode manipular diretamente as props do componente
setData pode manipular diretamente os dados de date do componente

Testando a tela em diferentes larguras

Por padrão, o tamanho de tela que o Jest usa como referencia é 1024px de largura e 768px de altura, mas podemos alterar esses valores para testar em outros tamanhos de tela. Vou usar um exemplo simples com uma mudança de texto da versão mobile para desktop.

<template>
  <div class="message__container">
    <h1 class="message__desk">Bem vindo a versão desktop</h1>
    <h1 class="message__mobile">Bem vindo a versão mobile</h1>
  </div>
</template>

<script>
export default {
  name: "AppMessage"
}
</script>

<style>
.message__mobile {
  display: block;
}

.message__desk {
  display: none;
}

@media (min-width: 1200px) {
  .message__mobile {
    display: none;
  }

  .message__desk {
    display: block;
  }
}
</style>
import AppMessage from "./AppMessage";

describe("must render", () => {
  it("desktop message", () => {
    const component = mount(AppMessage);

    expect(component.find("h1").text()).toBe("Bem vindo a versão mobile");

    global.innerWidth = toPx;
    global.dispatchEvent(new Event('resize'));
  })
})

Para saber mais:

Cypress: contém os arquivos de teste "end-to-end". Em projetos grandes, podem estar em outra app já que não precisam conhecer o código

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment