
こんにちは。 DSOC R&D グループの高橋寛治です。
アルゴリズムの考案と実装が主業務ですが、最近は少し幅を広げたテストを始めとしたコードの品質管理・運用など継続的インテグレーション(CI)に興味を持ち、少し取り組んでいます。 新しくアルゴリズムを実装する際に、CIツールのCircleCIとPythonのテストツールpytestを導入してみました。
今回はその導入方法について簡単に紹介を行います。
今回の目標
GitHubにプッシュした際に、次の項目を実現することを目標とします。
- テストの実行
- テスト結果をGitHubレポジトリに表示
- カバレッジを出力
別環境でのビルドやテストが実行されることで、手元では動いたがほかの環境ではうまくいかない、などという事態を防ぐことができるようになります。 また、コミットに紐付いて実行されるため、ファイルのコミット漏れなどの失敗も気づきやすくなります。
より品質のいい(運用や改善がしやすい)コードを実現します。
pytest
pytestはPythonのテストフレームワークです。 Pythonには標準でUnittestフレームワークが実装されていますが、pytestではより簡単にテストを書くことができます。 また fixture と呼ばれるテストにおける共通処理(unittestのsetupやteardownなど)を簡単に記述することができます。
今回はPythonモジュール開発におけるテストという設定で行います。 次のディレクトリ構成で開発を行います。
.
├── README.md
├── setup.py
├── setup.cfg
├── src
│ └── packagename
│ ├── __init__.py
│ └── algorithm.py
├── test-reports
│ ├── cov.xml
│ └── junit.xml
└── tests
├── __init__.py
└── test_algorithm.py
ここではPython virtualenv上でローカルでの開発・実行を行います。 (実際にはDocker上で開発実行するのが望ましいでしょう) 仮想環境を用意する目的は、最低限の環境で実行することで、想定外のモジュールの利用を防ぐためです。
Pythonコードは、pipでインストール可能なPythonのライブラリとします。
実装したアルゴリズムはライブラリとして活用が容易になります。
また、pytest利用時にテストの実行もしやすいためおすすめです。
pipでインストールできるようにするために、setup.pyとsetup.cfgを記述します。
setup.py
from glob import glob from os.path import basename from os.path import splitext from setuptools import setup from setuptools import find_packages setup( name="packagename", version="0.1.0", license="", description="Describe your algorithm.", author="Sansan", url="https://github.com/example/packagename", packages=find_packages("src"), package_dir={"": "src"}, py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], include_package_data=True, zip_safe=False, install_requires=[ "" # 必要な依存ライブラリを記入 ], setup_requires=["pytest-runner"], tests_require=["pytest", "pytest-cov"] )
setup.cfg
[aliases] test=pytest
setup.py では、Pythonライブラリの名前やバージョンなどメタ情報を記述します。
また、必要な依存ライブラリやテスト時に使うライブラリなども記述します。
setup.cfg は setup.py の内容を上書きする設定ファイルです。
テスト実行時に pytest を利用するように書いています。
パッケージのインストールを行います。 editableモード(開発向けモード)でパッケージをインストールし、テスト可能とします。
$ pip install -e . Obtaining file:///home/katakahashi/path/to/packagename ... Installing collected packages: packagename Running setup.py develop for packagename Successfully installed packagename
site-packagesに packagename.egg-link というファイルが作成され、プロジェクトの src ディレクトリへのリンクが張られます。
すなわち、編集したコードがそのままライブラリとしてリンクされているということです。
pytest のベストプラクティスに則っています。
上記の操作により、プロジェクトルートで pytest を実行した際に、作成したライブラリが読み込めるようになります。
pytestを実行してみます。
テスト(tests/test_algorithm.py)やコード(src/packagename/algorithm.py)は適宜準備してください。
手元の環境で一時的にテストする場合には、pip install pytest で動作させるPython環境にpytestが必要となります。
(tests_require は python setup.py test 時に必要なライブラリを記述しており、無ければローカルにインストールされます)
====================== test session starts ====================== platform linux -- Python 3.6.8, pytest-4.4.1, py-1.8.0, pluggy-0.9.0 rootdir: /home/katakahashi/path/to/packagename plugins: requests-mock-1.6.0, mock-1.10.0, cov-2.6.1 collected 3 items tests/test_algorithm.py ... [100%] =================== 3 passed in 0.05 seconds ====================
CircleCI
CircleCIはテストからデプロイまでを自動で行ってくれるアプリケーションです。 GitHubを始めとしたレポジトリと連携させることができ、その操作に発火してテストをしテストが通ればデプロイ、というように使えます。
さて、CircleCIのダッシュボード左側のADD PROJECTSから、対象のGitHubレポジトリを追加しましょう。 連携承認については、画面を見て適宜進めてください。
次に、CircleCIの設定ファイルを追加します。
.circleci/config.yml
version: 2 jobs: build: docker: - image: circleci/python:3.6 resource_class: small working_directory: ~/repo steps: - checkout - restore_cache: keys: - v1-dependencies-{{ .Branch }}-{{ checksum "setup.py" }} # fallback to using the latest cache if no exact match is found - v1-dependencies-{{ .Branch }}- - v1-dependencies- - run: name: install dependencies command: | python3 -m venv venv . venv/bin/activate pip install -e . pip install pytest pytest-cov - save_cache: paths: - ./venv key: v1-dependencies-{{ .Branch }}-{{ checksum "setup.py" }} test: docker: - image: circleci/python:3.6 resource_class: small working_directory: ~/repo steps: - checkout - restore_cache: keys: - v1-dependencies-{{ .Branch }}-{{ checksum "setup.py" }} # fallback to using the latest cache if no exact match is found - v1-dependencies-{{ .Branch }}- - v1-dependencies- - run: name: run tests command: | . venv/bin/activate pytest --junitxml=test-reports/junit.xml --cov=packagename --cov-report html:test-reports/coverage - store_test_results: path: test-reports - store_artifacts: path: test-reports destination: test-reports workflows: version: 2 build_and_test: jobs: - build - test: requires: - build
長々と記述していますが、要所要所を説明します。
jobs が動作させるタスクの内容を記述する領域です。
build や test がタスクの名前となります。
workflows は動作させるタスクを jobs で定義したものから指定します。
requires のように依存関係を記述することができます。
circleci/python:3.6 はCircleCI上で動作させる際のコンテナのバージョンを指定しています。
開発しているPythonのバージョンに合わせましょう。
steps では、具体的な処理を記述しています。
GitHubレポジトリからチェックアウトし、Python環境を整えたり、テストを走らせたりしています。
なお、Python環境は venv により構築します。
テストは、pytest --junitxml=test-reports/junit.xml --cov=packagename --cov-report html:test-reports/coverage により行います。
今回はユニットテストの結果やカバレッジの結果を出力します。
出力したファイルに関しては、store_test_results や store_artifact で設定します。
.circleci/config.yml をGitHubレポジトリにプッシュすると、CircleCIが自動でタスクを開始します。
build_and_test というワークフロー名で build と test のジョブが走りました。

test のジョブをクリックすると、Test Summary でテストの結果(XML)をブラウザ上でそのまま見ることができます。

GitHubのコミットにもテストの結果が表示されています。 コミット時にビルドやテストを走らせるということが実現できました。

カバッレッジは、Artifactsタブ内にHTMLとして作成されています。
index.html をクリックするとブラウザ上で、カバレッジレポートを閲覧することができます。
非常に便利だと思います。

より運用しやすいアルゴリズム開発を目指して
都度テストを走らせたり、Dockerコンテナで動くかなど初歩的と思える検証ですが、人手の運用だと忘れがちです。 コミットに紐付いてそういった検証が走ることで、軽微なミスが無くなり開発は円滑になることでしょう。
ソフトウェア開発では当たり前のことではありますが、テストがあることでアルゴリズムの変更がしやすかったり、複雑になりがちな入力と出力の意図がわかったりします。 高速かつ品質のいい開発を進めるために、アルゴリズム開発においてもどんどん活用していきたいと思います。
執筆者プロフィール
高橋寛治 Sansan株式会社 DSOC (Data Strategy & Operation Center) R&Dグループ研究員
阿南工業高等専門学校卒業後に、長岡技術科学大学に編入学。同大学大学院電気電子情報工学専攻修了。在学中は、自然言語処理の研究に取り組み、解析ツールの開発や機械翻訳に関連する研究を行う。大学院を卒業後、2017年にSansan株式会社に入社。現在はキーワード抽出など自然言語処理を生かした研究に取り組んでいる。