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

Hangman - Doctests

hangman/07-doctests.livemd

Hangman - Doctests

Modulos requeridos

defmodule Hangman.State do
  @moduledoc """
  The Hangman game state
  """

  @enforce_keys [:word, :goal]
  defstruct [
    :word,
    :goal,
    misses: MapSet.new(),
    matches: MapSet.new(),
    limit: 5,
    mask: "_",
    completed?: false
  ]

  @type t :: %__MODULE__{
          word: String.t(),
          goal: MapSet.t(),
          misses: MapSet.t(),
          matches: MapSet.t(),
          limit: pos_integer(),
          mask: String.t(),
          completed?: boolean()
        }

  @doc """
  Creates the initial game state
  """
  @spec new(String.t()) :: t()
  def new(word) when is_binary(word) do
    word = String.downcase(word)
    goal = word |> String.codepoints() |> MapSet.new()
    struct!(__MODULE__, word: word, goal: goal)
  end
end

Doctests

Seguramente vienes notando que para documentar módulos usamos @moduledoc y para documentar funciones usamos @doc, la documentación dentro de dichos bloques debe seguir la sintaxis Markdown, una manera de asegurarnos que la documentación está actualizando es proveyendo doctests, como beneficio extra proveemos documentación con ejemplos de código correcto.

Los doctests están especificados por una indentación de cuatro espacios seguidos de iex> prompt dentro de nuestra cadena de documentación. Si un comando se extiende más de una línea podemos usar ...>, tal como lo hace IEx. El resultado esperado debe comenzar en la siguiente línea después de iex> o ...> y termina tanto por un salto de línea o un nuevo prefijo iex>.

defmodule Hangman do
  @moduledoc """
  The famous Hangman game
  """

  alias Hangman.GameLogic
  alias Hangman.State
  alias Hangman.View

  @doc """
  Starts the game
  """
  def start_game do
    word = "hangman"

    word
    |> State.new()
    |> View.format_response()
  end

  @doc """
  Lets the user to take a guess

  ## Examples

      iex> {_word, state} = Hangman.start_game()
      iex> Hangman.take_a_guess("a", state) |> elem(0)
      "_a___a_"

  """
  def take_a_guess(letter, %State{limit: limit, completed?: false} = state) when limit > 0 do
    letter
    |> String.downcase()
    |> GameLogic.guess(state)
    |> View.format_response()
  end

  def take_a_guess(_, state), do: View.format_response(state)
end

Luego, dentro de nuestras pruebas unitarias podemos invocar el macro doctest(module, opts), dicho macro generará casos de pruebas para nuestros doctests. Por ejemplo, en nuestro caso particular sería doctest(Hangman). Para más información acerca de este macro puedes revisar su documentación acá.

El objetivo de los doctests no es reemplazar las pruebas unitarias, si no más bien asegurarse que nuestra documentación está actualizada. Puedes leer más acerca de doctests in la documentación ofrecida para ExUnit.DocTest.

Retos

  • Hasta ahora hemos creado pruebas unitarias para Hangman, ¿puedes crear pruebas unitarias para el resto de los módulos (e.g. Hangman.GameLogic, Hangman.View)?
  • ¿Puedes añadir más doctests donde lo creas conveniente?