エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

「どう書く」をElixirで書いたときのテストを簡単にする

Elixir でコールバックってどうやって実装しているのだろうと調べてみたら、マクロで実現されていました。なるほど。 その昔 C 言語でマクロを使ってコードを生成していたのを思い出した。

そんなわけで。マクロを使って「どう書く」のテストを実行するモジュールを書いてみた。

Elixir のドキュメントにテストの説明があったので、だいたいそのまま。

テストランナ

結果判定をする通常の関数 judgeuse されたときに実行される __using__ マクロ、個々のテストを定義する test マクロ、コンパイル時に実行される __before_compile__ マクロからなっています。

__before_compile__ が実行されるようにするには、@before_compile で指定する必要があるようです。

defmodule Doukaku.TestRunner do
  def judge(_, expected, expected) do
    IO.puts "\x1b[32mpassed\x1b[0m"
  end

  def judge(input, expected, actual) do
    IO.puts "\x1b[31mfailed  input: #{input}, expected: #{expected}, actual: #{actual}\x1b[0m"
  end

  defmacro __using__(_) do
    quote do
      import Doukaku.TestRunner
      @tests []
      @before_compile Doukaku.TestRunner
    end
  end

  defmacro test(input, expected) do
    quote do
      @tests [{unquote(input), unquote(expected)}|@tests]
    end
  end

  defmacro __before_compile__(_) do
    quote do
      def run do
        @tests
        |> Enum.reverse()
        |> Enum.each(fn {input, expected} -> judge(input, expected, solve(input)) end)
      end
    end
  end
end

どう書いたコードの例

英字の小文字だったばあいに大文字に変換するとかそういうの。

テストを定義する部分は CapitarlTest などに分離するのがきれいなのかもしれません。

defmodule Capitarl do
  use Doukaku.TestRunner

  def to_capital(c) do
    c - 32
  end

  def solve(input) do
    input
    |> String.to_charlist
    |> Enum.map(&to_capital/1)
    |> List.to_string
  end

  test("a", "A")
  test("ab", "AB")
  test("Ab", "AB")
end

Capitarl.run

実行

本当はパッケージにまとめておくのが正解の気がしますが、今回はファイル 2 つだけですまそうと。

テストランナをコンパイルします。

$ elixirc doukaku/test_runner.ex

どう書いたコードを実行します。

$ elixir capital.exs 
passed
passed
failed  input: Ab, expected: AB, actual: !B

いつか読むはずっと読まない:どこかで聞いた気がする

懐かしいとはちょっとちがう、どこかで聞いた気がする、という感じが集まった一冊。

一番にそれを感じたのが最後の防火水槽のくだり。

里山奇談

里山奇談

なお、「今日の早川さん」の第 4 集が控えているらしい。

今日の早川さん (早川書房)

今日の早川さん (早川書房)