檢視原始碼 Mix 簡介

在本指南中,我們將建構一個完整的 Elixir 應用程式,包含自己的監督樹、組態、測試等等。

本指南的要求如下(請參閱 elixir -v

  • Elixir 1.15.0 以上
  • Erlang/OTP 24 以上

應用程式作為一個分散式鍵值儲存。我們將把鍵值對組織成儲存區,並將這些儲存區分散到多個節點。我們還將建構一個簡單的用戶端,讓我們可以連線到任何一個節點,並傳送請求,例如

CREATE shopping
OK

PUT shopping milk 1
OK

PUT shopping eggs 3
OK

GET shopping milk
1
OK

DELETE shopping eggs
OK

為了建構我們的鍵值應用程式,我們將使用三個主要工具

  • OTP (開放電信平台)是一組隨附在 Erlang 中的函式庫。Erlang 開發人員使用 OTP 來建構強健、容錯的應用程式。在本章中,我們將探討 OTP 中有多少面向與 Elixir 整合,包括監督樹、事件管理員等等;

  • Mix 是隨附在 Elixir 中的建構工具,提供建立、編譯、測試應用程式、管理其依賴項等等的任務;

  • ExUnit 是隨附在 Elixir 中的基於測試單元的架構。

在本章中,我們將使用 Mix 建立我們的首個專案,並在進行的過程中探索 OTP、Mix 和 ExUnit 中的不同功能。

原始碼

在本指南中建構的應用程式的最終程式碼在 此儲存庫 中,可用作參考。

本指南是必讀嗎?

本指南並非您 Elixir 之旅中的必讀內容。我們將說明原因。

作為 Elixir 開發人員,您在撰寫 Elixir 程式碼時很可能會使用許多現有架構之一。Phoenix 涵蓋 Web 應用程式,Ecto 與資料庫通訊,您可以使用 Nerves 製作嵌入式軟體,Nx 為機器學習和 AI 專案提供動力,Membrane 組合音訊/視訊處理管線,Broadway 處理資料擷取和處理,以及更多。這些架構處理並行處理、分發和容錯的較低層級細節,因此您作為使用者可以專注於您自己的需求和要求。

另一方面,如果您想了解這些架構建立的基礎,以及為 Elixir 生態系提供動力的抽象,本指南將帶您了解幾個重要的概念。

我們的首個專案

安裝 Elixir 時,除了取得 elixirelixirciex 可執行檔外,您還會取得一個名為 mix 的可執行 Elixir 腳本。

讓我們從命令列呼叫 mix new 來建立我們的首個專案。我們會將專案路徑傳遞為引數(在本例中為 kv)。預設情況下,應用程式名稱和模組名稱會從路徑中擷取。因此,我們告訴 Mix 我們的首要模組應該是全大寫的 KV,而不是預設的 Kv

$ mix new kv --module KV

Mix 會建立一個名為 kv 的目錄,其中包含幾個檔案

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

讓我們簡要檢視這些產生的檔案。

PATH 中的可執行檔

Mix 是 Elixir 可執行檔。這表示為了執行 mix,您需要在 PATH 中同時擁有 mixelixir 可執行檔。這就是安裝 Elixir 時會發生的事。

專案編譯

在我們的新專案資料夾 (kv) 中產生了一個名為 mix.exs 的檔案,其主要責任是設定我們的專案。讓我們來看看它

defmodule KV.MixProject do
  use Mix.Project

  def project do
    [
      app: :kv,
      version: "0.1.0",
      elixir: "~> 1.11",
      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

我們的 mix.exs 定義了兩個公開函式:project,它傳回專案設定,例如專案名稱和版本,以及 application,用於產生應用程式檔案。

還有一個名為 deps 的私有函式,它是由 project 函式呼叫的,用於定義我們的專案相依性。將 deps 定義為一個獨立的函式並非必要,但有助於保持專案設定井然有序。

Mix 也會在 lib/kv.ex 產生一個檔案,其中包含一個名為 hello 的模組,該模組只有一個函式

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

  @doc """
  Hello world.

  ## Examples

      iex> KV.hello()
      :world

  """
  def hello do
    :world
  end
end

此結構足以編譯我們的專案

$ cd kv
$ mix compile

會產生輸出

Compiling 1 file (.ex)
Generated kv app

已編譯 lib/kv.ex 檔案,並產生一個名為 kv.app 的應用程式清單。所有編譯成品都放置在 _build 目錄中,並使用 mix.exs 檔案中定義的選項。

專案編譯完成後,你可以透過執行以下指令在專案中啟動 iex 會話。-S mix 是必要的,用於在互動式外殼程式中載入專案

$ iex -S mix

我們將在這個 kv 專案中工作,進行修改並嘗試從 iex 會話中測試最新的變更。雖然你可以在專案原始程式碼有變更時啟動新的會話,但你也可以使用 recompile 輔助程式從 iex 中重新編譯專案,如下所示

iex> recompile()
Compiling 1 file (.ex)
:ok
iex> recompile()
:noop

如果有任何內容需要編譯,你會看到一些資訊性文字,並取得 :ok 原子,否則函式會保持靜默,並傳回 :noop

執行測試

Mix 也產生了合適的結構,用於執行我們的專案測試。Mix 專案通常遵循慣例,在 test 目錄中為 lib 目錄中的每個檔案建立一個 <filename>_test.exs 檔案。因此,我們已經可以找到一個對應於 lib/kv.ex 檔案的 test/kv_test.exs。它目前沒有做太多事情

defmodule KVTest do
  use ExUnit.Case
  doctest KV

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

請務必注意以下幾件事

  1. 測試檔案是一個 Elixir 指令碼檔案 (.exs)。這很方便,因為我們不需要在執行測試檔案之前編譯它們;

  2. 我們定義一個名為 KVTest 的測試模組,其中我們 use ExUnit.Case 來注入測試 API;

  3. 我們使用其中一個匯入的巨集,ExUnit.DocTest.doctest/1,用來表示 KV 模組包含 doctest(我們將在後面的章節中討論這些);

  4. 我們使用 ExUnit.Case.test/2 巨集來定義一個簡單的測試;

Mix 也產生了一個名為 test/test_helper.exs 的檔案,負責設定測試架構

ExUnit.start()

每次在我們執行測試之前,Mix 都會需要這個檔案。我們可以使用以下方式執行測試

$ mix test
Compiled lib/kv.ex
Generated kv app
..

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

Randomized with seed 540224

請注意,透過執行 mix test,Mix 已編譯原始碼檔案,並再次產生應用程式清單。這會發生是因為 Mix 支援多個環境,我們將在本章節的後面討論。

此外,你可以看到 ExUnit 會為每個成功的測試印出一個點,並自動將測試隨機化。讓我們故意讓測試失敗,看看會發生什麼事。

test/kv_test.exs 中的斷言變更為以下內容

assert KV.hello() == :oops

現在再次執行 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.05 seconds
1 doctest, 1 test, 1 failure

對於每個失敗,ExUnit 會印出一個詳細的報告,其中包含測試名稱和測試案例、失敗的程式碼,以及 == 算子的左側和右側 (RHS) 的值。

在失敗的第二行,就在測試名稱的正下方,是定義測試的位置。如果你完整地複製測試位置,包括檔案和行號,並將其附加到 mix test,Mix 將會載入並只執行那個特定測試

$ mix test test/kv_test.exs:5

這個捷徑在我們建構專案時會非常有用,讓我們能夠透過執行單一測試來快速反覆運算。

最後,堆疊追蹤與失敗本身有關,提供有關測試的資訊,以及通常在原始碼檔案中產生失敗的位置。

自動程式碼格式化

mix new 所產生的檔案之一為 .formatter.exs。Elixir 附帶一個程式碼格式化器,它能根據一致的樣式自動格式化我們的程式碼庫。格式化器會透過 mix format 任務觸發。產生的 .formatter.exs 檔案會設定在執行 mix format 時應格式化的檔案。

若要嘗試使用格式化器,請變更 libtest 目錄中的檔案,加入額外的空白或額外的換行符號,例如 def hello do,然後執行 mix format

大多數編輯器提供與格式化器的內建整合,允許在儲存時或透過所選的鍵盤快速鍵格式化檔案。如果您正在學習 Elixir,編輯器整合會在學習 Elixir 語法時提供有用且快速的回饋。

對於公司和團隊,我們建議開發人員在持續整合伺服器上執行 mix format --check-formatted,確保所有現有和未來的程式碼都遵循標準。

您可以透過查看 格式化任務文件 或閱讀 Elixir v1.6 的發行公告(第一個包含格式化器的版本)來進一步了解程式碼格式化器。

環境

Mix 提供了「環境」的概念。它們允許開發人員針對特定情境自訂編譯和其他選項。預設情況下,Mix 了解三個環境

  • :dev — Mix 任務(例如 compile)預設執行的環境
  • :testmix test 所使用的環境
  • :prod — 您將用於在生產環境中執行專案的環境

環境只適用於目前的專案。正如我們在後續章節中所見,您新增至專案的任何相依性預設會在 :prod 環境中執行。

可以透過存取 mix.exs 檔案中的 Mix.env/0 來執行每一個環境的自訂,它會以原子形式傳回目前的環境。這正是我們在 :start_permanent 選項中所使用的

def project do
  [
    ...,
    start_permanent: Mix.env() == :prod,
    ...
  ]
end

當為 true 時,:start_permanent 選項會在永久模式下啟動您的應用程式,這表示如果您的應用程式監督樹關閉,Erlang VM 會崩潰。請注意,我們不希望在開發和測試中出現這種行為,因為在這些環境中讓 VM 實例持續執行以進行疑難排解很有用。

Mix 會預設為 :dev 環境,但 test 任務會預設為 :test 環境。環境可以透過 MIX_ENV 環境變數來變更

$ MIX_ENV=prod mix compile

或在 Windows 上

> set "MIX_ENV=prod" && mix compile

Mix 在生產環境中

Mix 是一個建置工具,因此預期它不會在生產環境中提供。因此,建議只在組態檔和 mix.exs 內存取 Mix.env/0,絕不要在您的應用程式程式碼 (lib) 中存取。

探索

Mix 還有很多功能,我們會在建置專案時繼續探索它。可以在 Mix 文件 中取得一般概觀,您隨時可以呼叫說明任務來列出所有可用的任務

$ mix help
$ mix help compile

現在讓我們繼續前進,並將第一個模組和函式新增到我們的應用程式中。