2

I have a function in Elixir that produces three random RGB tuples in a list.

defmodule Color do



  @doc """
  Create three random r,g,b colors as a list of three tuples

  ## Examples

      iex> colors = Color.pick_color()
      iex> colors
      [{207, 127, 117}, {219, 121, 237}, {109, 101, 206}]

  """
      def pick_color() do
        color = Enum.map((0..2), fn(x)->
          r = Enum.random(0..255)
          g = Enum.random(0..255)
          b = Enum.random(0..255)
          {r, g, b}
        end)
end

When I run my tests, my doctests fail. The resulting list of tuples are different than what is defined in my doctest. How can I write a doctest for a function that returns a random value?

Alex Fallenstedt
  • 2,040
  • 1
  • 18
  • 34
  • 2
    You can't. You can only doctest pure functions. – Justin Wood Feb 18 '18 at 23:09
  • 2
    @JustinWood you can make `Enum.random` deterministic by setting a seed value (see my answer). :) – Dogbert Feb 19 '18 at 02:46
  • You can (at least now) use pattern matching in doctests too: `iex> [_a, _b, {_r, 121, _b}] = Color.pick_color()`, just don't put an expected return value the line after. Test output will be prettyfied for this in case of an error as well. – dualed Jun 20 '20 at 12:59

2 Answers2

7

You can make the random functions deterministic by setting the seed of :rand's random number generator. This is also how Enum.random/1 is tested in Elixir.

First, open iex and set the seed of the current process to any value:

iex> :rand.seed(:exsplus, {101, 102, 103})

Then, run your function in iex

iex> Color.pick_color()

Now just copy this value into your doctest along with the :rand.seed call. By explicitly setting the seed, you're going to get the same values from the functions in :rand module and Enum.random/1 uses :rand internally.

iex(1)> :rand.seed(:exsplus, {1, 2, 3})
iex(2)> for _ <- 1..10, do: Enum.random(1..10)
[4, 3, 8, 1, 6, 8, 1, 6, 7, 7]
iex(3)> :rand.seed(:exsplus, {1, 2, 3})
iex(4)> for _ <- 1..10, do: Enum.random(1..10)
[4, 3, 8, 1, 6, 8, 1, 6, 7, 7]
iex(5)> :rand.seed(:exsplus, {1, 2, 3})
iex(6)> for _ <- 1..10, do: Enum.random(1..10)
[4, 3, 8, 1, 6, 8, 1, 6, 7, 7]
Dogbert
  • 212,659
  • 41
  • 396
  • 397
1

In order to test a function with doctests, you must be able to predict the output of your function. In this case, you can't predict the output of your function.


However you could test your function with regular tests.

Here is a test that would make sure Color.pick_color() produces a list of 3 tuples, using pattern matching:

test "pick color" do
  [{_, _, _}, {_, _, _}, {_, _, _}] = Color.pick_color()
end

You could also check if each value is between 0 and 255, etc.

Ronan Boiteau
  • 9,608
  • 6
  • 34
  • 56