Elixir单元测试 #

一、ExUnit概述 #

1.1 什么是ExUnit #

ExUnit是Elixir内置的测试框架,提供:

  • 测试组织
  • 断言函数
  • 测试辅助
  • 测试报告

1.2 测试结构 #

text
test/
├── test_helper.exs
├── my_app_test.exs
└── my_app/
    ├── user_test.exs
    └── post_test.exs

二、基本测试 #

2.1 测试定义 #

elixir
defmodule MyApp.UserTest do
  use ExUnit.Case

  test "greets the world" do
    assert 1 + 1 == 2
  end

  test "name is not empty" do
    user = %User{name: "Alice"}
    assert user.name != ""
  end
end

2.2 运行测试 #

bash
mix test
mix test test/my_app/user_test.exs
mix test test/my_app/user_test.exs:10

三、断言 #

3.1 基本断言 #

elixir
assert 1 + 1 == 2
refute 1 + 1 == 3

assert true
refute false

3.2 异常断言 #

elixir
assert_raise ArithmeticError, fn ->
  1 / 0
end

assert_raise RuntimeError, "error message", fn ->
  raise "error message"
end

3.3 模式匹配断言 #

elixir
assert {:ok, result} = some_function()
assert %User{name: "Alice"} = user

3.4 近似断言 #

elixir
assert_in_delta 1.1, 1.2, 0.2

四、测试组织 #

4.1 describe #

elixir
defmodule MyApp.UserTest do
  use ExUnit.Case

  describe "create/1" do
    test "creates a user with valid params" do
      assert {:ok, _user} = Accounts.create_user(%{name: "Alice"})
    end

    test "returns error with invalid params" do
      assert {:error, _changeset} = Accounts.create_user(%{})
    end
  end

  describe "update/2" do
    test "updates user name" do
      user = user_fixture()
      assert {:ok, user} = Accounts.update_user(user, %{name: "Bob"})
      assert user.name == "Bob"
    end
  end
end

4.2 setup #

elixir
defmodule MyApp.UserTest do
  use ExUnit.Case

  setup do
    user = %User{name: "Alice"}
    {:ok, user: user}
  end

  test "user has name", %{user: user} do
    assert user.name == "Alice"
  end
end

4.3 setup_all #

elixir
setup_all do
  Application.ensure_all_started(:my_app)
  :ok
end

4.4 on_exit #

elixir
setup do
  pid = start_server()
  on_exit(fn -> stop_server(pid) end)
  {:ok, server: pid}
end

五、测试标签 #

5.1 标记测试 #

elixir
@tag :slow
test "slow operation" do
  :timer.sleep(1000)
  assert true
end

@tag :wip
test "work in progress" do
  assert false
end

5.2 过滤测试 #

bash
mix test --exclude slow
mix test --only slow
mix test --only wip

5.3 配置默认标签 #

elixir
ExUnit.start(exclude: [:slow, :wip])

六、测试辅助 #

6.1 测试夹具 #

elixir
defmodule MyApp.Fixtures do
  def user_fixture(attrs \\ %{}) do
    {:ok, user} = MyApp.Accounts.create_user(Map.merge(%{
      name: "Alice",
      email: "alice@example.com"
    }, attrs))

    user
  end
end

6.2 使用夹具 #

elixir
defmodule MyApp.UserTest do
  use ExUnit.Case
  import MyApp.Fixtures

  test "create user" do
    user = user_fixture()
    assert user.name == "Alice"
  end
end

七、Mock #

7.1 使用mox #

elixir
defmodule MyApp.HTTPClient do
  @callback get(String.t()) :: {:ok, String.t()} | {:error, term()}
end

defmodule MyApp.HTTPClientMock do
  @behaviour MyApp.HTTPClient

  def get(_url), do: {:ok, "response"}
end

7.2 配置mox #

elixir
Mox.defmock(MyApp.HTTPClientMock, for: MyApp.HTTPClient)

Application.put_env(:my_app, :http_client, MyApp.HTTPClientMock)

7.3 使用Mock #

elixir
import Mox

setup :verify_on_exit!

test "fetches data" do
  expect(MyApp.HTTPClientMock, :get, fn "http://example.com" ->
    {:ok, "data"}
  end)

  assert {:ok, "data"} = MyApp.Service.fetch()
end

八、测试GenServer #

8.1 测试GenServer #

elixir
defmodule MyApp.CounterTest do
  use ExUnit.Case

  setup do
    {:ok, pid} = Counter.start_link(0)
    {:ok, counter: pid}
  end

  test "increments counter", %{counter: counter} do
    Counter.increment(counter)
    assert Counter.get(counter) == 1
  end
end

九、总结 #

本章学习了:

特性 用途
test 定义测试
assert 断言
describe 组织测试
setup 测试准备
@tag 测试标签

准备好学习测试进阶了吗?让我们进入下一章。

最后更新:2026-03-27