Começando testes unitários em Elixir
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: Hermes — Sim! 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.exsYour Mix project was created successfully.
You can use “mix” to compile it, test it, and more: cd hello_world
mix testRun “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
enddefmodule 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