Testando

Esta tradução está atualizada.

Testes são uma parte importante do desenvolvimento de software. Nesta lição nós veremos como testar nosso código Elixir com ExUnit e algumas das melhores práticas de como fazer isto.

Sumário

ExUnit

O framework de testes integrado do Elixir é o ExUnit, isto inclui tudo o que precisamos para testar exaustivamente o nosso código. Antes de avançar, é importante notar que testes são implementados como scripts Elixir, por isso precisamos usar a extensão de arquivo .exs. Antes de podermos executar nossos testes nós precisamos iniciar o ExUnit com ExUnit.start(), este é mais comumente feito em test/test_helper.exs.

Quando geramos nosso projeto de exemplo na lição anterior, o mix foi útil o suficiente para criar um teste simples para nós, podemos encontrá-lo em test/example_test.exs:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

  test "greets the world" do
    assert Example.hello() == :world
  end
end

Podemos executar testes do nosso projeto com mix test. Se fizermos isso agora devemos ver uma saída semelhante a:

..

Finished in 0.03 seconds
2 tests, 0 failures

Porque há dois testes na saída? Vamos dar uma olhada em lib/example.ex. Mix criou outro teste lá para nós, algum doctest.

defmodule Example do
  @moduledoc """
  Documentation for Example.
  """

  @doc """
  Hello world.

  ## Examples

      iex> Example.hello
      :world

  """
  def hello do
    :world
  end
end

assert

Se você escreveu testes antes, então você está familiarizado com assert; em alguns frameworks should ou expect preenchem o papel de assert.

Usamos o assert macro para testar se a expressão é verdadeira. No caso em que não é, um erro vai ser levantado e os nossos testes irão falhar. Para testar uma falha vamos mudar nosso exemplo e em seguida, executar o mix test.

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

  test "greets the world" do
    assert Example.hello() == :word
  end
end

Agora nós devemos ver uma saída bem diferente:

  1) test greets the world (ExampleTest)
     test/example_test.exs:5
     Assertion with == failed
     code:  assert Example.hello() == :word
     left:  :world
     right: :word
     stacktrace:
       test/example_test.exs:6 (test)

.

Finished in 0.03 seconds
2 tests, 1 failures

ExUnit nos diz exatamente onde nossos asserts falharam, qual era o valor esperado e qual o valor atual.

refute

refute é para assert como unless é para if. Use refute quando você quiser garantir que uma declaração é sempre falsa.

assert_raise

Às vezes pode ser necessário afirmar que um erro foi levantado, podemos fazer isso com assert_raise. Vamos ver um exemplo de assert_raise na próxima lição sobre Plug.

assert_receive

Em Elixir, as aplicações consistem em atores/processos que enviam mensagens um para o outro, portanto muitas vezes você quer testar mensagens sendo enviada. Como o ExUnit executa seu próprio processo ele pode receber mensagem como qualquer outro processo e você pode afirmar isso com a macro 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 :ping
  end
end

assert_received não espera mesangens com assert_receive você pode especificar um tempo limite.

capture_io and capture_log

Capturar uma saída da aplicação é possível com ExUnit.CaptureIO sem mudar a aplicação original. Basta passar a função gerando a saída em:

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.

Configuração de Teste

Em alguns casos, pode ser necessária a realização de configuração antes de nossos testes. Para fazer isso acontecer, nós podemos usar as macros setup e setup_all. setup irá ser executado antes de cada teste, e setup_all uma vez antes da suite de testes. Espera-se que eles vão retornar uma tupla de {:ok, state}, o estado estará disponível para os nossos testes.

Por uma questão de exemplo, vamos mudar o nosso código para usar setup_all:

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

Mocking

A resposta simples para mocking no Elixir é: não faça isso. Você pode instintivamente querer utilizar mocks, porém eles são altamente desaconselhados na comunidade Elixir por uma boa razão.

Para uma discussão mais longa, temos este excelente artigo. O ponto principal é que ao invés de mockar dependências para testar (mock como verbo), existem muitas vantagens em explicitamente definir interfaces (comportamentos) para código fora da aplicação e usar implementações mockadas (mock como nome) no nosso código cliente para testar.

Para alternar entre as implementações no código da aplicação, a maneira preferida é passar o módulo como argumento e usar um valor padrão. Se isto não funcionar, use o mecanismo de configuração embutido. Para criar estas implementações mockadas, você não precisa de uma biblioteca especial de mocking, apenas comportamentos e callbacks.