Começando testes unitários em Elixir

Raphael Santos
How Kovi Work
Published in
4 min readAug 21, 2020

Aqui na Kovi utilizamos o Elixir para desenvolver nosso próprio gateway de pagamento, que recebe milhares de requests diariamente e segue firme e forte com uma alta disponibilidade, carinhosamente chamado de: HermesSim! o deus grego mensageiro… que no nosso caso, entrega boletos para os motoristas

Todos os dias melhorias e correções são feitas no Hermes e nossa missão é entregar código com qualidade e manter a aplicação estável.

Tendo em vista toda a responsabilidade que a aplicação possui, utilizamos os testes unitários como uma de nossas ferramentas para garantir que novas features não vão quebrar o que já está desenvolvido e garantir a qualidade do que está em desenvolvimento.

E com Elixir, escrever testes unitários é como escrever poesia para máquinas.

Ele possui o ExUnit como framework de testes integrado e é completasso!

Iniciando

Para começar precisamos criar um novo projeto Elixir:
mix new hello_world

* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/hello_world.ex
* creating test
* creating test/test_helper.exs
* creating test/hello_world_test.exs
Your Mix project was created successfully.
You can use “mix” to compile it, test it, and more:
cd hello_world
mix test
Run “mix help” for more commands.

No diretório test o Mix criou 2 arquivos:

  • test_helper.exs
    -
    Contém apenas o ExUnit.start() que é necessário antes de usarmos o ExUnit.
  • hello_world_test.exs
    -
    Contém a estrutura básica dos nossos testes.

Todos os arquivos de teste devem ser criados com a extensão .exs e não .ex.
Basicamente:
- .ex é usado para compilação
- .exs é usado para scripts.

Para uma explicação mais completa a respeito desse assunto, sugiro este rápido e excelente artigo

O ExUnit.case é um módulo que deve ser usado em outros módulos para configurar o comportamento dos testes.
Quando usado, ele aceita as seguintes opções:

  • :async - configura os testes neste módulo para serem executados simultaneamente com os testes em outros módulos.
    Os testes no mesmo módulo nunca são executados simultaneamente.
    Deve ser habilitado apenas se os testes não alterarem nenhum estado global. O padrão é false.
use ExUnit.Case, async: true|false

Para executar os testes basta ir ao diretório raiz do projeto e rodar: mix test

Compiling 1 file (.ex)
Generated hello_world app
..Finished in 0.02 seconds1 doctest, 1 test, 0 failuresRandomized with seed 841742

Describe

Podemos agrupar nossos testes utilizando a função describe que por convenção é utilizada para organizar testes por função.

describe “HelloWorld.hello” do
test “greets the world” do
assert HelloWorld.hello() == :world
end
end

Ao usar o Mix, é possível executar todos os testes dentro de um describe:

mix test — only describe:"HelloWorld.hello"

Assert

Em alguns frameworks de teste should ou expect preenchem o papel de assert que é a macro no Elixir para testar se uma expressão é verdadeira.

defmodule HelloWorldTest do
use ExUnit.Case
doctest HelloWorld
test “greets the world” do
assert HelloWorld.hello() == :world
end
end

Refute

A macro refute é usada no caso de desejarmos garantir que um argumento seja sempre falso ou nulo.

defmodule HelloWorldTest do
use ExUnit.Case
doctest HelloWorld
test “greets the world” do
refute HelloWorld.hello() == :word
end
end

Assert_raise

As vezes é necessário testar se um erro é gerado para determinado comportamento da aplicação e pra isso utilizamos o assert_raise.

assert_raise ArithmeticError, fn ->
1 + “test”
end

Assert_received

Como o ExUnit executa seu próprio processo, ele pode receber mensagens como qualquer outro processo e você pode afirmar isso utilizando o assert_received.

defmodule SendingProcess do
def run(pid) do
send(pid, :ping)
end
end
defmodule TestReceive do
use ExUnit.Case

test “receives ping” do
SendingProcess.run(self())
assert_received :pinge
end
end

Capture_io and capture_log

Com o ExUnit.CaptureIO é possível capturar uma saída da aplicação sem mudar a aplicação original.

defmodule OutputTest do
use ExUnit.Case
import ExUnit.CaptureIO

test “outputs Hello World” do
assert capture_io(fn ->
IO.puts(“Hello World”)
end) == “Hello World\n”
end
end

ExUnit.CaptureLog é o equivalente para capturar a saída de Logger.

Test Setup

Em alguns casos é necessário alguma configuração prévia antes de executar os testes e para isso podemos utilizar duas macros, a setup e a setup_all.

- setup: é executado sempre antes de um teste

- setup_all: é executado apenas uma vez

defmodule ExampleTest do
use ExUnit.Case
doctest Example

setup_all do
{:ok, recipient: :world}
end
test “greets”, state do
assert Example.hello() == state[:recipient]
end
end

Mocks e Stubs

A comunidade Elixir defende basicamente que ao invés de mockar dependências, você pode definir comportamentos para o código fora da aplicação e usar implementações mockadas, como por exemplo: utilizar os setups para mockar a resposta de um serviço externo.

A discussão sobre esse assunto é bem mais profunda e pode ser conferida neste artigo.

Agora que você já teve uma pequena noção do quão simples é escrever testes unitários em Elixir, não tem desculpa pra abrir PR sem teste.

Por hoje é só pessoal!

Referências:
https://hexdocs.pm/ex_unit/ExUnit.html
https://elixirschool.com/pt/lessons/basics/testing

--

--