本文中コード
Noxとは
Noxはテスト用のpython仮想環境を作成し、テストを自動化するコマンドラインツールです。
Welcome to Nox — Nox 2024.10.9 documentation
Noxを利用することで以下のことを実現できます。
- チームメンバーのローカルPC上の環境差分の解消
- CIとローカルPCでの環境差分の解消
- 複数のPython・ライブラリバージョンでの動作確認
Pythonのテスト環境作成ツールとして tox が有名です。
tox
Noxとtoxの簡単な比較です。
tox | Nox | |
---|---|---|
リリース年 | 2010 | 2018 |
設定ファイルフォーマット | INI | python |
GitHubスター数 | 3664 | 1310 |
GitHub Start数の推移を見ても、toxの方が知名度の高さが伺えます。
Noxとtoxの大きな違いは、設定ファイルをtoxはDSLで記述するtox.iniで管理するのに対し、Noxはpythonファイルであるnoxfile.pyで管理することです。
NoxはPythonを用いた柔軟なテスト自動化を行えるのが、toxと比較した特徴です。
GitHub - wntrblm/nox: Flexible test automation for Python
インストール
Noxはプロジェクトの仮想環境ではなく、グローバルの環境にインストールするよう設計されています。
そのためNoxの公式ドキュメントではpipxによるインストールが推奨されてます。
$ pipx install nox
自分はuvを使ってpython環境を用意してるので、uv toolとしてnoxをinstallしました。
$ uv tool install nox
実行方法
以下の一連のテストをnoxで自動化してみます
- ruff check / format
- mypy
- pytest
サンプルプロジェクトとしてロジスティック回帰でirisデータセットを学習するコードを用意しました。
nox-github-actions-example/src/train_lr.py at main · nsakki55/nox-github-actions-example · GitHub
作成したnoxfile.pyです
import nox @nox.session(venv_backend="uv", python=["3.12"], tags=["lint"]) def lint(session): session.install("ruff") session.run("uv", "run", "ruff", "check") session.run("uv", "run", "ruff", "format") @nox.session(venv_backend="uv", python=["3.12"], tags=["lint"]) def mypy(session): session.install("pyproject.toml") session.install("mypy") session.run("uv", "run", "mypy", "src") @nox.session(venv_backend="uv", python=["3.12"], tags=["test"]) def test(session): session.run("uv", "sync", "--dev") if session.posargs: test_files = session.posargs else: test_files = ["tests"] session.run("uv", "run", "pytest", *test_files)
Noxでは@nox.sessionデコレータがついた関数を作成して、静的解析やpytestなど実行したいテストをそれぞれ記述します。
sessionごとに仮想環境が作成されるため、session単位で仮想環境の設定を変更できます。
noxは20240年3月のリリース以降、python仮想環境のバックエンドにuvを指定することができるようになりました。
Release 2024.03.02 · wntrblm/nox · GitHub
Configuration & API — Nox 2024.10.9 documentation
uvによるパッケージインストールのため、noxの仮想環境の立ち上げが高速になります。
Noxの実行はnoxfile.pyがあるディレクトリ直下で、 nox
コマンドを実行することで行えます。
上記のnoxfile.pyが読み取られ、各セッションが実行されています。
ruffとpyproject.tomlのパッケージインストールが uv pip install
で行われているのが分かります。
session.run(”uv”, “run”, “sync”, “—dev”)
コマンドでpyproject.tomlからdev用のパッケージをインストールできます。
$ uvx nox nox > Running session lint-3.12 nox > Creating virtual environment (uv) using python3.12 in .nox/lint-3-12 nox > uv pip install ruff nox > uv run ruff check Built nox-sandbox @ file:///Users/satsuki/github/nox-sandbox Uninstalled 1 package in 0.45ms Installed 1 package in 0.71ms All checks passed! nox > uv run ruff format 1 file reformatted, 5 files left unchanged nox > Session lint-3.12 was successful. nox > Running session mypy-3.12 nox > Creating virtual environment (uv) using python3.12 in .nox/mypy-3-12 nox > uv pip install pyproject.toml nox > uv pip install mypy nox > uv run mypy src Success: no issues found in 3 source files nox > Session mypy-3.12 was successful. nox > Running session test-3.12 nox > Creating virtual environment (uv) using python3.12 in .nox/test-3-12 nox > uv sync --dev Resolved 17 packages in 0.39ms Audited 15 packages in 0.02ms nox > uv run pytest tests ================================================================================================================================================================================ test session starts ================================================================================================================================================================================= platform darwin -- Python 3.12.5, pytest-8.3.3, pluggy-1.5.0 rootdir: /Users/satsuki/github/nox-sandbox configfile: pyproject.toml collected 4 items tests/test_train_lr.py .... [100%] ================================================================================================================================================================================= 4 passed in 0.46s ================================================================================================================================================================================== nox > Session test-3.12 was successful. nox > Ran multiple sessions: nox > * lint-3.12: success nox > * mypy-3.12: success nox > * test-3.12: success
python仮想環境が瞬時に立ち上がっているのを確認できます。
tagをsessionごとに割り当てて、tag単位で実行を行えます。
https://nox.thea.codes/en/stable/tutorial.html#session-tags
上記の例だとruffとmypyのsessionをlintというタグに割り当てているため、以下のようにlintタグを指定することでruffとmypyのみ実行できます。
$ uvx nox -t lint
実行引数を渡すことができます。test sessionの例ではテスト対象のファイルを実行引数で渡せるようにしてます。
https://nox.thea.codes/en/stable/config.html#passing-arguments-into-sessions
$ uvx nox -t test -- tests/test_train_lr.py
GitHub Actionsで実行する
uv経由でnoxをインストール・実行するGitHub Actionsを作成しました。
name: Run nox tests on: [push] jobs: tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.12 uses: actions/setup-python@v2 with: python-version: 3.12 - name: Install uv and nox run: | pip install --upgrade pip pip install uv uv tool install nox - name: Run Nox run: | uvx nox
nox実行部分が11秒で完了しています。
add check · nsakki55/nox-github-actions-example@94ea041 · GitHub
その他の便利な機能
公式ドキュメントを見ていて、自分が便利に思った機能をピックアップして紹介します。
session options 設定
コマンドライン引数で設定できる値を、noxfile.pyで設定できます。sessionごとにpythonバージョンや仮想環境のバックエンドを指定してましたが、まとめて行えます。
https://nox.thea.codes/en/stable/config.html#modifying-nox-s-behavior-in-the-noxfile
nox.options.python = "3.12" nox.options.default_venv_backend = "uv"
実行順序の設定
session.notify
をsessionの最後に追記することで、次に実行するsessionを指定できます。
https://nox.thea.codes/en/stable/config.html#nox.sessions.Session.notify
以下の例だとlint終了後にmypyが実行されます。
@nox.session() def lint(session): session.install("ruff") session.run("uv", "run", "ruff", "check") session.run("uv", "run", "ruff", "format") session.notify("mypy") @nox.session() def mypy(session): session.install("pyproject.toml") session.install("mypy") session.run("uv", "run", "mypy", "src")
環境変数の設定
session.run
コマンドの機能の一つですが、環境変数をrun単位で設定できます。
https://nox.thea.codes/en/stable/config.html#nox.sessions.Session.run
session.run( 'bash', '-c', 'echo $SOME_ENV', env={'SOME_ENV': 'Hello'})
runコマンドでは、以下のように1文で実行コマンドを渡せないので注意が必要です。
session.run('pytest -k fast tests/')
セッションパラメータの設定
事前に設定したパラメータの値ごとsessionを実行できます。
https://nox.thea.codes/en/stable/config.html#parametrizing-sessions
@nox.session @nox.parametrize('django', ['1.9', '2.0']) @nox.parametrize('database', ['postgres', 'mysql']) def tests(session, django, database): ...
Nox自体のバージョン指定
実行するNox自体のバージョンをnoxfile.py内で指定することができます。
https://nox.thea.codes/en/stable/config.html#nox-version-requirements
import nox nox.needs_version = ">=2019.5.30" @nox.session(name="test") # name argument was added in 2019.5.30 def pytest(session): session.run("pytest")
Tox or Nox ?
ToxとNoxについて分かりやすく説明してくれたYouTube動画があるのでこちらが参考になると思います。
www.youtube.com
ToxとNoxどちらの方が優れているというわけではなく、それぞれの良さがあるという話です。
知名度で言うとToxの方があり、Toxに関する記事の方がNoxよりも豊富にあります。
NoxはPythonで記述できることによる柔軟性を特徴とする一方、Toxはエンジニアに広く馴染みのあるDSLで記述できるという特徴があります。
どちらを採用するかはチームやプロジェクト内容に依存するところが大きいのかなと思います。