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

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

Hound を使った Phoenix app の integration test

For browser automation and writing integration tests in Elixir.

Hound を使って Phoenix app の integration test を書きました。 ここで書いたコードは GitHub に push してあります。

mattsan/phoenix_integration_test_sample

プロジェクトを用意

Phoenix app のテストなので、Phoenix プロジェクトを用意します。 プロジェクト名はご自由に。

$ mix phx.new sample
$ cd sample

パッケージを追加する

Hound パッケージを追加します。 mix.exs を編集して、hound の行を追加します。テストでのみ利用するので only: :test のオプションを指定しておきます。

       {:phoenix_html, "~> 2.10"},
       {:phoenix_live_reload, "~> 1.0", only: :dev},
       {:gettext, "~> 0.11"},
-      {:cowboy, "~> 1.0"}
+      {:cowboy, "~> 1.0"},
+      {:hound, "~> 1.0", only: :test}
     ]
   end
 end

編集できたらHound パッケージを取得(ダウンロード)します。

$ mix deps.get

設定する

config/test.exs を編集して Hound の設定を追加します。

テスト時にもサーバが起動するようにします。

config :sample, SampleWeb.Endpoint,
   http: [port: 4001],
-  server: false
+  server: true

ドライバを指定します。ここでは PhantomJS を利用する設定をしています。そのほかのドライバを指定するばあいはこちらを参照してください。

+config :hound, driver: "phantomjs"

test/test_helper.exs を編集してテスト開始前に Hound が起動するようにします。

+Application.ensure_all_started(:hound)
 ExUnit.start()

テストを書く

テストを書きます。今回は test/sample_web/ の下に intagration/ を作り、そこにテストファイルをおくようにしました。

例としてホームページのテスト test/sample_web/intagration/page_index_test.exs を書きます。

  • use Hound.Helpers を追加します
  • hound_session() を追加します。この関数を記述しておくとセッションの管理を自動的に行ってくれます
  • navigate_to/2 でページをリクエストします。詳しくはドキュメントを参照してください
  • find_element/3 で要素を取得します。詳しくはドキュメントを参照してください
  • inner_text/1 で要素のテキストを取得します。詳しくはドキュメント(ry
defmodule SampleWeb.PageIndexTest do
  use SampleWeb.ConnCase
  use Hound.Helpers

  hound_session()

  test "home", %{conn: conn} do
    navigate_to(page_path(conn, :index))

    h2 =
      find_element(:tag, "h2")
      |> inner_text()

    assert h2 == "Welcome to Phoenix!"
  end
end

テストを実行する…前に PhantomJS を起動しておく

PhantomJS を利用するので先に起動しておきます。このばあい、グローバルで実行できる状態でインストールされている必要があります(すぐ後でプロジェクトに含めておく方法を書きます)。 Remote WebDriver mode で利用するのでオプションで指定します。

$ phantomjs --wd

mix test でテストを実行します。今回書いたテストだけを実行するばあいはファイル名を指定して実行します。

$ mix test test/sample_web/intagration/page_index_test.exs 

PhantomJS をパッケージに追加する

PhantomJS はアプリケーションとは別に用意してもよいのですが、プロジェクトの中に閉じておきたいので assets に PhantomJS を追加してそれを利用するようにします。

assets/package.json に PhantomJS を追加します。合わせてスクリプトを定義しておいて簡単に実行できるようにしておきます。

 {
   "repository": {},
   "license": "MIT",
   "scripts": {
     "deploy": "brunch build --production",
-    "watch": "brunch watch --stdin"
+    "watch": "brunch watch --stdin",
+    "phantomjs": "./node_modules/phantomjs-prebuilt/bin/phantomjs --wd"
   },
   "dependencies": {
     "phoenix": "file:../deps/phoenix",
     "phoenix_html": "file:../deps/phoenix_html"
   },
   "devDependencies": {
     "babel-brunch": "6.1.1",
     "brunch": "2.10.9",
     "clean-css-brunch": "2.10.0",
+    "phantomjs-prebuilt": "^2.1.16",
     "uglify-js-brunch": "2.10.0"
   }
 }

インストールします。

$ cd assets
$ npm install

実行するには assets/ に移動して npm run で起動します。

$ npm run phantomjs

PhantomJS をテスト実行時に自動的に起動する、そして終了時に停止する

PhantomJS を別途起動する必要がないように、テストヘルパに PhantomJS の起動と停止を書きます。 これで PhantomJS の起動停止を気にする必要がなくなりますが、

  • テストのたびに起動のオーバーヘッドが発生する
  • 複数のテストを同時には実行できない(それぞれが同じポートを利用しようとするので)

などのデメリットもあります。

test/test_helper.exs を次のように編集します。

TestHelper. setup_phantomjs/0 で PhantomJS を起動します。そのなかでテスト終了時に kill コマンドを実行して PhantomJS のプロセスを停止するコードを System.at_exit/1 にかいておきます。 TestHelper.wait_starting/0 は、PhantomJS の起動が完了したときに標準出力に出力する文字列を待つ関数です。

テスト開始前に PhantomJS が起動するように TestHelper. setup_phantomjs/0 呼び出しを追加します。

defmodule TestHelper do
  @phantomjs_path "./assets/node_modules/phantomjs-prebuilt/bin/phantomjs --wd"

  def setup_phantomjs do
    port = Port.open({:spawn, @phantomjs_path}, [:binary])
    {:os_pid, os_pid} = Port.info(port, :os_pid)

    System.at_exit(fn _ ->
      "kill #{os_pid}"
      |> String.to_charlist()
      |> :os.cmd()
    end)

    wait_starting()
  end

  def wait_starting do
    receive do
      {_port, {:data, output}} ->
        if !String.match?(output, ~r/running on port 8910/) do
          IO.puts "Starting PhatomJS failed: #{inspect output}"
          System.halt(1)
        end

      after 1_000 ->
        wait_starting()
    end
  end
end

TestHelper.setup_phantomjs()
Application.ensure_all_started(:hound)
ExUnit.start()

いつか読むはずっと読まない:もののけのみやつこ

初版が発売されたとき、友人と散々プレイしたカードゲーム。

今年は初版発売から 30 年だそうです。

MONSTER MAKER モンスターメーカー

MONSTER MAKER モンスターメーカー