檢視原始碼 ExUnit.DocTest (ExUnit v1.16.2)

從文件擷取測試案例。

文件測試允許我們從 @moduledoc@doc 屬性中找到的程式碼範例產生測試。為此,請在測試案例中呼叫 doctest/1 巨集,並確保您的程式碼範例是根據以下語法和準則編寫的。

語法

每個新的測試都從新的一行開始,並加上 iex> 前綴。多行表達式可以使用 ...>(建議)或 iex> 為後續行加上前綴。

預期結果應從 iex>...> 行之後的那一行開始,並以換行符號結束。

範例

要執行文件測試,請使用 doctest 巨集將它們包含在 ExUnit 案例中

defmodule MyModuleTest do
  use ExUnit.Case, async: true
  doctest MyModule
end

doctest 巨集會迴圈處理在 MyModule 中定義的所有函數和巨集,並解析其文件以搜尋程式碼範例。

一個非常基本的範例是

iex> 1 + 1
2

也支援多行表達式

iex> Enum.map([1, 2, 3], fn x ->
...>   x * 2
...> end)
[2, 4, 6]

可以在同一個測試中檢查多個結果

iex> a = 1
1
iex> a + 1
2

如果您想要讓兩個測試保持分開,請在它們之間加入一個空行

iex> a = 1
1

iex> a + 1 # will fail with a `undefined variable "a"` error
2

如果您不想對文件測試中的每個結果進行斷言,您可以省略結果。您可以在表達式之間這樣做

iex> pid = spawn(fn -> :ok end)
iex> is_pid(pid)
true

以及在結尾

iex> Mod.do_a_call_that_should_not_raise!(...)

當結果是可變的(例如上述範例中的 PID)或當結果是複雜的資料結構,而您不想顯示所有內容,而只想顯示部分內容或某些屬性時,這很有用。

與 IEx 類似,您可以在「提示」中使用數字

iex(1)> [1 + 2,
...(1)>  3]
[3, 3]

這在兩種情況下很有用

  • 能夠參考特定的編號情境
  • 從實際的 IEx 會話中複製貼上範例

您還可以在呼叫 doctest 時選取或略過函數。請參閱下方 :except:only 選項的文件以取得更多資訊。

不透明類型

某些類型的內部結構會保持隱藏,並在檢查時顯示使用者友善的結構。Elixir 中的慣用語法會以 #Name<...> 格式列印這些資料類型。由於這些值會因為開頭的 # 符號而被視為 Elixir 程式碼中的註解,因此在 doctest 中使用時需要特別注意。

想像你有一個包含 DateTime 的映射,並列印為

%{datetime: #DateTime<2023-06-26 09:30:00+09:00 JST Asia/Tokyo>}

如果你嘗試比對這樣的表達式,doctest 將無法編譯。有兩種方法可以解決這個問題。

第一種方法是依賴 doctest 可以比較內部結構,只要它們在根部。因此可以寫成

iex> map = %{datetime: DateTime.from_naive!(~N[2023-06-26T09:30:00], "Asia/Tokyo")}
iex> map.datetime
#DateTime<2023-06-26 09:30:00+09:00 JST Asia/Tokyo>

每當 doctest 以 "#Name<" 開頭時,doctest 將執行字串比對。例如,以上的測試將執行以下比對

inspect(map.datetime) == "#DateTime<2023-06-26 09:30:00+09:00 JST Asia/Tokyo>"

或者,由於 doctest 結果實際上會被評估,你可以將 DateTime 建構表達式作為 doctest 結果

iex> %{datetime: DateTime.from_naive!(~N[2023-06-26T09:30:00], "Asia/Tokyo")}
%{datetime: DateTime.from_naive!(~N[2023-06-26T09:30:00], "Asia/Tokyo")}

這種方法的缺點是 doctest 結果並非使用者在終端機上實際看到的內容。

例外

你也可以展示會引發例外的表達式,例如

iex(1)> raise "some error"
** (RuntimeError) some error

Doctest 會尋找以 ** ( 開頭的行,並據此解析以擷取例外名稱和訊息。例外剖析器會將所有後續行視為例外訊息的一部分,直到出現空行或有新的表達式以 iex> 為字首。因此,只要訊息本身沒有空行,就可以比對多行訊息。

何時不使用 doctest

一般來說,當你的程式碼範例包含副作用時,不建議使用 doctest。例如,如果 doctest 列印到標準輸出,doctest 將不會嘗試擷取輸出。

類似地,doctest 也不會在任何沙箱中執行。因此,在程式碼範例中定義的任何模組都將在整個測試套件執行期間持續存在。

摘要

函數

從模組文件產生測試案例。

從 markdown 檔案產生測試案例。

函數

連結至這個巨集

doctest(module, opts \\ [])

檢視原始碼 (巨集)

從模組文件產生測試案例。

呼叫 doctest(Module) 會為在 module 中找到的所有 doctest 產生測試。

選項

  • :except - 為除了列出的函數以外的所有函數產生測試({function, arity} 組合的清單,和/或 :moduledoc)。

  • :only - 僅為列出的函數產生測試({function, arity} 組合的清單,和/或 :moduledoc)。

  • :import - 當 true 時,可以在不參考模組名稱的情況下測試在模組中定義的函數。然而,當與像 Kernel 這樣的模組發生衝突時,這並不可行。在這些情況下,:import 應設定為 false,而應改用 Module.function(...)

  • :tags - 要套用至所有產生 doctest 的標籤清單。

範例

defmodule MyModuleTest do
  use ExUnit.Case
  doctest MyModule, except: [:moduledoc, trick_fun: 1]
end

這個巨集會自動匯入至每個 ExUnit.Case

連結至這個巨集

doctest_file(file, opts \\ [])

檢視原始碼 (自 1.15.0 起) (巨集)

從 markdown 檔案產生測試案例。

選項

  • :tags - 要套用至所有產生 doctest 的標籤清單。

範例

defmodule ReadmeTest do
  use ExUnit.Case
  doctest_file "README.md"
end

這個巨集會自動匯入至每個 ExUnit.Case