💫

toxで快適なテストライフを満喫しよう

に公開

はじめに

仕事でPythonを使って開発をしていて、テストの実装やLinterのチェックも行っています。
これまでなんとなーく使っていたので、改めてtoxがどういったもので、どういったことができるのか理解を深めたいと思い、記事にまとめてみました。
興味を持たれた方が使うことができるように、インストールから実際にtoxを使用する内容も含んでいます。

toxとは?

toxは、Pythonプロジェクトのテスト自動化と環境管理を行うコマンドラインツールです。
pytestやdevpiと並んで、Pythonソフトウェアのパッケージング、テスト、リリースプロセスを容易にするエコシステムの一部です。
実装コードの品質面を確認するために、テスト実行だけでなく、特定のLinterやFormatterも実行したいケースが多いと思います。
これらのコマンドや実行順も指定することができ、toxのコマンドで一括実行することが可能になります。

toxでできること

toxでできることをざっとまとめると以下の通りになります。

  • 複数のPythonバージョンでのテスト

    • Python 3.9、3.10、3.11など、複数バージョンで同時にテスト実行
  • 仮想環境の自動管理

    • テスト環境ごとに独立した仮想環境を自動作成・管理
  • 開発ツールの統一実行

    • pytest、black、mypy、flake8などを統一的に実行
  • 依存関係の分離

    • 環境ごとに異なる依存関係を管理

私のチームでは特に「開発ツールの統一実行」のメリットを考えて採用していました。
複数人でpythonの開発を進めていくと、メンバーによって記述のブレがあったり、型の認識を共有しながら進めたかったりと、課題感が出てきていました。
そこで実装時にローカルでLinterとmypyの型チェック、pytestの実行を必ず行うようにしよう!という取り決めしており、その時の実行にtoxを使用していました。

さっそく使ってみよう!

概要を把握してところで、さっそく使ってみたいと思います。

インストール

はい、何事もまずはインストールからですね。コマンドはとてもシンプルです。

pip install tox

設定ファイルの作成

toxでは以下の設定ファイルを使用することができます(優先順位順):

  1. tox.ini (INI形式)
  2. setup.cfg (INI形式)
  3. pyproject.toml (INI形式 - tool.toxテーブル内のlegacy_tox_iniキー)
  4. pyproject.toml (TOML形式 - tool.toxテーブル)
  5. tox.toml (TOML形式)

初めて使う場合は、どのファイル形式を選択するか、最低限動作させるために必要な設定は何か?、など調べることが多い気がします。
しかし、toxの場合は便利なコマンドが用意されています。

tox quickstart

このコマンドを実行すると自動でtox.iniを生成してくれます。
実際に適当なpythonコードを作成してtox quickstartを実行してみると、以下のようなtox.iniファイルを生成してくれました。これをベースに設定ファイルを整備してくことができます。

[tox]
env_list =
    py311
minversion = 4.34.1

[testenv]
description = run the tests with pytest
package = wheel
wheel_build_env = .pkg
deps =
    pytest>=6
commands =
    pytest {tty:--color=yes} {posargs}

実行してみる

tox

以上。これで実行ができます。
お試しで使ってみる分にはかなりサクッとできる気がします。
ここまで使ったコマンドを振り返ってみましょう。

# tox自体をインストール
pip install tox

# 設定ファイルを自動生成
tox quickstart

# toxを動かしてみる
tox

コマンドもシンプルかつ少ないので、toxを試すだけならかなりサクッと始められます。
今回は自動生成したtox.iniですが、すでに実行可能な状態なので、
あとは設定値などを調べながら、変更->実行、変更->実行....と動かしながらトライアンドエラーできるような状態かと思います!
細かい設定値などは別で記事にまとめよう(もしくは追記しよう)と思いますが、公式サイトの記載も(英語ですが)わかりやすくまとまっているのでそちらも参考にしてください。

このあとは、もう少し詳しいtoxの動作フローや実行コマンドについて見てみようと思います。

toxの基本的な動作フロー

ここで改めてtoxの動作フローについて確認してみました。
公式サイトに図が載っていますが、日本語で簡単に表現すると、toxは以下のような流れで動作します。
理解を深めるためにも、各ステップについて簡単に見ていこうと思います。

┌─────────────────┐
│ 1. 設定読み込み   │
└────────┬────────┘


┌─────────────────┐
│ 2. 環境作成      │ ← 初回のみ、以降は再利用
└────────┬────────┘


┌─────────────────┐
│ 3. 依存関係      │
│   インストール    │
└────────┬────────┘


┌─────────────────┐
│ 4. パッケージング  │ ← プロジェクトをビルド
└────────┬────────┘


┌─────────────────┐
│ 5. パッケージ     │
│   インストール    │
└────────┬────────┘


┌─────────────────┐
│ 6. コマンド実行   │
└────────┬────────┘


┌─────────────────┐
│ 7. レポート表示   │
└─────────────────┘

1. 設定読み込み

toxは起動時に設定ファイル(tox.inipyproject.tomlなど)を探して読み込みます。

  • 設定ファイルから実行する環境リスト(env_list)を取得
  • 各環境の設定(依存関係、実行コマンドなど)を読み込み
  • コマンドライン引数やOS環境変数とマージ

: tox -e py311を実行した場合、py311環境の設定が読み込まれます。

2. 環境作成

次に、各テスト環境用の仮想環境を作成します。
この仮想環境のおかげで、完全に独立してテストを実行することができます。
異なるpythonやライブラリのバージョンを変えて異なる依存関係でテストを実行したり、テストやLinter用の環境など、目的別に環境を分けることができます。

  • 初回実行時: .tox/<env_name>ディレクトリに新しい仮想環境を作成
  • 2回目以降: 既存の環境を再利用(高速化)
  • デフォルトではvirtualenvを使用して環境を作成
  • 環境名から自動的にPythonバージョンを判定(例: py311 → Python 3.11)

上述したように、仮想環境は再利用されるため2回目以降は動作が高速化され効率よくテストを実行することができるようになります。
以下のケースの場合は環境が再作成されます。

以下のケースでは環境が再作成される:

  • 依存関係が変更された
  • Pythonバージョンが変更された
  • 明示的に-rオプションを指定した
# -r オプションの例(環境を強制的に再作成)
tox -r -e py311

3. 依存関係インストール

toxの設定ファイルのdepsで指定したパッケージを仮想環境にインストールします。

[testenv]
deps = 
    pytest>=8.0
    pytest-cov
    requests
  • デフォルトではpipを使用してインストール
  • 依存関係が変更されていない場合、このステップはスキップされる
  • バージョン指定やインデックスURLの指定も可能

インストールコマンドのカスタマイズ:
以下のようにインストールのコマンドを指定することも可能です。

[testenv]
install_command = pip install --no-cache-dir {opts} {packages}

4. パッケージング(オプション)

PEP-517/PEP-518に準拠したビルドプロセスで、プロジェクト自体をパッケージとしてビルドします。
pyproject.tomlで定義された内容でビルドが実行されます。

パッケージタイプの選択:

[testenv]
package = wheel  # wheelを使用(高速)
# package = sdist  # ソースディストリビューション(デフォルト)
# package = editable  # 編集可能モード

スキップする場合:

[testenv:lint]
skip_install = true  # パッケージングをスキップ

5. パッケージインストール

ステップ4でビルドしたプロジェクト自体を環境にインストールします。

  • ビルドしたパッケージ(sdist/wheel)を環境にインストール
  • 依存関係は既にインストール済みなので、パッケージ本体のみインストール
  • コードを変更するたびに、このステップで再インストールされる

開発モードでのインストール:

[testenv]
use_develop = true  # pip install -e . と同等

6. コマンド実行

commandsで指定したコマンドを順次実行します。

[testenv]
commands = 
    pytest tests
    coverage report
  • コマンドは上から順に実行される
  • いずれかのコマンドが失敗(終了コード≠0)すると、そこで停止
  • -プレフィックスを付けるとエラーを無視できる

例:エラーを無視:

[testenv]
commands = 
    - flake8 src  # エラーでも続行
    pytest tests

他にも、commands_precommands_postもあり、以下のような特徴があります。
特徴を理解してうまく使っていきたいですね。

  • commands_pre: commandsの前に実行される。ここで失敗するとcommandsは実行されない。
  • commands_post: commandsの後に実行される。commands_precommandsの結果に関係なく実行されます。

使用イメージ:

[testenv]
commands_pre = 
    python -c "print('テスト前の準備')"
commands = 
    pytest tests
commands_post = 
    python -c "print('テスト後のクリーンアップ')"

7. レポート表示

すべての環境の実行結果をまとめて表示します。

py39: OK (5.23 seconds)
py310: OK (5.45 seconds)
py311: FAIL (3.12 seconds)
py312: SKIP (interpreter not found)
lint: OK (2.34 seconds)
  • 各環境の成功/失敗状態と実行時間を表示
  • すべての環境が成功した場合のみ、終了コード0を返す
  • 失敗した環境がある場合は、終了コード1を返す

結果の種類:

  • OK: 成功
  • FAIL: 失敗(コマンドがエラーを返した)
  • SKIP: スキップ(Pythonインタープリターが見つからないなど)

実行コマンド

使ってみよう!のところで紹介した実行コマンドはtoxとシンプルなものでしたが、以下のようなオプションを指定して実行することも可能です。

# すべての環境を実行
tox

# 特定の環境のみ実行
tox -e py311

# 環境を再作成して実行
tox -r

# 環境一覧を表示
tox list

# 設定を表示
tox config

# 並列実行
tox run-parallel

個人的に注目したいのはtox run-parallelですね。
複数の独立した仮想環境を構築できるので、プロジェクトの規模が増えてきたり、テスト内容のバリエーションが増えてくると、環境も増えて実行時間が伸びることが予想されます。
並列実行を行うことで、ローカルでのテスト実行時間やCI/CDの実行時間の短縮につながり、快適な開発体験を得られると思います。
また、この並列実行コマンドは以下のように細かく制御することが可能です。

# 自動並列度で実行(CPUコア数に基づいて自動決定)
tox run-parallel

# すべての環境を同時に実行
tox run-parallel -p all

# 自動並列度(デフォルト:CPUコア数に基づく)
tox run-parallel -p auto

# 特定の数で並列実行(例:4つの環境を同時実行)
tox run-parallel -p 4

# CI環境での実行(スピナーなし、並列度4)
tox run-parallel -p 4 --parallel-no-spinner

# デバッグ時(ライブ出力、並列度2)
tox run-parallel -p 2 -o

# 特定の環境のみ並列実行
tox run-parallel -e py39,py310,py311,py312

# 環境を再作成して並列実行
tox run-parallel -r

使い分けのポイント:

  • auto: 通常はこれで十分。システムリソースを効率的に使用
  • 数値指定: リソース制限がある環境(CI/CD)で有用
  • all: 環境数が少なく、リソースに余裕がある場合
  • 0: デバッグ時や依存関係がある環境を順次実行したい場合

並列実行を推してきましたが、とりあえず使えば良いというわけではないので、用途や実装ケースによって注意しながら活用していきたいですね。

使わない方が良い場面

  • 環境間に依存関係がある(tox.iniでdependsを使っている場合)
  • 共有リソースがある(DB、ポートなど)

まとめ

toxを使用することで、テスト環境を分離できたり、実行コマンドを細かく調整できたりと、開発の品質向上につながる有用なツールかと思います。
使ってみるハードルも比較的低い気がするので、興味を持たれた方はぜひ使って見てください。
次回はtox.iniの設定値をもう少し深掘りしていきたいと思います!

参考資料

  • tox公式ドキュメント
    • 今回の記事内容は全て公式の内容をもとに執筆・動作確認しています
    • チートシートみたいなものもあったり、設定のオプションもまとまっているので非常にわかりやすいかと思います(英語です)

Discussion