查看源代码 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 支持机器学习和人工智能项目,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 时发生的事情。

项目编译

名为 mix.exs 的文件在我们的新项目文件夹 (kv) 中生成,它的主要职责是配置我们的项目。让我们看一下它

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 的应用程序清单。所有编译工件都使用 mix.exs 文件中定义的选项放置在 _build 目录中。

项目编译完成后,您可以通过运行以下命令在项目中启动一个 iex 会话。 -S mix 是必需的,以便在交互式 shell 中加载项目

$ 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 模块包含 doctests(我们将在后面的章节中讨论它们);

  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.env/0 在您的 mix.exs 文件中进行每个环境的自定义,该方法以原子形式返回当前环境。这就是我们在 :start_permanent 选项中使用的方法。

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

如果为真,则 :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

现在让我们继续前进,向我们的应用程序添加第一个模块和函数。