MixとOTP 01: Mixを使ってみる
Tags: elixir, webdev, tutorial, programming
Published: 2019-03-05 01:15:34

---
title: MixとOTP 01: Mixを使ってみる
published: true
description: Elixir公式サイトの許諾を得て「Introduction to Mix」の解説にもとづき、加筆補正を加えて、ElixirのビルドツールMixについてかいつまんでご説明します。
tags: #elixir, #webdev, #tutorial, #programming
---

本稿はElixir公式サイトの許諾を得て「[Introduction to Mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html)」の解説にもとづき、加筆補正を加えて、ElixirのビルドツールMixについてかいつまんでご説明します。

Elixirでアプリケーションをつくるためには、つぎの3つのツールを使います。

- **OTP** (Open Telecom Platform): Erlangに備わっている一式のライブラリです。Erlang開発者はOTPを使って、堅牢で耐障害性に優れたアプリケーションが構築できます(「System Architecture」「[1 Introduction](http://erlang.org/doc/system_architecture_intro/sys_arch_intro.html)」参照)。本稿では、OTPとElixirがいくつの面で統合されているか、監視ツリーやイベントマネージャなどを含めて確かめます。
- [**Mix**](https://hexdocs.pm/mix/): Elixirのビルドツールです。アプリケーションをつくってコンパイルし、テストしたり、依存関係を管理するなどのタスクに用います。
- [**ExUnit**](https://hexdocs.pm/ex_unit/): Elixirに備わるテストユニットにもとづくフレームワークです。

ここでは、Mixを使ってはじめてのプロジェクトをつくり、OTP、Mix、ExUnitのさまざまな機能についてご説明します。


# はじめてのプロジェクト
Elixirをインストールすると、実行ファイルとして`elixir`と`elixirc`および`iex`のほか、`mix`という名前のElixirスクリプトも入ります。コマンドラインツールで`mix new`に続けてプロジェクト名を打ち込めば、その名前のディレクトリにプロジェクトファイルがつくられます(図001)。

つぎのコマンドでは`--module`オプションでモジュール名(`KV`)を決めています。デフォルトではプロジェクト名の頭だけが大文字になる(`Kv`)ためです。

```terminal
$ mix new kv --module KV
```

```terminal
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/kv.ex
* creating test
* creating test/test_helper.exs
* creating test/kv_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd kv
    mix test

Run "mix help" for more commands.
```

#### 図001■Mixでつくられたプロジェクトファイル
![mix_otp_01_001.png](https://thepracticaldev.s3.amazonaws.com/i/60il0fs5uihdkwn0v7a2.png)

# プロジェクトのコンパイル
新たにつくられたプロジェクトフォルダ(`kv`)の中には`mix.exs`というファイルが納められます。プロジェクトを設定するファイルです。つぎのふたつのパブリックな関数が定められています。

- `project`: プロジェクトの設定を返します。
    - プロジェクト名
    - バージョンなど
- `application`: アプリケーションファイルをつくるために用いられます。
    - `mix help compile.app`で説明が示されます。

プライベートな関数`deps`は`project`関数から呼び出して、プロジェクトの依存関係を定めます。別の関数として分けられているのは、プロジェクトの設定をわかりやすくするためです(`mix help deps`で説明が示されます)。

```elixir
defmodule KV.MixProject do
  use Mix.Project

  def project do
    [
      app: :kv,
      version: "0.1.0",
      elixir: "~> 1.6",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
    ]
  end
end
```

`lib`フォルダに納められているのがプロジェクトのファイルです。モジュールには確認用の関数`hello`だけが定められています。

```elixir
defmodule KV do
  @moduledoc """
  Documentation for KV.
  """

  @doc """
  Hello world.

  ## Examples

      iex> KV.hello
      :world

  """
  def hello do
    :world
  end
end
```

コマンドラインツールでプロジェクトのディレクトリに切り替えて、`mix compile`コマンドでコンパイルできます。

```terminal
$ cd kv
$ mix compile
```

```terminal
Compiling 1 file (.ex)
Generated kv app
```

コンパイルが済むと、新たにつくられた`_build`ディレクトリに`mix.exs`の定めにしたがってファイルが納められます(図002)。プロジェクト名に拡張子`.app`のファイル(`kv.app`)がアプリケーションマニフェストです。また、プロトコルは統合(`consolidated`フォルダ)されます(「[Elixir入門 16: プロトコル](https://dev.to/gumi/elixir-16--lif)」の「プロトコルの統合」参照)。

#### 図002Mixでコンパイルしてつくられたファイル
![mix_otp_01_002.png](https://thepracticaldev.s3.amazonaws.com/i/b40khpht8oslhvzrrkgw.png)

プロジェクトがコンパイルされたら、プロジェクトのディレクトリからつぎのコマンドで`iex`セッションをはじめるとモジュールが使えるようになります。

```terminal
$ iex -S mix
```

```elixir
iex> KV.hello
:world
```


# テストを走らせる
Mixはプロジェクトのテストのためのファイルを、`test`フォルダにふたつつくります(図003)。

#### 図003■testフォルダにつくられたファイル
![mix_otp_01_003.png](https://thepracticaldev.s3.amazonaws.com/i/tbt38ude89o4gk14ma0n.png)

ひとつは、プロジェクト名に`_test.exs`が添えられたファイルです(`kv_test.exs`)。`lib`ディレクトリのプロジェクトファイル(`kv.ex`)に対応します。はじめは、簡単な動作確認のコードしか書かれていません。

```elixir
defmodule KVTest do
  use ExUnit.Case
  doctest KV

  test "greets the world" do
    assert KV.hello() == :world
  end
end
```

注意することがふたつあります。

1. テストファイルはElixirスクリプト(`exs`)です。そのため、テストのたびにファイルをコンパイルし直さなくて済みます。
2. テストモジュール(`KVTest`)の中では、[`ExUnit.Case`](https://hexdocs.pm/ex_unit/ExUnit.Case.html)を用いています。これはテスト用のAPIを加えるモジュールです。そして、[`test/3`マクロ](https://hexdocs.pm/ex_unit/ExUnit.Case.html#test/3)を使って、簡単なテストが定められています。

`test`フォルダの中のもうひとつのファイルは、テストのフレームワーク設定を担う`test_helper.exs`です。Mixがテストを実行するたびに必要とされます。

```elixir
ExUnit.start()
```

テストを行うには、コマンドラインツールから、つぎのように`mix test`を打ち込んでください。

```terminal
$ mix test
..

Finished in 0.03 seconds
1 doctest, 1 test, 0 failures

Randomized with seed 71551
```

`mix test`を走らせると、ソースファイルがコンパイルされ、`_build`ディレクトリはつくり直されます。アプリケーションマニフェストが改められるのです。これはMixが複数の環境をサポートしていることにもとづきます。

さらに、`mix test`を入力したあとには、ExUnitがテスト成功の数だけドットを出力します。また、テストは自動的にランダム化されます。

テスト用ファイル(kv_test.exs)のコードはつぎのように書き替えて、テストをわざと失敗させてみましょう。

```elixir
assert KV.hello() == :oops  # :world
```

```terminal
$ mix test


  1) test greets the world (KVTest)
     test/kv_test.exs:5
     Assertion with == failed
     code:  assert KV.hello() == :oops
     left:  :world
     right: :oops
     stacktrace:
       test/kv_test.exs:6: (test)

.

Finished in 0.03 seconds
1 doctest, 1 test, 1 failure

Randomized with seed 828995
```

ExUnitは、失敗するごとに詳しいレポートを出力します。含まれるのはつぎのような情報です。

- テスト名とテストケース
- 失敗したコード(`code`)
- 失敗した式の左辺値(`left`)と右辺値(`right`)

テスト名のつぎの行には、テストの定められた位置が示されます。この行はそのままコピーして`mix test`コマンドに添えれば、Mixがその場所から読み込んで、テストを始めます。プロジェクトを開発するときに、ひとつのテストだけ特定してすぐに繰り返せるので便利です。

```terminal
$ mix test test/kv_test.exs:5
```

スタックトレース(`stacktrace`)は、失敗そのものに関わり、テストの情報と、多くの場合ソースファイルのどこで障害が起こったかを示します。


# 自動コードフォーマット
`.formatter.exs`は、`mix new`コマンドがつくるファイルです。Elixirにはコードフォーマッタが備わっています。コードベースが自動的に統一されたスタイルに整えられるのです。フォーマッタは`mix format`タスクで実行されます。つくられた`.formatter.exs`は、`mix format`がどのファイルをフォーマットするのか決めます。

フォーマッタを試してみるには、`lib`や`test`ディレクトリのファイルのコードに、余分なスペースや改行を加えて保存すればよいでしょう。

```elixir
defmodule KVTest
do

  use ExUnit.Case

  doctest KV

  test "greets the world" do

    assert KV.hello()
    == :world
    
  end
end
```

コマンドラインツールから`mix format`コマンドを打ち込めば、コードのフォーマットは整います。

```terminal
$ mix format
```

```elixir
defmodule KVTest do
  use ExUnit.Case

  doctest KV

  test "greets the world" do
    assert KV.hello() == :world
  end
end
```

多くのエディタはフォーマッタとの統合機能が組み込まれていて、保存するときやキー操作によりファイルがフォーマットできます。Elixirを学ぶときは、この統合機能を使うと、Elixirの構文が理解しやすくなるでしょう。

企業やチームでの開発には、継続的なインテグレーションサーバーに`mix format --check-formatted`を実行することがお勧めです。現在から将来にわたるコードが標準に則ります。

コードフォーマッタについて詳しくは「[mix format](https://hexdocs.pm/mix/Mix.Tasks.Format.html)」をご参照ください。また、「[Elixir v1.6 released](https://elixir-lang.org/blog/2018/01/17/elixir-v1-6-0-released/)」も、このバージョンから備わったコードフォーマッタについて説明しています。


# 環境
Mixには「環境」という考え方があります。この環境によって、開発者はコンパイルなどのオプションを必要に応じて設定できるのです。デフォルトでは、Mixはつぎの3つの環境を認識します。

- `:dev:` Mixがコンパイルなどのタスクをデフォルトで行います。
- `:test:` `mix test`が使います。
- `:prod:` プロジェクトを製品として実行します。

環境は現行のプロジェクトのみに適用されます。プロジェクトに加えた依存関係は、デフォルトでは`:prod`環境で実行されます。

環境ごとのカスタマイズは、`mix.exs`の中で[`Mix.env/0`](https://hexdocs.pm/mix/Mix.html#env/0)関数を呼び出して行います。関数の戻り値は現在の環境を示すアトムです。`project`関数の中で、`start_permanent`オプションに用いられています。

```elixir
defmodule KV.MixProject do

  def project do
    [

      start_permanent: Mix.env() == :prod,

    ]
  end

end
```

`:start_permanent`の値が`true`つまり`:prod`環境のとき、アプリケーションは永続モードで起動します。すると、アプリケーションの監視ツリーが終了したとき、Erlang VMはクラッシュするのです。`:dev`や`:test`環境のときは、VMインスタンスを動かし続けた方が、トラブル対応できて便利でしょう。

Mixのデフォルトは`:dev`環境です。ただし、`test`タスクについては、デフォルトが`:test`環境になります。環境は変数[`MIX_ENV`](https://hexdocs.pm/mix/Mix.html#module-environment-variables)によって変えられます(「[Environments](https://hexdocs.pm/mix/Mix.html#module-environments)」参照)。

```terminal
$ MIX_ENV=prod mix compile
```

Mixはビルドツールです。そのため、製品では使えるとかぎりません。とくに、チームが明確なビルドの手順で進めている場合は使用できない可能性が高いです。したがって、`Mix.env/0`を参照するのは、設定ファイル内の`mix.exs`の中にかぎるべきでしょう。アプリケーションコード(`lib`フォルダ)では決して使わないでください。


# さらに詳しく
さらに詳しくは「[Mix](https://hexdocs.pm/mix/Mix.html)」をお読みください。[mixのソースコード](https://github.com/elixir-lang/elixir/tree/master/lib/mix)もご覧いただけます。

使えるタスクの一覧はコマンド`mix help`で表示されます。

```
$ mix help
mix                   # Runs the default task (current: "mix run")
mix app.start         # Starts all registered apps
mix app.tree          # Prints the application tree
mix archive           # Lists installed archives
mix archive.build     # Archives this project into a .ez file
mix archive.install   # Installs an archive locally
mix archive.uninstall # Uninstalls archives
mix clean             # Deletes generated application files
mix cmd               # Executes the given command
mix compile           # Compiles source files
mix deps              # Lists dependencies and their status
mix deps.clean        # Deletes the given dependencies' files
mix deps.compile      # Compiles dependencies
mix deps.get          # Gets all out of date dependencies
mix deps.tree         # Prints the dependency tree
mix deps.unlock       # Unlocks the given dependencies
mix deps.update       # Updates the given dependencies
mix do                # Executes the tasks separated by comma
mix escript           # Lists installed escripts
mix escript.build     # Builds an escript for the project
mix escript.install   # Installs an escript locally
mix escript.uninstall # Uninstalls escripts
mix format            # Formats the given files/patterns
mix help              # Prints help information for tasks
mix hex               # Prints Hex help information
mix hex.audit         # Shows retired Hex deps for the current project
mix hex.build         # Builds a new package version locally
mix hex.config        # Reads, updates or deletes local Hex config
mix hex.docs          # Fetches or opens documentation of a package
mix hex.info          # Prints Hex information
mix hex.organization  # Manages Hex.pm organizations
mix hex.outdated      # Shows outdated Hex deps for the current project
mix hex.owner         # Manages Hex package ownership
mix hex.publish       # Publishes a new package version
mix hex.repo          # Manages Hex repositories
mix hex.retire        # Retires a package version
mix hex.search        # Searches for package names
mix hex.user          # Manages your Hex user account
mix loadconfig        # Loads and persists the given configuration
mix local             # Lists local tasks
mix local.hex         # Installs Hex locally
mix local.phoenix     # Updates Phoenix locally
mix local.phx         # Updates the Phoenix project generator locally
mix local.public_keys # Manages public keys
mix local.rebar       # Installs Rebar locally
mix new               # Creates a new Elixir project
mix phoenix.new       # Creates a new Phoenix v1.3.2 application
mix phx.new           # Creates a new Phoenix v1.3.2 application
mix phx.new.ecto      # Creates a new Ecto project within an umbrella project
mix phx.new.web       # Creates a new Phoenix web project within an umbrella project
mix profile.cprof     # Profiles the given file or expression with cprof
mix profile.eprof     # Profiles the given file or expression with eprof
mix profile.fprof     # Profiles the given file or expression with fprof
mix run               # Starts and runs the current application
mix test              # Runs a project's tests
mix xref              # Performs cross reference checks
iex -S mix            # Starts IEx and runs the default task
```

さらに、コマンドのあとに知りたいタスク名を添えて、`mix help タスク名`と呼び出せば、タスクごとの詳しい情報も見ることができます(図004)。

#### 図004■タスクの詳細情報を表示する
![mix_otp_01_004.png](https://thepracticaldev.s3.amazonaws.com/i/mcsqsswaapzltvid0jyj.png)


#### MixとOTPもくじ
- MixとOTP 01: Mixを使ってみる
- [MixとOTP 02: Agent](https://dev.to/gumi/mixotp-02-agent-3n9g)
- [MixとOTP 03: GenServer](https://dev.to/gumi/mixotp-03-genserver-2jhn)
- [MixとOTP 04: スーパーバイザーとアプリケーション](https://dev.to/gumi/mix-otp-04-bj0)
- [MixとOTP 05: ダイナミックスーパーバイザー](https://dev.to/gumi/mix-otp-05-l3p)
- [MixとOTP 06: ETS](https://dev.to/gumi/mix-otp-06-ets-ol3)
- [MixとOTP 07: 依存関係とアンブレラプロジェクト](https://dev.to/gumi/mix-otp-07-1p5k)
- [MixとOTP 08: Taskとgen_tcp](https://dev.to/gumi/mix-otp-08-gentcp-5120)
- [MixとOTP 09: DocTestとwithのパターンマッチング](https://dev.to/gumi/mix-otp-09-doctest-with-533g)
- [MixとOTP 10: 分散処理のTaskと設定](https://dev.to/gumi/mix-otp-10-3i31)