閉じる

【Python】doctestの使い方入門|docstringテストを最速で理解する

Pythonでの関数やクラスの使い方を、そのままドキュメントとテストとして両立できたら便利だと思いませんか。

doctestは、サンプルコード入りのdocstringを書くだけで、その動作を自動で検証してくれる仕組みです。

本記事では、doctestの基礎から、実践的なテクニック、プロジェクトへの組み込み方までを、サンプルコードと図解を交えながら丁寧に解説します。

doctestとは何か?概要とメリット

doctestの基本概要

Python標準ライブラリにはdoctestというモジュールがあり、docstring内に書いた対話型シェル風のサンプルコードを実行し、その出力がdocstringに書かれた期待値と一致するかどうかを検証してくれます。

doctestが読み取るのは、Pythonインタラクティブシェルと同じような書式の>> プロンプトです。

サンプルとテストが1カ所にまとまるため、ドキュメントとテストが自然に同期しやすくなるという特徴があります。

doctestは標準ライブラリの一部なので、追加インストールなしで、Pythonさえ入っていればすぐに使えます。

小規模な関数やユーティリティの動作確認、ライブラリの使い方をサンプル付きで示したい場合に特に効果を発揮します。

doctestでdocstringをテストするメリット

doctestを利用することで、次のようなメリットがあります。

まず、docstringに書いたサンプルコードがそのまま実行可能なテストになります。

読者はサンプルを見ながら実際の挙動をイメージしやすくなり、開発者はテストを回すことで、そのサンプルが現在のコードでも正しく動くことを確認できます。

また、テストコードを書くために別ファイルを用意しなくても、関数やクラスの定義のすぐそばにテストを書けるため、ちょっとした動作確認をすばやく自動化できます。

特にライブラリの公開時など、「ドキュメントに載っているコードが本当に動くか」を機械的に保証できるのは大きな安心材料になります。

さらに、doctestは非常に軽量で、テストフレームワークを導入していない既存コードにも後付けしやすいという利点があります。

既存のdocstringに>> 形式の例を足していくだけで、少しずつテストカバレッジを増やしていくことができます。

unittestやpytestとの違いと使い分け

doctestと他のテストフレームワークの違いを、文章で整理しておきます。

まずdoctestは、docstring内に書いたサンプルコードがそのままテストになります。

テスト用の関数やクラスを用意する必要はありませんが、出力の比較は基本的に文字列比較であり、複雑な検証には向きません。

一方、unittestはPython標準の本格的なテストフレームワークで、クラスベースのテストケースや各種アサーションメソッドを使って、柔軟にテストを書けます。

ただし、その分だけ構造がやや重く、学習コストもかかります。

pytestはサードパーティのテストフレームワークで、関数ベースでシンプルに書ける一方、強力なプラグインエコシステムを持ち、パラメータ化テストやフィクスチャなどを効果的に扱えます。

中規模以上のプロジェクトではpytestが主力となることが多いです。

まとめると、doctestは「サンプルコードをそのまま保証したい」「軽量に始めたい」場面に適し、unittestやpytestは「網羅的で柔軟な自動テスト」を行いたい場面に向いていると考えると使い分けがしやすくなります。

実務では、これらを併用することも一般的です。

doctestの基本的な書き方と実行方法

doctestのインポートと最小サンプルコード

まずは、最小限のサンプルコードから見ていきます。

次のようなファイルを用意します。

Python
# sample_basic.py

def add(a, b):
    """
    2つの数値を足し合わせて返します。

    使用例:
    >>> add(2, 3)
    5
    >>> add(-1, 1)
    0
    """
    return a + b

この段階では、doctestのインポートも実行も書いていません。

ただdocstring内に>> 形式の例を記述しているだけです。

このファイルに対して、後述の方法でdoctestを実行すると、docstringの中身がテストとして扱われます。

docstringへの対話形式(>>> )の書き方

doctestが認識するのは、Pythonのインタラクティブシェルと同じ書式です。

基本形は次のようになります。

Python
def double(x):
    """
    値を2倍にして返します。

    >>> double(4)   # 引数4を渡した場合
    8              # 戻り値として8が返るはず
    """
    return x * 2

書き方のポイントは次の通りです。

1行目に>> で始まる入力行を書き、その直後の行に期待される出力を書きます。

空行を挟まずに連続させることが重要です。

コメントも通常のPythonコードと同様に#を使って書けますが、コメントは出力の比較対象にはなりません。

複数行にまたがる入力も書けます。

たとえばリスト内包表記のような例は、次のように書けます。

Python
def squares(n):
    """
    0からn-1までの二乗のリストを返します。

    >>> squares(5)
    [0, 1, 4, 9, 16]

    複数行にわたる式の例:
    >>> data = [1, 2, 3]
    >>> [x * x for x in data]
    [1, 4, 9]
    """
    return [i * i for i in range(n)]

このように、普段Pythonシェルで試しているコード断片を、そのままdocstringに貼り付けるイメージで書いていくとわかりやすくなります。

コマンドラインからdoctestを実行する方法

Pythonには、モジュールをスクリプトのように実行する-mオプションがあります。

doctestもこれを利用して簡単に実行できます。

Shell
python -m doctest sample_basic.py

このコマンドは、sample_basic.py内のすべてのdocstringからdoctestを探し出し、テストとして実行します。

何も表示されずに終了した場合は、すべてのテストにパスしたことを意味します。

より詳細な結果を知りたい場合は、-vオプションを付けます。

Shell
python -m doctest -v sample_basic.py

実行すると、各docstringに含まれるテストケースごとに、成功・失敗の詳細が表示されます。

たとえば次のような出力が得られます。

Trying:
    add(2, 3)
Expecting:
    5
ok
Trying:
    add(-1, 1)
Expecting:
    0
ok
1 items had no tests:
    sample_basic
1 items passed all tests:
   2 tests in sample_basic.add
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

このようにして、ファイル単位で簡単にdoctestを走らせることができます。

スクリプト内からdoctestを実行する方法

コマンドラインからではなく、スクリプトの中でdoctestを呼び出すこともできます。

代表的なのがdoctest.testmod()です。

Python
# sample_run_inside.py

import doctest

def mul(a, b):
    """
    2つの数値を掛け合わせて返します。

    >>> mul(3, 4)
    12
    """
    return a * b

if __name__ == "__main__":
    # このモジュール内のdoctestをすべて実行します
    doctest.testmod()

このファイルを通常のPythonスクリプトとして実行します。

Shell
python sample_run_inside.py

エラーが発生しなければ、doctestはすべて成功したということです。

テストの結果を詳細に確認したい場合は、次のようにverbose=Trueを指定します。

Python
if __name__ == "__main__":
    doctest.testmod(verbose=True)

この方法を使うと、ファイルを直接実行したときにだけdoctestを走らせるなど、柔軟な制御がしやすくなります。

__main__ガードでdoctestを自動実行する

Pythonでは、モジュールがスクリプトとして直接実行されたときにだけ特定のコードを動かす__main__ガードの慣習があります。

doctestもこれと組み合わせるのが定番です。

Python
# sample_main_guard.py

import doctest

def divide(a, b):
    """
    a を b で割った結果を返します。

    >>> divide(6, 3)
    2.0
    """
    return a / b

if __name__ == "__main__":
    # このファイルを直接実行したときだけdoctestを実行する
    doctest.testmod(verbose=True)

このように書いておけば、import sample_main_guardと他のモジュールからインポートしたときにはテストは実行されません。

一方、ターミナルから直接python sample_main_guard.pyと起動した場合には、doctestが動きます。

Shell
python sample_main_guard.py
実行結果
Trying:
    divide(6, 3)
Expecting:
    2.0
ok
1 items passed all tests:
   1 tests in sample_main_guard.divide
1 tests in 1 items.
1 passed and 0 failed.
Test passed.

ライブラリとして再利用される可能性があるモジュールでは、この書き方を採用しておくと、安全にdoctestを組み込めます。

doctestを使いこなすための実践テクニック

doctestでよくある書き方のパターン

実務でよく使うdoctestの書き方には、いくつかのパターンがあります。

代表的なものをいくつか紹介します。

まず、最もシンプルなのは「入力と出力の1対1対応」を示すパターンです。

Python
def to_upper(s):
    """
    文字列を大文字にして返します。

    >>> to_upper("hello")
    'HELLO'
    """
    return s.upper()

次に、複数の例を並べて挙動のバリエーションを見せるパターンがあります。

Python
def is_even(n):
    """
    n が偶数なら True、奇数なら False を返します。

    >>> is_even(2)
    True
    >>> is_even(3)
    False
    >>> is_even(0)
    True
    """
    return n % 2 == 0

さらに、状態を変化させるオブジェクトの挙動をテストする場合には、順番にメソッドを呼んでいくスタイルが使えます。

Python
class Counter:
    """
    シンプルなカウンタークラスです。

    >>> c = Counter()
    >>> c.value
    0
    >>> c.increment()
    >>> c.value
    1
    """

    def __init__(self):
        self.value = 0

    def increment(self):
        self.value += 1

このように、「利用者が実際にインタプリタで試しそうな手順」をそのまま書いていくと、読みやすくてテストにもなるdocstringが書けます。

doctestのオプション(flag)設定の基本

doctestでは、出力の比較方法を細かく制御するためのoptionflagsを指定できます。

よく使われるのは次のようなフラグです。

  • doctest.ELLIPSIS: 出力中の...を「何かがここに入る」ワイルドカードとして扱う
  • doctest.NORMALIZE_WHITESPACE: 空白文字(空白や改行)の違いを無視して比較する
  • doctest.IGNORE_EXCEPTION_DETAIL: 例外メッセージの詳細を無視し、例外の種類だけを比較する

実際の使い方は次のようになります。

Python
# sample_flags.py

import doctest

def messy_output():
    """
    空白や余計な情報を含む出力の例です。

    >>> messy_output()
    some result: 42   # 実際には空白が多少ずれてもよい
    """
    return "some result: 42"

if __name__ == "__main__":
    doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE |
                               doctest.ELLIPSIS,
                    verbose=True)

ここではNORMALIZE_WHITESPACEELLIPSISをビットORで組み合わせて指定しています。

これにより、出力中の空白や、...で示した部分の差異を許容しつつテストを行えます。

改行や空白を含む出力のテスト方法

改行や空白が含まれる出力を厳密に比較しようとすると、わずかなフォーマットの違いでテストが失敗しがちです。

doctestでは、いくつかのテクニックでこれを緩やかに扱えます。

1つ目はNORMALIZE_WHITESPACEを使う方法です。

次のような関数を考えてみます。

Python
# sample_whitespace.py

import doctest

def format_list(items):
    """
    複数行の文字列としてリストを整形します。

    >>> print(format_list(["a", "b", "c"]))
    - a
    - b
    - c
    """
    return "\n".join(f"- {x}" for x in items)

if __name__ == "__main__":
    doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE,
                    verbose=True)

このようにprint()を使った出力は、末尾の改行も含めて比較されますが、NORMALIZE_WHITESPACEを指定すると、連続する空白や改行の違いをある程度無視して比較してくれます。

もう1つはELLIPSISを使い、長い出力の中で重要な部分だけをテストする方法です。

Python
def long_output():
    """
    長い出力の一部だけを検証する例です。

    >>> print(long_output())
    header: result
    ...
    footer: end
    """
    lines = [
        "header: result",
        "detail: line 1",
        "detail: line 2",
        "footer: end",
    ]
    return "\n".join(lines)

ここでELLIPSISフラグを指定すると、...の部分には任意のテキストが入っていてもOKとみなされます。

Python
if __name__ == "__main__":
    doctest.testmod(optionflags=doctest.ELLIPSIS, verbose=True)

このように、「厳密に全部一致させたいのか」「ざっくり一部だけ検証したいのか」を意識して、フラグを使い分けることが重要です。

例外(Exception)をdoctestでテストする方法

doctestでは、例外が発生することもテスト対象にできます。

基本的な書き方は、実際にPythonシェルで例外が表示される形式をそのままdocstringに書く方法です。

Python
# sample_exception.py

def positive_only(n):
    """
    正の整数以外が渡されたら ValueError を送出します。

    >>> positive_only(10)
    10
    >>> positive_only(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be positive
    """
    if n <= 0:
        raise ValueError("n must be positive")
    return n

Traceback行と、その後に続く例外クラスとメッセージを記述します。

このとき、スタックトレースの詳細は...で省略してしまって構いません。

例外クラスとメッセージが一致していれば、テストは成功します。

もしメッセージの細かな違いを無視して、例外の種類だけをテストしたい場合は、IGNORE_EXCEPTION_DETAILフラグを指定します。

Python
if __name__ == "__main__":
    import doctest
    doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL |
                               doctest.ELLIPSIS,
                    verbose=True)

これにより、「この入力ではValueErrorが起きるべき」という仕様だけを、メッセージに依存せずに確認できます。

ランダム値・現在時刻など不安定な出力への対処法

doctestは文字列を厳密に比較する前提なので、ランダムな値や現在時刻のように実行のたびに変わる出力は、そのままではテストが安定しません。

これに対処するおもな方法をいくつか紹介します。

1つ目は、テスト内でランダム性を固定する方法です。

たとえば乱数シードを設定すれば、同じ結果を再現できます。

Python
# sample_random.py

import random

def pick_one(seq):
    """
    シーケンスからランダムに1要素を選んで返します。

    >>> random.seed(0)  # doctest内でシードを固定
    >>> pick_one([1, 2, 3])
    2
    """
    return random.choice(seq)

このように、docstring内でrandom.seed()を呼び出してしまえば、結果が安定します。

2つ目は、ELLIPSISフラグを使って、重要な部分だけを比較し、具体的な値そのものは無視する方法です。

Python
from datetime import datetime

def current_time_str():
    """
    現在時刻を文字列にして返します。

    >>> current_time_str()  # doctest: +ELLIPSIS
    '2025-...'
    """
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

ここではdoctest: +ELLIPSISというコメントを使い、特定のテスト行にだけフラグを適用しています。

出力の中に...が含まれているため、「先頭4文字が’2025’であればOK」といったゆるい条件にできます。

3つ目は、関数の設計段階で、時間や乱数の取得を引数に切り出し、テスト時に固定値を渡せるようにするリファクタリングです。

この場合、doctestに限らず、通常の単体テストでも扱いやすくなります。

「どこまで厳密にどの部分をテストしたいのか」を明確にした上で、シード固定・ELLIPSIS・設計変更などを組み合わせて安定したテストにしていくとよいでしょう。

doctestを既存のdocstringに後付けするコツ

すでにdocstringが書かれているコードベースに、後からdoctestを導入する場合には、いくつかのコツがあります。

まず、既存の説明文を壊さないように、「使用例」セクションを追加するスタイルが取り入れやすいです。

Python
def normalize(text):
    """
    テキストの前後の空白を取り除き、小文字に変換します。

    引数:
        text (str): 正規化したい文字列。

    戻り値:
        str: 正規化された文字列。

    使用例:
    >>> normalize("  Hello  ")
    'hello'
    """
    return text.strip().lower()

このように、既存のdocstringの末尾に>> 形式の例を足していきます。

最初からすべての関数・メソッドにテストを書く必要はなく、特に重要な関数や、外部に公開しているAPIから優先的にサンプルを追加していくと効率的です。

また、doctest導入直後は、既存コードが必ずしもテストしやすい形になっていないことも多いです。

その場合は、docstringに書く例をシンプルに保ち、複雑なロジックのテストはunittestやpytest側に任せるといった棲み分けも有効です。

doctestをプロジェクトに組み込む方法

パッケージ全体のdoctestを一括実行する

単一ファイルではなく、パッケージ全体にdoctestを仕込んだ場合、一括で実行したくなることがあります。

doctest単体ではパッケージを再帰的に走査する機能はありませんが、いくつかの方法で似たことができます。

シンプルなのは、モジュール名を指定してpython -m doctest -mを繰り返す方法です。

Shell
python -m doctest -v -m mypkg.module1 -m mypkg.module2

ただしモジュールが増えると管理が大変になるため、現実的にはunittestやpytestのテストランナーからdoctestを呼び出すことが多くなります。

これについては後ほど詳しく説明します。

もう1つの方法として、パッケージ内でモジュールを列挙しdoctest.testmod()を回すスクリプトを自作することもできますが、あまり凝った仕組みを作るよりは、既存のテストランナーに統合した方がメンテナンスしやすいケースが多いです。

unittestやpytestからdoctestを呼び出す方法

doctestをプロジェクトの通常のテストフローに統合するには、unittestやpytestから呼び出すのが便利です。

unittestの場合、doctest.DocTestSuiteを使ってテストスイートに追加できます。

Python
# test_doctest_unittest.py

import unittest
import doctest
import mypkg.module1
import mypkg.module2

def load_tests(loader, tests, pattern):
    # module1とmodule2のdoctestをunittestのテストスイートに追加
    tests.addTests(doctest.DocTestSuite(mypkg.module1))
    tests.addTests(doctest.DocTestSuite(mypkg.module2))
    return tests

if __name__ == "__main__":
    unittest.main(verbosity=2)

このファイルをpython -m unittestで実行すれば、通常のunittestのテストと一緒に、doctestも実行されるようになります。

pytestの場合はさらに簡単で、何も特別な設定をしなくても、pytestはdoctestを自動で見つけて実行してくれます

ただし、これには--doctest-modulesオプションを付けて起動する必要があります。

Shell
pytest --doctest-modules

このコマンドは、プロジェクト内のPythonモジュールを探索し、すべてのdocstringからdoctestを抽出して実行します。

pytestの強力なレポート機能や、並列実行機能なども、そのままdoctestの結果に適用されます。

プロジェクトによっては、pytest.inipyproject.tomlなどの設定ファイルに--doctest-modulesをデフォルトオプションとして記述し、常にdoctestを含めて実行するようにしていることもあります。

doctestを使う場面・使わない場面の判断基準

doctestは便利な一方で、万能なテスト手段ではありません

用途に応じて使い分けることが重要です。

使うべき場面としては、次のようなケースが挙げられます。

まず、小さなユーティリティ関数や、単純な変換処理など、入出力がわかりやすい関数では、doctestが最も輝きます。

また、ライブラリやAPIの公開時に、使い方をサンプルコードとして示したい場面でも、そのサンプルが常に動作することを保証する目的でdoctestを活用できます。

逆に、避けた方がよい場面としては、複雑なビジネスロジック、大量のテストケースが必要な処理、外部サービスやファイルI/Oに強く依存する処理などが挙げられます。

これらはdoctestの「文字列比較」や「docstringベース」という制約と相性がよくありません。

通常はpytestやunittestで、フィクスチャやモックなどを使いながらテストするのが自然です。

判断基準としては、「このテストは、人間がドキュメントとして読んで理解しやすいか」「サンプルコードとして価値があるか」を意識するとよいでしょう。

ドキュメントとして読みにくいテストは、doctestにはあまり向いていません。

doctestでドキュメントとテストを一元管理するコツ

doctestをうまく使うと、ドキュメントとテストを1カ所に集約できますが、そのためにはいくつかの工夫が必要です。

1つ目のポイントは、docstringの構造を意識して整えることです。

説明文、引数・戻り値の記述、注意事項、そして「使用例」という順番で書くなど、一定のフォーマットを保つと、Sphinxなどのドキュメント生成ツールとも相性がよくなります。

2つ目は、例を「最小限かつ代表的なケース」に絞ることです。

doctestとしてすべてのパターンを網羅しようとするとdocstringが肥大化し、読みづらくなります。

代表例はdoctestとしてdocstringに残し、複雑なテストケースは別のテストファイルに分離する、という住み分けを意識するとバランスが取れます。

3つ目は、テストの実行を開発フローに組み込むことです。

たとえばGitHub ActionsなどのCIでpytest --doctest-modulesを常に実行する設定にしておけば、ドキュメントを編集したときにも、自動でサンプルコードの整合性が確認されるようになります。

このように、docstringを「人が読むドキュメント」と「機械が読むテスト」の両方として設計していくことで、保守性の高いコードベースを育てていくことができます。

まとめ

doctestは、docstringに書いた>> 形式のサンプルコードをそのままテストとして実行し、ドキュメントとテストを一元管理できる強力な仕組みです。

小さな関数や公開APIの使い方を示すのに向いており、標準ライブラリだけで手軽に導入できます。

一方で、複雑なロジックや大量のケースにはunittestやpytestを併用し、doctestはあくまで「動くサンプル」として位置づけるのが現実的です。

この記事で紹介した基本構文、optionflags、例外や不安定な値への対処法、そしてテストランナーとの連携を押さえておけば、明快なドキュメントと信頼できるテストを同時に保つことができるようになります。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!