One of the best features of elixir language are the tools that ships with it. One of then Is Mix. Mix is build tool that helps create, setup and run an elixir project. To demonstrate how mix do all this, let's create a simple project for a popular Kata named FizzBuzz.
The rules of FizzBuzz are:
The program must output a line from 1 to a given number with three exceptions:
- If the value of that line is divided by 3 then the output must be Fizz.
- If the value of that line is divided by 5 then the output must be Buzz.
- If the value of that line is divided by 3 and by 5 the output of that line must be FizzBuzz.
Now that we have all the rules. Let's start by creating a new app. The fist step is to type mix new fizz_buzz
at the terminal we will see this output:
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/fizz_buzz.ex
* creating test
* creating test/test_helper.exs
* creating test/fizz_buzz_test.exs
Your mix project was created successfully.
You can use mix to compile it, test it, and more:
cd fizz_buzz
mix test
Run `mix help` for more commands.
Our app is now created, we can now enter at the fizz_buzz folder and see the file structure for a typical elixir app. The three most important files are the mix.exs
, test/fizz_buzz_test.exs
and lib/fizz_buzz.ex
. mix.exs
is the file that configures your project. It has 2 main public functions application
and project
. The project
function must return a list with the project information just as the app name, the app version and its dependencies. The application
function is where you put the information that will be used to generate an .app
file.
# mix.exs
defmodule FizzBuzz.Mixfile do
use Mix.Project
def project do
[app: :fizz_buzz,
version: "0.0.1",
elixir: "~> 1.1.0-dev",
deps: deps]
end
def application do
[applications: [:logger]]
end
defp deps do
[]
end
end
The lib/fizz_buzz.ex
is the file generated that is intended to be the main file of our app. It is created by default and only has an empty module to be implemented.
# fizz_buzz.ex
defmodule FizzBuzz do
end
The test/fizz_buzz_test.exs
file is where you put your tests. In fact every file that lies inside the test
will be executed as a test file. By default when we create a project, mix creates a simple test that tests if 1 + 1 is equals 2.
# fizz_buzz_test.exs
defmodule FizzBuzzTest do
use ExUnit.Case
test "the truth" do
assert 1 + 1 == 2
end
end
You can run this test with mix test
command at the root of the project. We will see that our test will be pass. Try changing the value of the sum from 2 to 3 and see what happens.
Let's start to implement our FizzBuzz by writing some tests. First Let's create a test. Guess the first thing that we got to do is to create the first test from 1
to 3
where it will output this:
1
2
Fizz
And here is the code of the test.
test "compute values from 1 to 3" do
expected_result = """
1
2
Fizz
"""
assert FizzBuzz.compute(3) == expected_result
end
If we try to run the tests again we will see an error. Saying that the function compute/0
is not defined. Let's go and implement it.
# fizz_buzz.ex
defmodule FizzBuzz do
def compute(number) do
"""
1
2
Fizz
"""
end
end
Aha! I bet you already realized what I did. In elixir the last expressions is the return value of the function. So what our function does is just returning the expected output for our test. if we run mix test
we will see that everything is passing and we are very happy. This is part of the TDD philosophy, first we write the test and it should fail, then we write a small piece of software so the test can pass. Then finally we refactor the code. We already known what we want so we can go straight to our code.
defmodule FizzBuzz do
def compute(number) when number > 1 do
compute(number - 1) <> output_line(number)
end
def compute(1) do
output_line(1)
end
defp output_line(number) do
if rem(number, 3) == 0 do
"Fizz\n"
else
"#{number}\n"
end
end
end
We can see that at our function compute/1
definition we are defining some rules for two specific cases. In Elixir what makes a function signature is its names and its arity (number of values you can pass to a function). But we can define some rules for a function to execute, just like we did with when
. Note that we are using recursion instead of using a conventional loop, it's a common thing at functional languages to use recursion calls. Run mix test
and our test pass. Let's write the test to print until 5 so we can see the Buzz word at screen.
test "compute values from 1 to 5" do
expected_result = """
1
2
Fizz
4
Buzz
"""
assert FizzBuzz.compute(5) == expected_result
end
If we try to run this test it will fail. This is because we have not implemented a rule of what happens when the number is divided by 5. Since we have refactored our code before, we can just modify our output_line/1
function and just add some simple rule there and everything will pass! yey!
defp output_line(number) do
if rem(number, 3) == 0 do
"Fizz\n"
else
if rem(number, 5) == 0 do
"Buzz\n"
else
"#{number}\n"
end
end
end
Now that everything is passing we can try to refactor that code. I really don't like the inner block thing that we are producing for every new rule we must explicitly create a new if block and close it. Let's try another conditional that is more clear to use.
defp output_line(number) do
cond do
rem(number, 3) == 0 ->
"Fizz\n"
rem(number, 5) == 0 ->
"Buzz\n"
true ->
"#{number}\n"
end
end
Okay now just one more case we must cover that is if the number is divided by 3
and 5
at the same time. First let's write our test case until 15
run the tests and see it failing.
test "compute values from 1 to 15" do
expected_result = "1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n"
assert FizzBuzz.compute(15) == expected_result
end
It fails as expected let's write the last rule first so we can make sure it has a higher precedent.
defp output_line(number) do
cond do
rem(number, 3) == 0 && rem(number, 5) == 0 ->
"FizzBuzz\n"
rem(number, 3) == 0 ->
"Fizz\n"
rem(number, 5) == 0 ->
"Buzz\n"
true ->
"#{number}\n"
end
end
And our tests are passing again. By this time we must already solve the problem. You can try by creating a string that goes from 1 to 100 and will see that our tests will pass. At this point we have our mix application completely done. We can run test on it. Now let's try to run our code at the console. If we type iex -S mix
we will enter at an interactive Elixir console and we will have access to all our code thought here. Let's try by typing FizzBuzz.compute(5)
. The output of our FizzBuzz challenge is done there. And you can try any other value that you want.
This was a quick introduction to mix, a build tool that ships with Elixir. There's a lot to go from here. But having a such nice tool to create a project with a full functional test suite is already great.