Mix Math
Mix.install([
{:kino, github: "livebook-dev/kino", override: true},
{:kino_lab, "~> 0.1.0-dev", github: "jonatanklosko/kino_lab"},
{:vega_lite, "~> 0.1.4"},
{:kino_vega_lite, "~> 0.1.1"},
{:benchee, "~> 0.1"},
{:ecto, "~> 3.7"},
{:math, "~> 0.7.0"},
{:faker, "~> 0.17.0"},
{:utils, path: "#{__DIR__}/../utils"},
{:tested_cell, git: "https://github.com/BrooklinJazz/tested_cell"}
])
Navigation
Setup
Ensure you type the ea
keyboard shortcut to evaluate all Elixir cells before starting. Alternatively you can evaluate the Elixir cells as you read.
Mix Math
To learn how to create a mix project, you’re going to recreate a familiar exercise of
building a Math
module that can abstract away the operators for adding different data types.
It should be able to handle adding strings
, integers
, floats
, lists
, and maps
.
You’ll use Dialyzer to create type specifications and Credo to ensure code consistency.
You will also use ExUnit and DocTests to create a comprehensive suite of tests and documentation.
After creating the fully functional Math
module, you will document the project with HexDoc.
Create A New Mix Project
Using the command line, create a new project in the projects
folder called math
.
mix new math
Evaluate the cell below to ensure you have correctly created the project.
Utils.feedback(:created_project, "math")
Configure Dialyzer
Add the following to your dependencies in mix.exs
.
{:dialyxir, "~> 1.0", only: [:dev], runtime: false}
Run the following in the command line to ensure Dialyzer is working correctly:
mix deps.get
mix dialyzer
Configure Credo
Add the following to your dependencies in mix.exs
.
{:credo, "~> 1.6", only: [:dev, :test], runtime: false}
Run the following in the command line to ensure Credo is working correctly:
mix deps.get
mix credo
Configure ExDoc
Add the following to your dependencies in mix.exs
.
{:ex_doc, "~> 0.27", only: :dev, runtime: false}
Run the following in the command line to ensure ExDoc
is working correctly.
mix deps.get
mix docs
Then open the generated doc/index.html
to see the project documentation. You will need
to re-run mix docs
if you wish to update the documentation.
Doc Test Cases
In the math.exs
file create a full suite of doctests for the Math
module.
add/2
Create a suite of doc tests to represent the following cases.
- adding two numbers
- adding (concatenate) two strings
- adding two lists
- adding two maps.
For example:
Math.add(1, 1)
2
Math.add("1", "1")
"11"
Math.add([1], [2])
[1, 2]
Math.add(%{1 => 1}, %{2 => 2})
%{1 => 1, 2 => 2}
Comprehensive ExUnit Tests (Reading)
The current doctests are not comprehensive enough to properly test functionality.
For example, you can pass the doc tests by simply returning the same output like so:
# math.exs
@doc ~S"""
Adds two similar data types together.
## Examples
iex> Math.add("1", "1")
"11"
"""
def add(string1, string2) do
"11"
end
This passing test is a false-positive because it would pass even though the implementation is incomplete.
Tests are more comprehensive when there are fewer or no possible implementations that produce false positives.
For example, the following test is more comprehensive but can still produce a false positive.
# math_test.exs
test "add/2 add two numbers" do
assert Math.add(1, 1) == 2
assert Math.add(2, 1) == 3
end
# math.ex
def add(number1, number2) do
if number1 == 1 do
2
else
3
end
end
Randomized values can make your tests more comprehensive.
For example, the following test should ensure that Math.add/2
works correctly with two integers.
# math_test.exs
test "add/2 two random integers" do
number1 = Enum.random(0..99)
number2 = Enum.random(0..99)
assert Math.add(number1, number2) == number1 + number2
end
False Positives
Add the bare-minimum code necessary to make the current doctests pass. Do not write a full solution yet.
For example:
# math.exs
def add(map1, map2) when is_map(map1) and is_map(map2) do
%{1 => 1, 2 => 2}
end
def add(string1, string2) when is_binary(string1) and is_binary(string2) do
"11"
end
def add(list1, list2) when is_list(list1) and is_list(list2) do
[1, 2]
end
def add(number, number) do
2
end
Add Typespecs
Add typespects using @spec
for the add/2
function.
You will need a separate @spec
for each of the following types:
- number
- bitstring
- list
- map
For example:
@spec add(number(), number()) :: number()
def add(number1, number2) do
number1 + number2
end
Run mix dialyzer
in the command line to ensure that your typespecs are correct.
Combative Pairing (Partner Exercise)
add/2
Create a comprehensive ExUnit test for the following test cases:
- adding two integers
- adding two floats
- adding two lists
- adding two maps
Do not implement the code to make the tests pass yet. Once complete, find a partner.
Partner Implementation
Complete the following exercise with a partner. One partner will be the TESTER and the other partner will be the IMPLEMENTER.
This exercise is best completed through LiveShare in Visual Studio Code.
If you do not have a partner, you can complete the exercise being both roles.
Use the test suite created by the TESTER in the previous exercise.
As the IMPLEMENTER, create a false positive implementation that will make the TESTER‘s test suite pass.
The TESTER will rewrite their tests until the IMPLEMENTER cannot think of a way to create a false positive and must write a valid solution.
Once complete, swap roles and repeat the exercise.
Complete!
To finish this exercise:
-
Ensure your tests all pass.
mix test
- Ensure your Dialyzer check passes.
mix dialyzer
-
Ensure your Credo check passes.
mix credo
-
Ensure you have generated the latest documentation.
mix docs
Then make sure to add your changes to git from the curriculum
folder.
git add .
git commit -m "finish mix math exercise"
git push
Edge Case Tests (Bonus)
It’s important to consider edge cases with your tests. For example, what do we want
to happen when we use the Math
module incorrectly?
iex> Math.add(%{}, [])
** (ArithmeticError) bad argument in arithmetic expression: %{} + []
:erlang.+(%{}, [])
(math 0.1.0) lib/math.ex:40: Math.add/2
> Depending on your implementation, you may get a different error than the above.
ArithmeticError
is not particularly clear. Instead, use assert_raise
to test that we
raise a FunctionClauseError
. Then, write the code necessary to make the test pass.
Commit Your Progress
Run the following in your command line from the project folder to track and save your progress in a Git commit.
$ git add .
$ git commit -m "finish mix math exercise"