Elixir basics · Lesson 24 (open contents)
01 · Let the code speak02 · Meet six kinds of values03 · Put values together04 · Build a new list05 · Make the shapes match06 · Choose with case07 · Parse a number safely08 · Unpack &1 and the pipe09 · Put code in a project10 · Ask precise true-or-false questions11 · Read and update nested data12 · Count visible text and bytes13 · Let function clauses choose14 · Solve a list twice15 · Choose the smallest clear control form16 · Give domain data a shape17 · Put one tested module in Mix18 · Make success and failure predictable19 · Read and write one real file20 · Take only the data you need21 · Give different data one shared action22 · Write a callback contract23 · Write down the data contract24 · Test behavior and boundaries25 · Turn a project into a command26 · Write the project promise27 · Parse one real line28 · Keep the public API small29 · Make the promise executable30 · Package the edge and leave clues31 · Prove the project is done
ELIXIR · INTERMEDIATE · LESSON 2425 minutes

Test behavior and boundaries

Use setup data, doctests, focused assertions, and async-safe tests.

01 · Start with the whole example

Run this first

Run it once, change one input, and compare the new result.

Elixir
# lib/score.ex
defmodule Score do
  def pass?(score) when is_integer(score) and score in 60..100, do: true
  def pass?(_score), do: false

  def check(score) when is_integer(score) and score in 0..100,
    do: {:ok, score}

  def check(_score), do: {:error, :out_of_range}
end

# test/score_test.exs
defmodule ScoreTest do
  use ExUnit.Case, async: true

  test "pass boundary" do
    assert Score.pass?(60)
    refute Score.pass?(59)
  end
end
Check the result first
  • mix test reports one passing test containing two boundary assertions.
02 · Take the code apart

Read from the first line down

  1. Read use ExUnit.Case, async: true: Allow independent tests to run concurrently.
  2. Read setup: Return context data for each test.
  3. Read doctest Module: Run documented IEx examples as tests.
03 · Meet the new symbols

Symbols are not secret signs

use ExUnit.Case, async: true

Allow independent tests to run concurrently.

setup

Return context data for each test.

doctest Module

Run documented IEx examples as tests.

04 · Ideas inside the code

Match each name to its meaning

01

test boundary

Boundary cases sit where behavior changes, such as 59 versus 60.

02

setup

A setup callback prepares fresh data for each test.

03

doctest

Doctest runs examples written in module documentation.

05 · Make the idea clear

Why these forms are useful

Run the example first. Predict one result, then change one input and run it again.

Boundary cases sit where behavior changes, such as 59 versus 60.

A setup callback prepares fresh data for each test.

06 · Change it yourself

Close the answer and try

Add a test that rejects a negative score.

Practice starting point
test "negative score" do
  assert Score.check(-1) == ____
end

Target result: Expect {:error, :out_of_range}.

Stuck? Read one hint

Use the tagged error returned by the public API.

After you run it, see one answer
One answer
test "negative score" do
  assert Score.check(-1) == {:error, :out_of_range}
end
Think about it: When is `async: true` unsafe?

Avoid it when tests share mutable external state such as one database record or global process.

Take with you

Remember these three lines

  1. 1

    Test behavior, not private steps.

  2. 2

    Place tests on both sides of boundaries.

  3. 3

    Share examples through setup or doctests only when they stay clear.

Lesson completeRun the code once, then mark this lesson as done.