1-Mix簡介
=========
在這份指導手冊中,我們將學習創建一個完整的Elixir應用程序,以及監督樹、配置、測試等高級概念。
這個程序是一個分布式的鍵-值存儲數據庫。我們會把鍵-值存儲在“桶”中,分布存儲到多個節點。
我們還會創建一個簡單的客戶端工具,可以連接任意一個節點,并且能夠發送類似以下的命令:
```
CREATE shopping
OK
PUT shopping milk 1
OK
PUT shopping eggs 3
OK
GET shopping milk
1
OK
DELETE shopping eggs
OK
```
為了編寫這個程序,我們將主要用到以下三個工具:
* __OTP(Open Telecom Platform)__
OTP是一個隨Erlang發布的代碼庫集合。Erlang開發者使用OTP來創建健壯的、高度容錯的程序。在本章中,我們將來探索與Elixir整合在一起的OTP,包括監督樹、事件管理等等;
* __Mix__
Mix是隨Elixir發布的構建工具,用來創建、編譯、測試你的應用程序,還可以用來管理依賴等;
* __ExUnit__
ExUnit是一個隨Elixir發布的單元測試工具
本章會用Mix來創建我們第一個工程,探索OTP、Mix以及ExUnit的各種特性。
>注意:
本手冊需要Elixir v0.15.0(1.2.0發布后,這里被改為1.2.0了)或以上。
你可以使用命令```elixir -v```查看版本。
如果需要,可以參考《Elixir入門手冊》第一章節內容安裝最新的版本。
如果發現任何錯誤,請開issue或者發pull request。
## 1.1-第一個應用程序
當你安裝Elixir時,你不僅得到了```elixir```,```elixirc```和```iex```命令,
還得到一個可執行的Elixir腳本叫做```mix```。
從命令行輸入```mix new```命令來創建我們的第一個工程。
我們需要傳遞工程名稱作為參數(在這里,比如叫做 ```kv```),
然后告訴mix我們的主模塊的名字是全大寫的```KV```。
否則按照默認,mix會創建一個主模塊,名字是第一個字母大寫的工程名稱(```Kv```)。
因為K和V的含義在我們的程序中上是平等關系,所以最好是都用大寫:
```sh
$ mix new kv --module KV
```
Mix將創建一個文件夾名叫```kv```,里面有一些文件:
```
* creating README.md
* 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
```
現在簡單看看這些創建的文件。
>注意:
Mix是一個Elixir可執行腳本。這意味著,你要想用mix為名直接調用它,
需要提前將Elixir目錄放進系統的環境變量中。否則,你需要使用elixir來執行mix:
```sh
$ bin/elixir bin/mix new kv --module KV
```
你也可以用```-S```選項來執行elixir,它不管你有沒有把mix的目錄加入環境變量:
```sh
$ bin/elixir -S mix new kv --module KV
```
## 1.2-工程的編譯
一個名叫```mix.exs```的文件會被自動創建在工程目錄中。
它的主要作用是配置你的工程。它的內容如下(略去代碼中的注釋):
```elixir
defmodule KV.Mixfile do
use Mix.Project
def project do
[app: :kv,
version: "0.0.1",
elixir: "~> 1.2",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps]
end
def application do
[applications: [:logger]]
end
defp deps do
[]
end
end
```
我們的```mix.exs```定義了兩個公共函數:
一個是```project```,它返回工程的配置信息,如工程名稱和版本;
另一個是```application```,它用來生成應用程序文件。
還有一個私有函數叫做```deps```,它被```project```函數調用,里面定義了工程的依賴。
不一定非要把```deps```定義為一個獨立的函數,但是這樣做可以使工程的配置文件看起來整潔美觀。
Mix還生成了文件```lib/kv.ex```,其內容是個簡單的模塊定義:
```elixir
defmodule KV do
end
```
以上這個結構就足以編譯我們的工程了:
```sh
$ cd kv
$ mix compile
```
將生成:
```
Compiled lib/kv.ex
Generated kv app
Consolidated List.Chars
Consolidated Collectable
Consolidated String.Chars
Consolidated Enumerable
Consolidated IEx.Info
Consolidated Inspect
```
注意文件```lib/kv.ex```被編譯,生成了程序manifest文件:```kv.app```
及一些 _協議(參考入門手冊)_。根據```mix.exs```的配置,所有編譯產出被放在```_build```目錄中。
一旦工程被編譯成功,便可以從工程目錄啟動一個```iex```會話:
```sh
$ iex -S mix
```
## 1.3-執行測試
Mix還生成了合適的文件結構,來測試我們的工程。Mix工程一般沿用一些命名規則:
在```test```目錄中,測試文件一般以```<filename>_test.exs```模式命名。
每一個```<filename>```對應一個```lib```目錄中的文件名。
根據這個命名規則,我們已經有了測試```lib/kv.ex```所需的```test/kv_test.exs```文件。
只是目前它幾乎什么也沒做:
```elixir
defmodule KVTest do
use ExUnit.Case
doctest KV
test "the truth" do
assert 1 + 1 == 2
end
end
```
需要注意幾點:
1. 測試文件使用的擴展名(.exs)即Elixir腳本文件。這很方便,我們不用在跑測試前還編譯一次。
2. 我們定義了一個測試模塊名為```KVTest```,用```ExUnit.Case```來注入測試API,
并使用宏```test/2```定義了一個簡單的測試;
Mix還生成了一個文件名叫```test/test_helper.exs```,它負責設置測試框架:
```
ExUnit.start()
```
每次Mix執行測試時,這個文件將自動被導入(required)。執行測試,使用命令```mix test```:
```
Compiled lib/kv.ex
Generated kv app
[...]
.
Finished in 0.04 seconds (0.04s on load, 0.00s on tests)
1 tests, 0 failures
Randomized with seed 540224
```
注意,每次運行```mix test```時,Mix會重新編譯源文件,生成新的應用程序。
這是因為Mix支持多套執行環境,我們稍后章節會詳細介紹。
另外,ExUnit為每一個成功的測試結果打印一個點,它還會自動隨機安排測試順序。
讓我們把測試改成失敗看看會發生啥。修改```test/kv_test.exs```里面的斷言,改成:
```elixir
assert 1 + 1 == 3
```
現在再次運行```mix test```(注意這次沒有編譯行為發生):
```
1) test the truth (KVTest)
test/kv_test.exs:5
Assertion with == failed
code: 1 + 1 == 3
lhs: 2
rhs: 3
stacktrace:
test/kv_test.exs:6
Finished in 0.05 seconds (0.05s on load, 0.00s on tests)
1 tests, 1 failures
```
ExUnit會為每個失敗的測試結果打印一個詳細的報告。其內容包含了測試名稱,失敗的代碼,
失敗斷言中```==```號的左值(lhs)和右值(rhs)。
在錯誤提示的第二行(測試名稱下面那行),是該測試的代碼位置。
將這個位置作為參數給```mix test```命令,則將僅執行該條測試:
```
$ mix test test/kv_test.exs:5
```
這個十分有用是吧。
最后是關于錯誤的追蹤棧信息,給出關于測試的額外信息。
包括測試失敗的地方,還有原文件中產生失敗的具體位置等。
## 1.4-環境
Mix支持“環境”的概念。它允許開發者為某些場景定義不同的編譯等動作。
默認地,Mix理解三種環境:
* ```:dev``` - Mix任務的默認執行環境(如編譯等操作)
* ```:test``` - ```mix test```使用的環境
* ```:prod``` - 用來將應用程序發布到產品環境
環境配置只對當前工程有效。我們之后會看到,向工程中添加的依賴默認在```:prod```環境下工作。
可以通過訪問```mix.exs```工程配置文件中的```Mix.env```函數定義不同的環境配置,
它會以原子形式返回當前的環境。
比如我們用之于```:build_embedded```和```:start_permanent:```這兩個選項:
```elixir
def project do
[...,
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
...]
end
```
上面代碼的含義就是程序在```:prod```環境中運行的話,則使用那兩個選項。
當你編譯代碼的時候,Elixir把編譯產出都置于```_build```目錄。
但是,有些時候Elixir為了避免一些不必要的復制操作,
會在```_build```目錄中創建一些鏈接指向特定文件而不是copy。
當```:build_embedded```選項被設置為true時可以制止這種行為,
從而在```_build```目錄中提供執行程序所需的所有文件。
類似地,當```:start_permanent```選項設置為true的時候,程序會以“Permanent模式”執行。
意思是如果你的程序的監督樹掛掉,Erlang虛擬機也會掛掉。
注意在:dev和:test環境中,我們可能不需要這樣的行為。
因為在這些環境中,為了troubleshooting等目的,需要保持虛擬機持續運行。
Mix默認使用```:dev```環境,除非在執行測試時需要用到```:test```環境。
環境可以隨時更改:
```
$ MIX_ENV=prod mix compile
```
或在Windows上:
```
> set /a "MIX_ENV=prod" && mix compile
```
## 1.5-探索
關于Mix,內容還有很多,我們在編寫這個工程的過程中還會陸續接觸到一些。
詳細信息可以參考[Mix的文檔](http://elixir-lang.org/docs/stable/mix)。
記住,你可以使用mix的幫助信息來幫助理解一些任務的操作方法,如:
```
$ mix help TASK
```