Github上のPythonプロジェクトに必要なテストツールとCIツールのセットアップを行った話

Githubに公開するなら、普通のPython関連の周辺ツールのインストールや設定をしてみようと、色々と試行錯誤したところでハマったところとかポイントとか工夫とかのメモ。

今回はプロジェクトの構造は boto3 のものをまねることにした。

対象プロジェクト - https://github.com/a-hisame/testplay-card-generator

セットアップしたツールの列挙

setup.py のためのディレクトリ構成

以下のように設定(抜粋)。

.
├── data
│  └── fonts
│    ├── NotoSansCJKjp-Bold.ttf
│    └── NotoSansCJKjp-Light.ttf
├── requirements-dev.txt
├── requirements.txt
├── scripts
│  └── install.py
├── setup.py
├── tcgen
│  └── (プロダクトソースコード)
├── tests
│  └── data
│  │  └── (ユニットテスト用テストデータ)
│  └── (ユニットテストコード)
├── .coveragerc
├── .travis.yml
└── tox.ini

どのツールにも共通することとして、実際に動作させる場合には virtualenv で一度環境を作成した後に pip で自分のプロジェクトをインストールした状態で unittest を実施する。
そのため、任意のスクリプトから自分のプロジェクトの import に困ることが無くなる*1

今回は日本語レンダリングのためのフォントが必要だったので、配布時にFontを含めたかった。 このようなケースでプログラム以外に static file をパッケージに含める場合は setup.py の data_files オプションを指定する。
これらの static file のルートパスを取得するには sys.prefix を参照するのだが、もちろんこれだけだと setup.py できちんとインストールされていない場合に失敗するので今回はローカルでの検証用に簡単なラッパーを書いて対応することとした。

プロジェクト用のスクリプトは scripts 以下に配置しており、 .travis.yml 内でも scripts/install.py を呼び出すことでテスト環境の構築を行っている。 具体的には、

  1. pipを利用してrequirements.txt に従って必要なライブラリ類のインストール
  2. setup.py を使って whl ファイルを作成
  3. whl を 環境内にインストールする

と非常にシンプルなことをやっている。
このスクリプトを作っておくことで逐一ライブラリを覚えなくても .venv の作成 → 環境のセットアップ までが簡単にできる。

unittestのための設定

READMEに記載がある通り、以下のコマンドで開発に必要なツールが現在有効な virtualenv 上にインストールした上でテストが実施できる。先述の通り、unittest を実施する対象ファイルとのパスを考える必要がなくてよいので非常に楽。

python script/install.py requirements-dev.txt 

単体テストは boto3 にならってプロジェクト直下に tests ディレクトリを切り、その下にテストに必要なすべての情報を格納する。
今回はテストのための static file が必要だったのでデータを tests/data 以下に格納して test/__init__.py 内に有効なパスを返す関数を用意して使うようにした。

ローカルでの単体テストの実施は nosetests コマンドによる命名自動収集による単体テストの実行*2と、その結果を出力する coverage コマンドの実行。

ローカルで単体テストを実施した後は基本 coverage を更新して欲しかったので scripts/run-local-tests.sh にやることをまとめ、ローカルテストではこれを蹴るようにした。

CI SaaSとの連携

やり方は以下をそのまま参考にして実施。
この中で coverage に対しての omit ディレクトリの指定やエントリポイント (いわゆる if __name__ == '__main__') をテスト対象外にするように .coveragerc を作成。

https://poyo.hatenablog.jp/entry/2018/03/11/210219

まずは Github アカウントを Travis CI および Codecov に連携させる。
その後、 .travis.yml 内に「単体テストを実行する時に coverage 用のファイルを作成する。 テストが正常に終了すれば、その結果を codeconv に連携する」という設定を書いてコミットすれば終わり。

正直、Jenkinsの内部ジョブを使ってゴリゴリゴリゴリと色々必要な内容を最近まで書いていたので、SaaSサービスで簡単に完結していることに軽く驚きつつも、こりゃ面倒なことを自前で書いて人件費を下手に増やすんじゃなくて、ちゃんと外部のSaaSフォーマットに合うように作る方がそりゃ何倍も開発速度出るわなとか漠然と思いつつ連携させました。

これが終わった後は README ファイルに Travis CI および Codecov の結果を表示してくれる badge を貼ればOK。 取得の方法は以下の通り。

  • Travis CI: ビルド完了ページ内に "build:passing" のようなバッジが表示されているので、ここをクリックすることで自分のプロジェクトに必要なバッジを張り付けるコードを得る
  • Codecov: 該当プロジェクト内の Settings > Badge から必要なバッジを張り付けるためのコードを得る

そして、やっぱりPythonは動的型検査言語なので、少なくともC0は保証したいなとまずは最初のunittestを書きあげました。
今回のプロジェクトが副作用を組み合わせまくっているので、テストを書くのが難しいともいえるのですが...

所感

今回、Githubにコードを公開するにあたってプロジェクトの構築が色々と我流になってしまっているなあと思っていたので、既存のプロジェクトにならい、とりあえずの標準系を自分の中に持ち込みました。

なぜこういった構成が我流になってしまうのかを振り返ってみると、仕事上のプライベートな環境で物を作り続けていると、作ったスクリプトはなんだかんだ動いてくれて、利用者や利用環境が限定されるので標準に乗らなくても良い。 そのうえ、Open系のCI SaaSがお金の問題で使えなかったりするので選択肢に入れられないなどの事情があり、これらの構成を学ぶモチベーションが薄くなりがちというところもあるのかなと思いました。

そういった意味では、著名なOSSを開発する環境を自分のローカル上に構築するという作業を行うことでその言語におけるプロジェクトの標準CI管理のための基礎を学べるのは大きいのかもしれません。 特に、新しい言語の開発環境を学ぶ場合には。

*1:我流でやってた時は sys.path.append で自分で追加してやってた

*2:Windows on Linuxの/mnt/以下の都合で --exe 混み