TDD in Elixir with ExUnit and Doctest
ExUnit & DocTest
Elixir has it's own test framework built in natively, called ExUnit. ExUnit is a core component of Elixir itself, as much as the task runner and dependency manager mix. When you start a new project with mix, everything is directly set up for you, including basic unit tests for your first module and preconfigured tasks to do TDD right away.
In addition to traditional test suites, there is also another (often overlooked) feature, called DocTests. This means that you can copy a sample IEx call of your function, including the result, and paste it directly into a documentation block above the function in your code. When you run your test suite, these snippets are picked up by ExUnit and executed along with your tests! Using these DocTests you can ensure that comments will say the truth, even if someone changes the code of the function (because a test error will pop up that wants to be fixed).
When not to use doctest
In general, doctests are not recommended when your code examples contain side effects. For example, if a doctest prints to standard output, doctest will not try to capture the output.
Similarly, doctests do not run in any kind of sandbox. So any module defined in a code example is going to linger throughout the whole test suite run.
Example:
An example app can be found here:
defmodule SchizoTest do
use ExUnit.Case
doctest Schizo
test "uppercase doesnt change the first word" do
assert(Schizo.uppercase("foo") === "foo")
end
test "uppercase converts the second word to uppercase" do
assert(Schizo.uppercase("foo bar") == "foo BAR")
end
test "uppercase converts every other word to uppercase" do
assert(Schizo.uppercase("foo bar baz whee") == "foo BAR BAZ WHEE")
end
test "unvowel doesnt change the first word" do
assert(Schizo.unvowel("foo") === "foo")
end
test "unvowel removes the second words vowels" do
assert(Schizo.unvowel("foo bar") == "foo br")
end
test "unvowel removes every other words vowels" do
assert(Schizo.unvowel("foo bar baz whee") == "foo br bz wh")
end
end
and the accompanying code for the above test:
defmodule Schizo do
@moduledoc """
A nice module that lets you upcase or unvowel from every other word in a sentence
"""
@doc """
Uppercase every other word in a sentence, Example:
iex> Schizo.uppercase("you are awesome")
"you ARE AWESOME"
"""
def uppercase(string) do
transformer(string, &upcaser/1)
end
@doc """
Removes vowels from every other word in a sentence. Example:
iex> Schizo.unvowel("you are silly")
"you r slly"
"""
def unvowel(string) do
transformer(string, &unvoweler/1)
end
defp transformer(string, transformation) do
string
|> String.split()
|> Stream.with_index
|> Enum.map(transformation)
|> Enum.join(" ")
end
defp upcaser(input) do
transform(input, &String.upcase/1)
end
defp unvoweler(input) do
transform(input, fn (word) -> Regex.replace(~r/[aeiou]/, word, "") end)
end
defp transform({word, index}, transformation) do
case {word, index} do
{word, 0} -> word
_ -> transformation.(word)
end
end
end