Doctests

Documenterはデフォルトで、見つけたjldoctestコードブロックを実行し、実際の出力がドクトテストにあるものと一致することを確認します。これにより、ドキュメントの例が古くなったり、不正確になったり、誤解を招いたりするのを避けることができます。パッケージの例はできるだけ多くDocumenterのドクトテストで実行可能であることが推奨されます。makedocs中のドクトテストの失敗はデフォルトで致命的ですが、makedocswarnonly=:doctestを渡すことで警告に変更することができます。

このマニュアルのこのセクションでは、パッケージのドキュメント内のコードブロックに対してドクトテストを有効にする方法について説明します。

"Script" Examples

最初の2種類のドクトテストのうちの1つは、「スクリプト」コードブロックです。この種類のコードブロックをDocumenterが検出できるようにするには、次のフォーマットを使用する必要があります:

```jldoctest
a = 1
b = 2
a + b

# output

3
```

コードブロックの「言語」は jldoctest でなければならず、正確に # output というテキストを含む行を含む必要があります。この行の前のテキストは実行されるスクリプトの内容です。# output の後に表示されるテキストは、スクリプトが include された場合にJulia REPLに表示されるテキスト表現です。特に、行の末尾にあるセミコロン ; は影響を与えません。

"script"を実行して得られた実際の出力は期待される結果と比較され、違いがある場合はmakedocsがエラーを投げて終了します。

# output行の上と下に表示される空白の量は重要ではなく、必要に応じて増減できます。

出力を抑制することは可能であり、output キーワード引数を false に設定することで実現できます。例えば、

```jldoctest; output = false
a = 1
b = 2
a + b

# output

3
```

スクリプトの出力は、期待される結果、すなわち # output セクションと比較されることに注意してください。ただし、# output セクションはレンダリングされたドキュメントでは抑制されます。

REPL Examples

他の種類のドクトテストは、シミュレートされたJulia REPLセッションです。次の形式は、DocumenterによってREPLドクトテストとして検出されます:

```jldoctest
julia> a = 1
1

julia> b = 2;

julia> c = 3;  # comment

julia> a + b + c
6
```

スクリプトドクトテストと同様に、コードブロックの言語は jldoctest に設定する必要があります。コードブロックの行の先頭に julia> が1行以上含まれている場合、それはREPLドクトテストであると見なされます。行の末尾にセミコロン ; があると、Julia REPLと同様に出力が抑制されますが、その行は依然として評価されます。

REPLのすべての機能がサポートされているわけではなく、シェルモードやヘルプモードなどは含まれていません。

Soft vs hard scope

Julia 1.5は、forループなどでグローバル変数を扱う際にソフトスコープを使用するようにREPLを変更しました。Julia 1.5以降でDocumenterを使用する場合、Documenterは@replブロックおよびREPLタイプのドクトテストでソフトスコープを使用します。

Exceptions

Doctestsは、スローされた例外とそのスタックトレースをテストすることもできます。実際の結果と期待される結果の比較は、期待される結果が実際の結果の先頭と一致するかどうかを確認することによって行われます。したがって、以下の2つのエラーは、実際の結果と一致します。

```jldoctest
julia> div(1, 0)
ERROR: DivideError: integer division error
 in div(::Int64, ::Int64) at ./int.jl:115

julia> div(1, 0)
ERROR: DivideError: integer division error
```

最初の div(1, 0) エラーが次のように書かれていた場合

```jldoctest
julia> div(1, 0)
ERROR: DivideError: integer division error
 in div(::Int64, ::Int64) at ./int.jl:114
```

115114 に置き換えられると、ドクトテストは失敗します。

第二の div(1, 0) では、スタックトレースが表示されないため、読者には実際にエラーを再現しようとしたときにスタックトレースが表示されないことが期待されているように見えるかもしれません。出力結果が切り捨てられており、スタックトレース全体(またはその一部)が表示されていないことを読者に示すために、チェックを停止すべき行に [...] と書くことができます。すなわち、

```jldoctest
julia> div(1, 0)
ERROR: DivideError: integer division error
[...]
```

Preserving Definitions Between Blocks

すべてのドクトテストブロックは、それぞれの module 内で評価されます。これは、ブロック内の定義(型、変数、関数など)が次のブロックで 使用できない ことを意味します。例えば:

```jldoctest
julia> foo = 42
42
```

次のブロックでは変数 foo は定義されません:

```jldoctest
julia> println(foo)
ERROR: UndefVarError: foo not defined
```

定義を保持するために、ブロックにラベルを付けて、複数のブロックを同じモジュールにまとめることができます。同じラベルを持つすべてのブロック(同じファイル内)は、同じモジュールで評価されるため、スコープを共有します。これは、例えばテキストや他のドクトテストブロックの間に同じ定義が複数のブロックで使用される場合に便利です。例:

```jldoctest mylabel
julia> foo = 42
42
```

今、下のブロックが上のブロックと同じラベルを持っているので、変数 foo を使用できます:

```jldoctest mylabel
julia> println(foo)
42
```
Note

ラベル付きのドクトテストブロックは、同じモジュールに含まれるために連続している必要はありません(上記の例のように)。ラベルなしのブロックや別のラベルのブロックと交互に配置することができます。

Setup and Teardown Code

ドクトテストには、実際の例の前に評価される必要があるセットアップコードが必要な場合がありますが、それは最終的なドキュメントには表示されるべきではありません。同様に、例の後に評価される必要があるテアダウンコードも必要な場合があります(例:リソースを解放するか、セットアップ中に変更された設定を元に戻すため)。セットアップとテアダウンコードを指定する方法は3つあり、それぞれ異なる状況に適しています。

Documenter 1.9

Teardownコードは少なくともDocumenter 1.9を必要とします。

DocTestSetup and DocTestTeardown in @meta blocks

Markdownソースファイルのドクトテストでは、DocTestSetup = ... および DocTestTeardown = ... の値を含む @meta ブロックを使用できます。以下の例では、関数 foo@meta ブロック内で定義されています。このセットアップブロックは、次のドクトテストブロックの開始時に評価され、テアダウンブロックは終了時に評価されます:

```@meta
DocTestSetup = quote
    function foo(x)
        return x^2
    end
end

DocTestTeardown = quote
    # restore settings, release resources, ...
end
```

```jldoctest
julia> foo(2)
4
```

```@meta
DocTestSetup = nothing
DocTestTeardown = nothing
```

DocTestSetup = nothing および DocTestTeardown = nothing は厳密には必要ではありませんが、次のドクトテストブロックでの意図しない定義を避けるために良い習慣です。

技術的には @meta ブロックはドキュメント文字列内でも機能しますが、REPLでドキュメント文字列をクエリするときに @meta ブロックが表示されるため、そこでの使用は推奨されていません。

Historic note

以前は、ドキュメント文字列を含むMarkdownファイルの@metaブロック内のDocTestSetupが、ドキュメント文字列内のドクトテストにも影響を与えていました。しかし、Documenter 0.23以降はそうではなくなりました。代わりにModule-level metadataまたはBlock-level setup and teardown codeを使用してください。

Module-level metadata

ドクトストリームにあるドクトテストのために、エクスポートされた DocMeta モジュールは、特定のモジュール内のすべてのドクトストリームに適用されるメタデータを添付するためのAPIを提供します。 DocTestSetupDocTestTeardown メタデータの設定は、 makedocs または doctest コールの前に行うべきです:

using MyPackage, Documenter
DocMeta.setdocmeta!(MyPackage, :DocTestSetup, :(using MyPackage); recursive=true)
makedocs(modules=[MyPackage], ...)
Note

すべての(トップレベルの)モジュールを含めることを確認してください。これには、makedocsmodules引数に、ドキュメント文字列を含むdoctestが必要です。そうしないと、これらのdoctestは実行されません。

Block-level setup and teardown code

もう一つの選択肢は、jldoctestブロックにsetupまたはteardownキーワード引数を使用することで、これは短い定義やインラインドキュメンテーションに必要なセットアップに便利です。

```jldoctest; setup = :(foo(x) = x^2)
julia> foo(2)
4
```
Note

DocTestSetupDocTestTeardownsetup および teardown の値は、 doctest ブロックの開始時および終了時に 再評価 され、コードブロック間で状態は共有されません。定義を保持するには、Preserving Definitions Between Blocks を参照してください。

Filtering Doctests

ドクトテストの出力の一部は非決定的である可能性があります。例えば、ポインタのアドレスやタイミングなどです。正規表現を使用して、ドクトテストの出力の一部をフィルタリングまたは変換し、決定的な部分をテストできるようにすることが可能です。

ドクトテストフィルターは、正規表現と置換文字列のペアです。ドクトテストでは、期待される出力と実際の出力の各一致が、2つの出力が比較される前にフィルターに従って置き換えられます。例えば、以下のように、フィルターは浮動小数点数に一致し、小数点以下の最初の4桁のみを保持し、残りを***で置き換えます:

```jldoctest; filter = r"(\d*)\.(\d{4})\d+" => s"\1.\2***"
julia> sqrt(2)
1.4142135623730951
```

実際にドクトテストが最新であるかどうかを判断する際に比較される文字列は実際には

1.4142***

フィルターは通常の正規表現でもあり、その場合は一致した全ての部分文字列が削除されます(つまり、置換は単に "" です)。正規表現と置換文字列の構文についての詳細は、Julia manual を参照してください。

ドクトテストフィルターは、グローバルに、ファイルごとに、または各ドクトテストごとに適用できます。すべてのドクトテストに対してグローバルに適用するには、フィルターのリストを makedocs にキーワード doctestfilters とともに渡すことができます。

より細かい制御を行うために、@meta ブロック内で DocTestFilters 変数にフィルターを割り当てることが可能です。これは単一の正規表現として(DocTestFilters = [r"foo"])、または複数の正規表現のベクターとして(DocTestFilters = [r"foo", r"bar"])定義できます。例として、@time からの非決定的な出力の一部をフィルタリングするために、以下のフィルタリングを適用することができます:

```@meta
DocTestFilters = r"[0-9\.]+ seconds \(.*\)"
```

```jldoctest
julia> @time [1,2,3,4]
  0.000003 seconds (5 allocations: 272 bytes)
4-element Array{Int64,1}:
 1
 2
 3
 4
```

```@meta
DocTestFilters = nothing
```

DocTestFilters = nothingは厳密には必要ありませんが、次のドクトストブロックでの意図しないフィルタリングを避けるための良いプラクティスです。

Info

フィルターマッチは、replaceを使用して期待される出力と実際の出力の両方で空の文字列に置き換えられます。例えば、replace(str, filter => substitution)のようにです。これは、同じフィルターが複数回マッチする可能性があることを意味し、同じフィルターが複数行にマッチする必要がある場合は、正規表現がそれに対応する必要があります。

別のオプションは、filterキーワード引数を使用することです。これは、特定のドクトテストに対してのみアクティブなドクトテストローカルフィルターを定義します。このようなフィルターは、名前付きドクトテスト間で共有されないことに注意してください。単一の正規表現(filter = r"foo")または正規表現のリスト(filter = [r"foo-[0-9]+", r"bar-([0-9]+)" => s"\1"])によってフィルターを定義することが可能です。例:

```jldoctest; filter = r"[0-9\.]+ seconds \(.*\)"
julia> @time [1,2,3,4]
  0.000003 seconds (5 allocations: 272 bytes)
4-element Array{Int64,1}:
 1
 2
 3
 4
```
Note

グローバルフィルター、@metaブロックで定義されたフィルター、およびfilterキーワード引数で定義されたフィルターは、すべて各ドクトテストに適用されます。

Testing and debugging

Documenterは、実行する各doctestに対してデバッグレベルのログを出力し、フィルタリングの前後の出力を表示します。これは、フィルタが期待通りに適用されているか(例えば、フィルタが広すぎないことを確認するためや、フィルタが機能していない理由を理解するため)を確認するために、doctestを開発する際に役立ちます。

デバッグ出力を有効にするには、JULIA_DEBUG 環境変数を Documenter に設定できます。例えば、make.jl スクリプトで次のようにします:

ENV["JULIA_DEBUG"] = "Documenter"

Doctesting as Part of Testing

Documenterは、すべてのドクトテストを手動ビルドに依存せずに検証するために使用できるdoctest関数を提供します。これは@testsetのように動作し、すべてのテストが成功した場合はテストセットを返し、そうでない場合はTestSetExceptionをスローします。

例えば、runtests.jlに以下のように記述することで、通常のテストスイートの一部としてドクトストを検証するために使用できます:

using Test, Documenter, MyPackage
doctest(MyPackage)

デフォルトでは、手動の .md ファイルにあるすべてのドクトテストを検証しようとします。これらのファイルは docs/src の下にあると仮定されます。これは manual キーワードを使用して設定または無効にすることができます(詳細については doctest を参照してください)。

別のテストセットに含めることもでき、その場合は親テストセットに組み込まれます。したがって、別の例として、別々のマニュアルページを持ち、ドキュメント文字列のみを持ち、すべてのテストを単一のテストセットに集めるパッケージをテストするために、runtests.jlは次のようになるかもしれません:

using Test, Documenter, MyPackage
@testset "MyPackage" begin
    ... # other tests & testsets
    doctest(MyPackage; manual = false)
    ... # other tests & testsets
end

注意してください。Module-level metadata のために必要なすべての設定が、doctest が呼び出される前に行われていることを確認する必要があります。また、ドクトテストで読み込んでいるDocumenterや他のすべてのパッケージをテスト依存関係として追加する必要があります。

Fixing Outdated Doctests

古いドクトテストを修正するには、doctest 関数を fix = true で呼び出すことができます。これにより、ドクトテストが実行され、古い結果が新しい出力で上書きされます。これはREPL内でのみ行うことができます:

julia> using Documenter, MyPackage
julia> doctest(MyPackage, fix=true)

代わりに、makedocsdoctest = :fix キーワードを渡すこともできます。

Note
  • :fixオプションは現在、LF行末('\n')にのみ対応しています。

  • コードの変更を行ったら、doctestの修正を実行する前に git commit することをお勧めします。そうすれば、修正がうまくいかなかった場合に以前の状態に戻すのが簡単です。

  • 修正アルゴリズムが間違ったコードスニペットを置き換える可能性があるいくつかのコーナーケースがあります。したがって、コミットする前に修正結果を手動で確認することをお勧めします。

Skipping Doctests

ドクトテストは、makedocs キーワード doctest = false を設定することで無効にできます。これは、パッケージのドキュメントの構造を最初に設定する際にのみ行うべきであり、その後はドキュメントをビルドする際に常にドクトテストを実行することが推奨されます。