ExUnitの`assert`でパターンマッチを用いて複雑なデータ構造をテストする


Elixirの特徴のひとつとして、柔軟なパターンマッチが挙げられます。本記事では、ExUnitを用いたテストにおいて、複雑なデータ構造をパターンマッチを用いて効率的にテストする方法を見ていきます1

ExUnitのassertについて

ExUnit.Assertionsのassert/1のマニュアルにもある通り、assertではtruthy/falsyをテストするだけでなく、パターンマッチもテストできます。

Similarly, if a match expression is given, it will report any failure in terms of that match. Given

assert [1] = [2]

you'll see:

match (=) failed
code:  assert [1] = [2]
left: [1]
right: [2]

これを使って、複雑なデータ構造をテストしてみます。

複雑なデータ構造のテスト

たとえば、SlackのAPIで取得できるような、ユーザを表すデータ構造をテストしたいとします。以下は、SlackのAPIドキュメントを参考にして例を作りました。

ExUnit.start()

defmodule UserTest do
  use ExUnit.Case, async: true

  setup_all do
    %{
      id: 123_456,
      name: "あんちぽ",
      profile: %{
        real_name: "栗林健太郎",
        image: %{
          original: "https://0.gravatar.com/avatar/23f4d5d797a91b6d17d627b90b5a42d9?s=1000",
          thumbnail: "https://0.gravatar.com/avatar/23f4d5d797a91b6d17d627b90b5a42d9?s=24"
        }
      }
    }
  end

  test "`user`のデータ構造をテストする", user do
    assert %{id: _, name: "あんちぽ", profile: profile} = user
    assert %{real_name: "栗林健太郎", image: image} = profile
    assert %{original: _, thumbnail: _} = image
  end
end

setup_allにあるのは、テストしたいデータ構造です。その下のtestマクロ内で、実際のテストを行っています。テストしたいデータは、APIから返ってきたJSONをElixirのマップにデコードしたものだとおもってください。

このデータは三階層になっています。このような深いデータ構造をテストしようと思うと、階層ひとつひとつをマップのキーを使って参照したり、別の変数に1行ずつ束縛したりしながらテストしていくみたいな感じにもなりそうです。

上記のコードから、アサーションの部分のみを抜粋します。

test "`user`のデータ構造をテストする", user do
  assert %{id: _, name: "あんちぽ", profile: profile} = user
  assert %{real_name: "栗林健太郎", image: image} = profile
  assert %{original: _, thumbnail: _} = image
end

こうやって書いていくと、パターンマッチにより構造をテストしつつ、下位の階層を変数に束縛して次のアサーションに用いるということで、少ない記述でとてもわかりやすく複雑なデータ構造のテストが実現できていますね。

おわりに

本記事で書いた内容は、Elixirに慣れたひとや、関数型言語的なパターンマッチに詳しいひとからすると当たり前かもしれませんが、個人的に感心した内容だったので、書いておきました。


  1. この記事で書いている方法は、pelemay/analyzer_test.exs at master · zeam-vm/pelemayのコードを見ていて、そんな書き方があるのかと知りました。