Testing

Learn different types of testing in Elixir.

We already used the ExUnit framework to write tests for our Issues tracker application. But that chapter only scratched the surface of Elixir testing. Let’s dig deeper.

Testing the comments

In the Elixir world, a common way to document functions is by showing the function being used in an IEx session.

Let’s look at an example from our Issues application. The TableFormatter formatter module defines a number of self-contained functions that we can document.

defmodule Issues.TableFormatter do
  import Enum, only: [ each: 2, map: 2, map_join: 3, max: 1 ] 
@doc """

The code takes a list of row data, where each row is a map, and a list of headers. It prints a table to standard output of the data from each row identified by each header. That is, each header identifies a column, and those columns are extracted and printed from the rows. We calculate the width of each column to fit the longest element in that column.

"""
def print_table_for_columns(rows, headers) do
  with data_by_columns = split_into_columns(rows, headers),
       column_widths   = widths_of(data_by_columns),
       format          = format_for(column_widths)
  do
       puts_one_line_in_columns(headers, format)
       IO.puts(separator(column_widths))
       puts_in_columns(data_by_columns, format)
end end
@doc """

Given a list of rows where each row contains a keyed list of columns, the code returns a list containing lists of the data in each column. The headers parameter contains the list of columns to extract.

Run the commands above to execute the code below.

## Example
    iex> list = [Enum.into([{"a", "1"},{"b", "2"},{"c", "3"}], %{}),
    ...> Enum.into([{"a", "4"},{"b", "5"},{"c", "6"}], %{})]
    iex> Issues.TableFormatter.split_into_columns(list, [ "a", "b", "c" ]) 
    [ ["1", "4"], ["2", "5"], ["3", "6"] ]
"""
def split_into_columns(rows, headers) do
  for header <- headers do
    for row <- rows, do: printable(row[header])
  end 
end
@doc """

the code returns a binary (string) version of our parameter.

## Examples
  iex> Issues.TableFormatter.printable("a") 
  "a"
  iex> Issues.TableFormatter.printable(99) 
  "99"
"""
def printable(str) when is_binary(str), do: str
def printable(str), do: to_string(str)
@doc """

Given a list containing sublists, where each sublist contains the data for a column, the code returns a list containing the maximum width of each column.

## Example
  iex> data = [ [ "cat", "wombat", "elk"], ["mongoose", "ant", "gnu"]] 
  iex> Issues.TableFormatter.widths_of(data)
  [ 6, 8 ]
  """
  def widths_of(columns) do
    for column <- columns, do: column |> map(&String.length/1) |> max
  end
@doc """

Given a list containing rows of data, a list containing the header selectors, and a format string, this writes the extracted data under control of the format string.

"""
def puts_in_columns(data_by_columns, format) do
    data_by_columns
    |> List.zip
    |> map(&Tuple.to_list/1)
    |> each(&puts_one_line_in_columns(&1, format))
end
def puts_one_line_in_columns(fields, format) do
    :io.format(format, fields)
end 
end

Note how some of the documentation contains sample IEx sessions. This helps people who come along later understand how to use the code. Just as important , it lets us understand what our code will feel like to use.

But the problem with comments is that they just don’t get maintained. The code changes, the comment gets stale, and it becomes useless. Fortunately, ExUnit has doctest, which is a tool that extracts the iex sessions from the code’s @doc strings, runs it, and checks that the output agrees with the comment. To invoke it, we simply add one or more doctest «ModuleName» lines to the test files. We can add them to existing test files for a module (such as table_formatter_test.exs) or create a new test file just for them. the latter is what we’ll do here. Let’s create a new test file called test/doc_test.exs in the code below.

We exit from iex session using “Ctrl+C” twice and then we can run the following commands:

$ mix test test/doc_test.exs 
......
Finished in 0.00 seconds
5 doctests, 0 failures

And, of course, these tests are integrated into the overall test suite:

$ mix test 
..............
Finished in 0.1 seconds
5 doctests, 9 tests, 0 failures

Get hands-on with 1400+ tech skills courses.