Do you want to pick up from where you left of?
Take me there

Pruebas

Las pruebas son una parte importante en el desarrollo de software. En esta lección vamos a ver como hacer pruebas de nuestro código Elixir con ExUnit y también vamos a ver algunas buenas prácticas para hacer las pruebas.

Tabla de contenidos

ExUnit

El framework de pruebas que viene con Elixir es ExUnit e incluye todo lo que necesitamos para hacer pruebas a fondo de nuestro código. Antes de empezar es importante tener en cuenta que las pruebas en Elixir están implementadas como scripts de Elixir por lo que necesitamos usar la extensión .exs. Antes de ejecutar nuestras pruebas necesitamos iniciar ExUnit con ExUnit.start(), esto suele estar hecho en test/test_helper.exs.

Cuando generamos nuestro proyecto de ejemplo en las lecciones anteriores, mix fue lo suficientemente útil para crear una prueba simple para nosotros, podemos encontrarla en test/example_test.exs:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

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

Podemos ejecutar las pruebas de nuestro proyecto con mix test. Si hacemos esto ahora deberíamos ver una salida similar a:

..

Finished in 0.03 seconds
2 tests, 0 failures

¿Por qué hay dos pruebas en la salida? Echemos un vistazo a lib / example.ex. Mix creo ahí otra prueba para nosotros, algunos doctest.

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

  @doc """
  Hello world.

  ## Examples

      iex> Example.hello
      :world

  """
  def hello do
    :world
  end
end

assert

Si has escrito pruebas antes, entonces debes estar familiarizado con assert; en algunos frameworks should o expect cumplen el rol de assert.

Usamos el macro assert para probar que la expresión es verdadera. En el caso que no lo sea, un error será lanzado y nuestras pruebas fallarán. Para probar un error vamos a cambiar nuestro ejemplo y luego ejecutamos mix test:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

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

Ahora deberíamos ver un tipo diferente de salida:

  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 dirá exactamente donde están las aserciones que fallaron, cual es el valor esperado y cual fue el valor actual.

refute

refute es a assert como unless es a if. Usa refute cuando deseas asegurarte que una declaración siempre es falsa.

assert_raise

A veces puede ser necesario verificar que un error fue lanzado, podemos hacer esto con assert_raise. Vamos a ver un ejemplo de assert_raise en la siguiente lección (Plug).

assert_receive

En Elixir, las aplicaciones constan de actores/procesos que se envían mensajes entre si, a menudo se desea probar los mensajes que se envían. Dado que ExUnit se ejecuta en su propio proceso, puede recibir mensajes como cualquier otro proceso y podemos buscar equivalencia usando 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 no espera por los mensajes, con assert_receive podemos especificar el tiempo de espera.

capture_io y capture_log

Capturar la salida de una aplicación es posible con ExUnit.CaptureIO sin cambiar la aplicación original. Simplemente pasa la función generando la salida en:

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 es el equivalente a capturar la salida en Logger.

Test Setup

En algunos casos, puede ser necesario realizar la configuración antes de nuestras pruebas. Para lograr esto, podemos usar los macros setup y setup_all. setup se ejecutara entes de cada prueba y setup_all una vez antes de todas las pruebas. Se espera que devuelvan una tupla con {:ok, state}, el estado estará disponible para nuestras pruebas.

Por ejemplo, cambiaremos nuestro 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 (simulaciones)

La respuesta simple en Elixir es: No. Puede llegar a buscar intensivamente la forma de utilizar los mocks, pero son poco recomendado por la comunidad de Elixir y existe una buena razón.

Para una discusión mas extensa, esta este excelente articulo. Lo esencial es que, en lugar de simular las dependencias para las pruebas, tiene muchas ventajas explicitamente definir interfaces (behaviors) para el código fuera de nuestra aplicación usando implementaciones Mock en el código cliente para la prueba.

Para cambiar las implementaciones en el código de la aplicación, la forma preferida es pasar el modulo como argumento y usar un valor predeterminado. Si eso no funciona, use el mecanismo de configuración incorporado. Para crear estos mocks, no necesita una librería especial para mocks, solo behaviours y callbacks.

¿Encontraste un error o quieres contribuir a la lección? ¡Edita esta lección en GitHub!