こんにちは、えいりんぐーです
みなさんはテストを書いていますか?
(ここでは単体テストを指しています)
また、テストを自動化していますか?
Circle CI や Travis CI などで自動テストを実施している方も多いかと思います
一方で、制限の厳しい状況 (クライアントや社内事情など) で仕事をしていると、容易には外部サービスを利用できない場合がありますね
今回は pre-commit を使って git push 時にテストを自動で走らせる方法をご紹介します
pre-commit
pre-commit は Python 製の Git hook のラッパーです
自前でいちいちスクリプトを書くことなく、よくある処理 (リンタやフォーマッタなど) を実装することができます
フック自体には、コミット時やマージ時などの所定のタイミングをトリガーとすることができます
pre-push フックを使えば、プッシュ時にテストが自動で走るように設定できます
余談
pre-commit にはすでに多くのフックがサポートされていますが、pytest のフックは実装されていないようです
- https://github.com/pre-commit/pre-commit-hooks/issues/291
- https://stackoverflow.com/questions/64011304/running-pytest-as-a-pre-commit-hook-no-such-file-or-directory-issue
要望などは何度か上がっているようですが、コミット時に走らせるには、テストが遅いことがネックなようです
実際、コミットするたびにテストは過剰ですよね
pre-push の設定
repos:
- repo: local
hooks:
- id: test
name: test
entry: pytest -v -s tests
stages:
- "push"
language: system
pass_filenames: false
always_run: true
stages
に "push"
を指定すると、プッシュ時にフックを実行します
entry
に実行するコマンドを設定します
pre-commit install -t pre-push
で pre-push フックをインストールします
コード例
ディレクトリ構成
+ .pre-commit-config.yaml
+ mypackage
| + __init__.py
| + foo.py
|
+ tests
| + __init__.py
| + test_foo.py
|
+ requirements.txt
def foo():
return "bar"
from mypackage import foo
def test_foo():
assert foo.foo() == "foo"
このコードをプッシュしようとするとテストが走り、失敗するため、プッシュにも失敗します
$ git push origin pre-commit
test.....................................................................Failed
- hook id: test
- exit code: 1
============================= test session starts =============================
platform win32 -- Python 3.10.6, pytest-7.1.3, pluggy-1.0.0 -- C:\Users\***\Programs\mypackage\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\***\Programs\mypackage
collecting ... collected 1 item
tests/test_foo.py::test_foo FAILED
================================== FAILURES ===================================
__________________________________ test_foo ___________________________________
def test_foo():
> assert foo.foo() == "foo"
E AssertionError: assert 'bar' == 'foo'
E - foo
E + bar
tests\test_foo.py:5: AssertionError
=========================== short test summary info ===========================
FAILED tests/test_foo.py::test_foo - AssertionError: assert 'bar' == 'foo'
============================== 1 failed in 0.09s ==============================
foo
関数の返り値をテストが通るように修正してプッシュします
テストを通過するのでプッシュが成功します
$ git push origin pre-commit
test.....................................................................PassedEnumerating objects: 26, done.
Counting objects: 100% (26/26), done.
Delta compression using up to 8 threads
Compressing objects: 100% (22/22), done.
Writing objects: 100% (25/25), 2.12 KiB | 1.06 MiB/s, done.
Total 25 (delta 9), reused 2 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (9/9), done.
remote:
remote: Create a pull request for 'pre-commit' on GitHub by visiting:
remote: https://github.com/***/mypackage/pull/new/pre-commit
remote:
To github.com:***/mypackage
* [new branch] pre-commit -> pre-commit
テストが成功するまでプッシュできないので、テストを回避しない限りは、ある意味テスト駆動開発が強制されることになります笑
終わり
如何だったでしょうか
テストを実行するコマンドをいちいち実行しなくても、プッシュするだけでテストが走るので便利ですね
また、Circle CIなどでテストを実行する場合と違い、ローカルで実行するため、レスポンスを早く得られるというメリットもあります
pre-commit には他のタイミングや処理のフックもあるので、ぜひご活用ください
本記事がみなさんの参考になれば幸いです