CirclrCI を使って Python の最低限の単体テストを行うための設定

はじめて CircleCI を触ったので、将来の自分用として作業メモ。

前提とする知識

本記事では
  • Python における Unittest を用いたテストについて
  • カバレッジなどテストにおける基本的な概念
の説明は割愛させていただきます。
テストトハナンゾヤ?ドウヤッテカクノダ?と言う方はまずそちらについての記事を読むことをお薦めします。

テストを行うリポジトリの準備

テストコードを準備

テストしかない、非常にシンプルなリポジトリを用意しました。


行うテストの内容も a と b を比較するだけの非常にシンプルなものです。
import unittest


class TestSample(unittest.TestCase):
    def test_success_sample(self):
        a = 1
        b = 1
        self.assertEqual(a, b, msg='a is not equals b')


if __name__ == '__main__':
    unittest.main()
念のためローカルでテストを実行して問題ないことを確認しておきましょう
$ python -m unittest discover -s tests/
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

CircleCI の設定ファイルを記述

全ての設定項目について知りたい方は公式ドキュメントを参照してください。
今回はテストの実行しか行わないため、ほとんどの機能を使いません(笑)
CirclrCI の設定ファイルは .circlrci/config.yml に設置されます。

ここに色々と設定を書けるのですが、今回使うのは
  • version
  • jobs
だけです。詳しくは公式ドキュメントのPythonでのサンプルを参照して下さい。

version の設定

version はその名の通りバージョンを指定します。とりあえず 2 もしくは 2.1 を指定しておけば大丈夫かと思います。

jobs の設定

ここで、具体的にビルド、テスト、デプロイの手順を記述します。
最低限必要な物は以下の要素になります。
jobs:
  build:
    working_directory: [コンテナ内での作業ディレクトリ]
    docker:
      - image: [Dockerコンテナ名]
    steps:
      - checkout
      - run:
          command: |
              [実行コマンド]
  • jobs の後は、ジョブ名を指定します。今回、やっている事的には test となるべきなのにジョブ名が build になっていますが、これは __build というジョブがないとエラーになるため__です。 Python のようなビルドが不要な言語からすると、少し煩わしい仕様になっています。
  • working_directory では作業ディレクトリ名を指定します。分かれば何でも大丈夫です。今回の例では ~/circleci-test としています。
  • docker -> image では実際にビルドやテストが走るイメージを指定します。CircleCI 自体が CircleCI で使いやすいようにカスタマイズしたイメージを配布しているので、そちらを使うことをお薦めします。
  • steps -> checkout では working_directory で指定した場所にリポジトリを checkout してきます。
  • steps -> run で指定したコマンドを実行します。今回はコマンドを1つしか実行しませんが、今後のことを考えて複数行受け取れるようにしています。(詳しくはYamlの仕様を参照して下さい。)

できあがり

最終的に次のような設定ファイルが出来上がります
気をつけて欲しい点として、 デフォルトだと /usr/local/bin に PATH が通っていないので、python はフルパスで指定する必要があります。
version: 2.1
jobs:
  build:
    working_directory: ~/circleci-test
    docker:
      - image: circleci/python:3.7.3
    steps:
      - checkout
      - run:
          command: |
            /usr/local/bin/python -m unittest discover -s tests/

これでリポジトリの準備は完了しました。が、まだ CirclrCI 側の設定が完了していないため GitHub への PUSH はしないでください。

CircleCI の Web での設定

一通り前準備が終わったので、今度は CircleCI の GUI を使って設定をしていきます。
Jenkins と違ってほとんどの設定をファイルに書くので、ここでの作業は本当に最低限のものになります。

アカウント作成

特に難しい所はないので説明は割愛します。
強いて言うと、 自分のリポジトリのあるサービスと連携してアカウント作っておくと、その後が楽になると思います。

監視するリポジトリを設定

まず、右側のメニューから「Setting」を選び、サブメニューから「Projects」を選びます。
すると、こんな感じの画面になるはずです。(circleci-test 以外は私が実際に使っているリポジトリなので無視してください)


今回は circleci-test というリポジトリをテストで作っているので、その設定を行うため、歯車マークをクリックします。
色々複雑な設定も出来ますが、今は未だ早いです。とりあえず、水色の「Follow Prject」を押しましょう


これで、プロジェクトが push された際、自動でビルド(今回の場合はテスト)が走るようになります。
Jenkins と違ってとても楽ですね…!

試しに push してみる

先ほど作ったリポジトリを GitHub に Push してみましょう。
少し待つとテストが始まり…
無事テストが通りました!


おまけ: Coverage を取得する

ここまでで、基本的にテストを行う設定は完了です。が、カバレッジは取得したいですよね?ですよね?
ついでにその設定を行ってみましょう。
とりあえず、カバレッジを取得するため、以下のようなファイルを作成します。
def add(a: int, b: int) -> int:
    return a + b


def mul(a: int, b:int) -> int:
    return a * b

そして、テストも次のように書き換えます

import unittest

from sample import add


class TestSample(unittest.TestCase):
    def test_success_sample(self):
        self.assertEqual(add(1, 2), 3, msg='function add is invalid')


if __name__ == '__main__':
    unittest.main()

config.yml を設定するのですが、ほぼ公式ドキュメントのPythonでのサンプル に書かれているので、結果だけ書きます。
以下の設定ファイルで Coverage が取得できるようになります。
Coverage のオプションについては、本筋からそれるのでここでは解説しません。Coverage.py の公式ドキュメントを参照して下さい。
本当は pipenvなどを使った方が今風だと思うのですが、そこまでやる必要もないテストなので、直接 /usr/local/bin 以下にパッケージをインストールします。
version: 2.1
jobs:
  build:
    working_directory: ~/circleci-test
    docker:
      - image: circleci/python:3.7.3
    steps:
      - checkout
      - run:
          command: |
            sudo chown -R circleci:circleci /usr/local/bin
            sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages
            pip install coverage==5.0a5
      - run:
          command: |
            coverage run -m unittest discover -s ./tests
      - store_test_results:
          path: result.xml

ついでに、ローカルでデバッグしていると色々ファイルが生成されるので、 .gitignore ファイルに入れておきましょう。
.coverage
htmlcov/

これで準備は整いました、PUSHしてみましょう。
すると、テストの終盤、色々アップロードがはじまって…


Artifacts のタブから HTML でのカバレッジが確認出来るようになります!(沢山ありますが、index.htmlが本体です)


これで、どこのテストを書いていないかが一目で分かるようになります。


ホントのおまけ: .coveragerc に設定を分離

最後に、coverage のオプションは .coveragerc に分離した方が、オプションが長くなった際に管理が楽なので、そちらに移動させましょう。
次のようなファイルを作成します。
[run]
command_line = -m unittest discover -s ./tests
[report]
omit = */tests/*,*/venv/*
これで、run, report 時のオプションを省略できるようになります。
version: 2.1
jobs:
  build:
    working_directory: ~/circleci-test
    docker:
      - image: circleci/python:3.7.3
    steps:
      - checkout
      - run:
          command: |
            sudo chown -R circleci:circleci /usr/local/bin
            sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages
            pip install coverage==5.0a5
      - run:
          command: |
            coverage run
            coverage report
            coverage html
      - store_artifacts:
          path: htmlcov

image.png
これで PUSH してみましょう。先ほどと同じ結果が得られるはずです。

参考

コメント