Doctests
Documenterはデフォルトで、見つけたjldoctest
コードブロックを実行し、実際の出力がドクトテストにあるものと一致することを確認します。これにより、ドキュメントの例が古くなったり、不正確になったり、誤解を招いたりするのを避けることができます。パッケージの例はできるだけ多くDocumenterのドクトテストで実行可能であることが推奨されます。makedocs
中のドクトテストの失敗はデフォルトで致命的ですが、makedocs
にwarnonly=: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のすべての機能がサポートされているわけではなく、シェルモードやヘルプモードなどは含まれていません。
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
```
行 115
が 114
に置き換えられると、ドクトテストは失敗します。
第二の 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
```
ラベル付きのドクトテストブロックは、同じモジュールに含まれるために連続している必要はありません(上記の例のように)。ラベルなしのブロックや別のラベルのブロックと交互に配置することができます。
Setup and Teardown Code
ドクトテストには、実際の例の前に評価される必要があるセットアップコードが必要な場合がありますが、それは最終的なドキュメントには表示されるべきではありません。同様に、例の後に評価される必要があるテアダウンコードも必要な場合があります(例:リソースを解放するか、セットアップ中に変更された設定を元に戻すため)。セットアップとテアダウンコードを指定する方法は3つあり、それぞれ異なる状況に適しています。
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
ブロックが表示されるため、そこでの使用は推奨されていません。
以前は、ドキュメント文字列を含むMarkdownファイルの@meta
ブロック内のDocTestSetup
が、ドキュメント文字列内のドクトテストにも影響を与えていました。しかし、Documenter 0.23以降はそうではなくなりました。代わりにModule-level metadataまたはBlock-level setup and teardown codeを使用してください。
Module-level metadata
ドクトストリームにあるドクトテストのために、エクスポートされた DocMeta
モジュールは、特定のモジュール内のすべてのドクトストリームに適用されるメタデータを添付するためのAPIを提供します。 DocTestSetup
と DocTestTeardown
メタデータの設定は、 makedocs
または doctest
コールの前に行うべきです:
using MyPackage, Documenter
DocMeta.setdocmeta!(MyPackage, :DocTestSetup, :(using MyPackage); recursive=true)
makedocs(modules=[MyPackage], ...)
すべての(トップレベルの)モジュールを含めることを確認してください。これには、makedocs
のmodules
引数に、ドキュメント文字列を含むdoctestが必要です。そうしないと、これらのdoctestは実行されません。
Block-level setup and teardown code
もう一つの選択肢は、jldoctest
ブロックにsetup
またはteardown
キーワード引数を使用することで、これは短い定義やインラインドキュメンテーションに必要なセットアップに便利です。
```jldoctest; setup = :(foo(x) = x^2)
julia> foo(2)
4
```
DocTestSetup
、DocTestTeardown
、setup
および 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
は厳密には必要ありませんが、次のドクトストブロックでの意図しないフィルタリングを避けるための良いプラクティスです。
フィルターマッチは、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
```
グローバルフィルター、@meta
ブロックで定義されたフィルター、およびfilter
キーワード引数で定義されたフィルターは、すべて各ドクトテストに適用されます。
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)
代わりに、makedocs
に doctest = :fix
キーワードを渡すこともできます。
:fix
オプションは現在、LF行末('\n'
)にのみ対応しています。コードの変更を行ったら、doctestの修正を実行する前に
git commit
することをお勧めします。そうすれば、修正がうまくいかなかった場合に以前の状態に戻すのが簡単です。修正アルゴリズムが間違ったコードスニペットを置き換える可能性があるいくつかのコーナーケースがあります。したがって、コミットする前に修正結果を手動で確認することをお勧めします。
Skipping Doctests
ドクトテストは、makedocs
キーワード doctest = false
を設定することで無効にできます。これは、パッケージのドキュメントの構造を最初に設定する際にのみ行うべきであり、その後はドキュメントをビルドする際に常にドクトテストを実行することが推奨されます。