Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Mix Math

exercises/mix_math.livemd

Mix Math

Mix.install([
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"},
  {:tested_cell, github: "brooklinjazz/tested_cell"},
  {:utils, path: "#{__DIR__}/../utils"}
])

Navigation

Return Home Report An Issue

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 beta_curriculum folder to track and save your progress in a Git commit.

$ git add .
$ git commit -m "finish mix math exercise"