查看源代码 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 时,除了获得 elixir
、elixirc
和 iex
可执行文件外,您还会获得一个名为 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 中同时拥有mix
和elixir
可执行文件。这就是您安装 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
需要注意以下几点
测试文件是一个 Elixir 脚本文件 (
.exs
)。这很方便,因为我们不需要在运行测试文件之前编译它们;我们定义了一个名为
KVTest
的测试模块,在其中我们use ExUnit.Case
来注入测试 API;我们使用导入的宏之一,
ExUnit.DocTest.doctest/1
,来指示KV
模块包含 doctests(我们将在后面的章节中讨论它们);我们使用
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
运行时应格式化的文件。
为了试用格式化程序,将 lib
或 test
目录中的文件更改为包含额外的空格或换行符,例如 def hello do
,然后运行 mix format
。
大多数编辑器都提供与格式化程序的内置集成,允许在保存时或通过选定的键盘绑定来格式化文件。如果您正在学习 Elixir,编辑器集成会在您学习 Elixir 语法时为您提供有用且快速的反馈。
对于公司和团队,我们建议开发人员在他们的持续集成服务器上运行 mix format --check-formatted
,以确保所有当前和将来的代码都遵循标准。
您可以通过查看 格式化任务文档 或阅读 Elixir v1.6 的发布公告(第一个包含格式化程序的版本)来详细了解代码格式化程序。
环境
Mix 提供了“环境”的概念。它们允许开发人员为特定场景定制编译和其他选项。默认情况下,Mix 理解三种环境
:dev
— Mix 任务(如compile
)默认运行的环境:test
—mix 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
现在让我们继续前进,向我们的应用程序添加第一个模块和函数。