Hacer tests en la pista de Elixir

Aprende a probar tus ejercicios de Elixir en Exercism


Desde la terminal, cambia al directorio base del ejercicio y luego ejecuta los tests con:

$ mix test

Esto ejecutará el archivo de tests que se encuentra en la subcarpeta test: un archivo que termina en _test.exs

Tests pendientes

En las suites de tests de los ejercicios de práctica, todos los tests menos el primero están etiquetados para omitirse.

Cuando consigas que un test pase, puedes dejar de omitir el siguiente comentando el @tag :pending correspondiente con el símbolo #.

Por ejemplo:

# @tag :pending
test "shouting" do
  assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end

Si quieres ejecutar todos los tests a la vez, puedes incluir todos los tests omitidos usando la opción --include del comando mix test:

$ mix test --include pending

O bien puedes habilitar todos los tests comentando la línea ExUnit.configure de la suite de tests.

# ExUnit.configure exclude: :pending, trace: true

Otras funcionalidades de testing de Elixir

ExUnit y mix test ofrecen bastantes métodos para agrupar, etiquetar y ejecutar tests, así como varias formas de controlar la ejecución de los tests, buena parte de las cuales se resume a continuación.

Métodos para ejecutar tests concretos

Documentación:

Ejecutar los tests de un archivo concreto

Todos los tests de un solo archivo pueden ejecutarse con mix test especificando el archivo:

$ mix test test/<FILE>.exs

NOTA: el tagging puede afectar a qué tests se ejecutan realmente con este método.

Ejecutar tests individuales

Los tests individuales pueden ejecutarse haciendo referencia al número de línea de un test en el archivo:

$ mix test test/<FILE>.exs:LINENUM

Puedes ejecutar varios tests indicando varios números de línea separados por :.

Por ejemplo, dado un archivo con el siguiente contenido con números de línea:

test "Test 1" do           # 1
  # test implementation    # 2-6
end                        # 7
                           # 8
test "Test 2" do           # 9
  # test implementation    # 10-21
end                        # 22
                           # 23
test "Test 3" do           # 24
  # test implementation    # 25-35
end                        # 36

El primer y el tercer test pueden ejecutarse con:

$ mix test test/FILE.exs:1:24

NOTA: cuando especificas tests mediante números de línea, el tagging se ignora.

Ejecutar grupos de tests

Los tests pueden agruparse con describe:

describe "short test group description" do
  test "test description" do
    # test implementation
  end

  test "another test description" do
    # test implementation
  end
end

Todos los tests de un grupo pueden ejecutarse haciendo referencia a su número de línea en el archivo, igual que cuando haces referencia a tests individuales y los ejecutas.

Documentación:

Otras opciones útiles de mix test

  • --include y --exclude: ejecuta o no tests concretos según sus @tag
  • --failed: ejecuta solo los tests que fallaron la última vez que se ejecutaron
  • --max-failures: la suite deja de evaluar tests cuando se alcanza este número de fallos
  • --seed: inicializa el generador de números aleatorios que se usa para aleatorizar el orden de los tests. --seed 0 desactiva la aleatorización, de modo que los tests de un mismo archivo se ejecutarán siempre en el mismo orden en que se definieron
  • --stale: ejecuta solo los tests que hacen referencia a módulos que han cambiado desde la última vez que se ejecutaron los tests con --stale
  • --only task_id:1: o con otro número; en los ejercicios de aprendizaje ejecuta solo los tests asociados a la tarea concreta

Documentación:

Typespecs y Dialyzer (DIscrepancy AnalYZer for ERlang programs)

Los ejercicios de Elixir incluyen un archivo de implementación esqueleto en la subcarpeta lib. Este archivo esboza el módulo y las funciones que se espera que implementes. En la mayoría de los ejercicios encontrarás typespecs encima de la declaración de la función. Empiezan por la etiqueta @spec y suelen seguir el formato @spec function_name(type1, type2) :: return_type. Se usan en Elixir y Erlang como documentación y, junto con una herramienta llamada Dialyzer, para encontrar incoherencias de tipos y posibles bugs. Para más información, consulta la documentación de typespecs. Para ver la documentación sobre Dialyzer, consulta Erlang -- dialyzer.

De forma opcional, puede que quieras comprobar los tipos de tu implementación con Dialyzer. Tendrás que dar un par de pasos. Para ello, debes añadir la dependencia Dialyxir al archivo mix.exs de tu ejercicio.

defp deps do
  # Add this:
  [{:dialyxir, "~> 0.4", only: [:dev]}]
end

Después, usa las tareas de mix para obtener y compilar desde la línea de comandos:

$ mix deps.get
...
$ mix deps.compile
...

Si es la primera vez que ejecutas Dialyzer, lo más probable es que no tengas un archivo plt. Dialyzer usa la tabla de búsqueda persistente, o PLT, para almacenar en caché información sobre los tipos integrados de Elixir y Erlang. Para crear un PLT con valores predeterminados razonables, ejecuta:

$ mix dialyzer --plt

Por último, puedes ejecutarlo con:

$ mix dialyzer

Asegúrate de ajustar la ruta a la ruta de las bibliotecas de Elixir de tu sistema. Por ejemplo, si instalaste Elixir con homebrew, probablemente lo encuentres en /usr/local/Cellar/elixir/1.3.2.

Cabe reiterar que ejecutar Dialyzer y eliminar todas las advertencias es un paso opcional al completar un ejercicio. Las advertencias de Dialyzer pueden ser difíciles de descifrar. Por ejemplo, examina las advertencias de esta implementación de lo más absurda del ejercicio Bob.

defmodule Bob do
  @spec hey(input :: String.t()) :: String.t()
  def hey(input) do
    1
  end

  def hey(input) do
  end
end

Esto produce las siguientes advertencias.

bob.exs:2: Invalid type specification for function 'Elixir.Bob':hey/1. The success typing is (_) -> 1
bob.exs:7: The variable _input@1 can never match since previous clauses completely covered the type any()

La primera advertencia significa que la función no devuelve el tipo correcto. La última indica que la segunda definición de la función nunca se alcanzará porque la primera siempre coincide.