💬

コード差分に関係するテストだけ実行するpytest-testmon

に公開

普段使ってるツールの使ってない機能を使ってみる Advent Calendar 2025です。

pytestの記事で紹介されてたpytest-testmonが気になっていたので使ってみました。

pytest-testmonとは

公式ドキュメント曰く

pytest-testmon is a pytest plugin which selects and executes only tests you need to run

ということで、意味あるテストだけ実行できるようにするプラグインです。

具体的には、ある時点(例えばmainブランチ)でのテスト実行(テストによって呼ばれたコード)を保存しておき、そことの差分に関係あるテストだけを実行します。

試してみる

準備

すでにuvの環境設定・pytestのインストールが出来てる前提でtestmonをインストールから始めます。

> uv add  pytest-testmon

サンプルコードとテストコードとしてfizzbuzzを用意します。


def fizzbuzz(n):
    if is_fizzbuzz(n):
        return "FizzBuzz"
    elif is_fizz(n):
        return "Fizz"
    elif is_buzz(n):
        return "Buzz"
    else:
        return str(n)

def is_fizzbuzz(n):
    return n%15 == 0


def is_fizz(n):
    return n%3 == 0


def is_buzz(n):
    return n%5 == 0

import pytest
from fizzbuzz import fizzbuzz

def test_fizzbuzz():
    assert "FizzBuzz" == fizzbuzz(15)

def test_fizz():
    assert "Fizz" == fizzbuzz(3)

def test_buzz():
    assert "Buzz" == fizzbuzz(5)


試してみる

testmonを付けて実行する時は、--testmonパラメータを付けて実行します。最初の実行は、普通のテストと同様に全テスト実行されます。

> pytest --testmon

普通にテストが実行されます。

> pytest --testmon
============================================================================================== test session starts ==============================================================================================
platform linux -- Python 3.11.11, pytest-9.0.1, pluggy-1.6.0
testmon: new DB, environment: default
We'd like to hear from testmon users! Please go to https://testmon.org/survey to leave feedback.
rootdir: /home/notrogue/project/pytest/pytest_sandbox
configfile: pyproject.toml
plugins: testmon-2.2.0
collected 6 items

test_fizzbuzz.py ......                                                                                                                                                                                   [100%]

=============================================================================================== 6 passed in 0.05s ===============================================================================================

SQLite3のファイルが作成されます。

> file .testmondata
.testmondata: SQLite 3.x database, user version 14, last written using SQLite version 3047001, writer version 2, read version 2, file counter 2, database pages 13, cookie 0x9, schema 4, UTF-8, version-valid-for 2

もう一回testmonで実行すると、実行はスキップされます。
(変更箇所がなく、テスト実行する必要ないので)

> pytest --testmon
============================================================================================== test session starts ==============================================================================================
platform linux -- Python 3.11.11, pytest-9.0.1, pluggy-1.6.0
testmon: changed files: 0, unchanged files: 2, environment: default
rootdir: /home/notrogue/project/pytest/pytest_sandbox
configfile: pyproject.toml
plugins: testmon-2.2.0
collected 0 items

差分実行を見てみる

testmonの差分実行機能を見るために、fizzのロジックだけ変えてみます。

def is_fizz(n):
    return n%4 == 0

期待通り、fizzに関するテストだけ実行されます。

pytest --testmon
============================================================================================== test session starts ==============================================================================================
platform linux -- Python 3.11.11, pytest-9.0.1, pluggy-1.6.0
testmon: changed files: test_fizzbuzz.py, unchanged files: 1, environment: default
rootdir: /home/notrogue/project/pytest/pytest_sandbox
configfile: pyproject.toml
plugins: testmon-2.2.0
collected 3 items / 1 deselected / 2 selected

test_fizzbuzz.py F.                                                                                                                                                                                       [100%]

=================================================================================================== FAILURES ====================================================================================================
___________________________________________________________________________________________________ test_fizz ___________________________________________________________________________________________________

    def test_fizz():
>       assert "Fizz" == fizzbuzz(3)
E       AssertionError: assert 'Fizz' == '3'
E
E         - 3
E         + Fizz

test_fizzbuzz.py:8: AssertionError
============================================================================================ short test summary info ============================================================================================
FAILED test_fizzbuzz.py::test_fizz - AssertionError: assert 'Fizz' == '3'

テスト追加

追加したテストが実行されるかも見てみます。


def test_0():
    assert "0" == fizzbuzz(0)
 pytest --testmon
============================================================================================== test session starts ==============================================================================================
platform linux -- Python 3.11.11, pytest-9.0.1, pluggy-1.6.0
testmon: changed files: test_fizzbuzz.py, unchanged files: 1, environment: default
rootdir: /home/notrogue/project/pytest/pytest_sandbox
configfile: pyproject.toml
plugins: testmon-2.2.0
collected 4 items

test_fizzbuzz.py F...                                                                                                                                                                                     [100%]

=================================================================================================== FAILURES ====================================================================================================
____________________________________________________________________________________________________ test_0 _____________________________________________________________________________________________________

    def test_0():
>       assert "0" == fizzbuzz(0)
E       AssertionError: assert '0' == 'FizzBuzz'
E
E         - FizzBuzz
E         + 0

test_fizzbuzz.py:14: AssertionError
===================================================

実行されてますね。

仕組み

SQLite3のテーブルに、テストとそのテストで実行されたファイルの紐づけを持っており、変更されたファイル(のハッシュ)に紐づくテストを探して実行してるようです。

Discussion