CircleCIでFlaskアプリの自動テストができるようにする(Flaskで簡単なWebサービスを作ってみる[第6回])

はじめに

PythonのWebアプリケーションフレームワークFlaskを使って、「消耗品買い物リスト」を作ってみます。
今回はテスト自動化の設定を行います。

前回まで

今回やること

前回までで、本番環境へのデプロイができるようになったところで、テスト自動化・デプロイ自動化の設定をしていきます。

実現したいこと

  • テスト自動化 ← 本記事はここまで
    • 変更がPushされたらテストが自動で走る
  • デプロイ自動化
    • masterに変更がPush(基本的にはブランチのマージ)がされたらテストが自動で走り、テストが通ると自動で本番環境にデプロイする

採用ツール: CircleCI

CI/CDツールはCircleCIを採用します。
以前CI/CD何もわからん奴がCircleCIでPythonリポジトリのビルドを通すまでで触ったのと、業務でも使い込もうと取り組んでいるのが理由です。

流れ

  • CircleCIプロジェクト作成
  • 設定ブランチをPull
  • requirements.txtを作成
  • config.ymlを調整
  • ビルド・テストが通ることを確認
  • 設定ブランチをmasterにマージ

CircleCIプロジェクト作成

※前提 : CircleCIアカウントは作成済み、GitHubアカウントとの連携済み

CircleCIページからログインし、ビルドするプロジェクトをGitHubリポジトリから選択します。

Pythonの設定がデフォルトで表示されますが、後で変更するので気にせずAdd Projectしてビルドを開始します。

色々あって落ちます。
このあとは、当該リポジトリをチェックアウトしてきてから作業進めていきます。

設定ブランチをPull

※ここからは、ローカル開発環境にて作業します。

前項の操作によって、circleci-project-setupというブランチが作成されるので、リモートリポジトリからfetchしてチェックアウトします。

$ git fetch
$ git checkout -b circleci-project-setup origin/circleci-project-setup

requirements.txtを作成

Pythonアプリのビルドにおいては、requirements.txtというファイルをリポジトリ直下に配置し、その中身をインストールするというプロセスが入ります。
なので、必要なライブラリを記載したrequirements.txtを生成します。

ローカルで作業しているコンテナに入り、pip freezeを使って使用ライブラリを書き出します。
(コンテナ内でやらないと、ホストPCに入っているライブラリがすべて書き出されてしまうため)

# ローカルのコンテナ起動
$ docker ps -a
$ docker start [container_name]

# コンテナに入る
$ docker exec -it [container_name] /bin/bash

# コンテナ内で書き出し
root@1caa75e54efb:/workdir# pip freeze > requirements.txt

requirements.txtの中身は私の場合は以下でした。

astroid==2.4.2
click==7.1.2
Flask==1.1.2
isort==4.3.21
itsdangerous==1.1.0
Jinja2==2.11.2
lazy-object-proxy==1.4.3
MarkupSafe==1.1.1
mccabe==0.6.1
pylint==2.5.3
six==1.15.0
toml==0.10.1
typed-ast==1.4.1
Werkzeug==1.0.1
wrapt==1.12.1

生成されたファイルをリポジトリ直下に配置しておきます。

config.ymlを調整

調整前

デフォルトでは以下のようなテンプレートがconfig.ymlとして追加されます。

version: 2.1

orbs:
  python: circleci/python@0.2.1

jobs:
  build-and-test:
    executor: python/default
    steps:
      - checkout
      - python/load-cache
      - python/install-deps
      - python/save-cache
      - run:
          command: ./manage.py test
          name: Test

workflows:
  main:
    jobs:
      - build-and-test

このままでは動かないので、調整していきます。

調整後

version: 2

jobs:
  build-and-test:
    working_directory: ~/to-buy-list
    docker:
      - image: circleci/python:3.7.3

    steps:
      - checkout      
      - restore_cache:
          keys:
            - pip--{{ checksum "requirements.txt" }}
      - run:
          name: Install dependencies
          command: pip install --user -r requirements.txt
      - save_cache:
          key: pip--{{ checksum "requirements.txt" }}
          paths: /home/circleci/.local/bin/

      - run:
          command: python -m unittest discover tests
          name: Test
          working_directory: ~/to-buy-list/src

workflows:
  version: 2
  workflows:
    jobs:
      - build-and-test

ローカルでCircleCIを実行できるようにする

GitHubにPushするとCircleCIが実行されるようになるのですが、細かな構文の修正や動作確認には煩雑すぎるので、ローカルでCircleCIを実行できるようにします。

ちなみに、デフォルトで追加されるconfig.ymlはversion: 2.1ですが、ローカルでのCircleCIにはまだ対応していないので、対応済みの2.0で書きます。

CircleCIのインストール

$ curl https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh --fail --show-error |bash

$ circleci update check

構文チェック

$ circleci config validate 

Config file at .circleci/config.yml is valid. と出れば構文はOKです。

実行

$ circleci build --job build-and-test

--job [job名]でjobを指定して実行します。

config.ymlの変更点

主な変更点は以下です。

  • versionを2.1→2に
  • orbs, executorなど、2.1以降対応の記法を削除
  • Orbのコマンド(python/load-cacheなど)を書き換え
  • Test部分、テストディレクトリのテストを実行するコマンドに変更

「Orbのコマンド(python/load-cacheなど)を書き換え」については補足します。
それ以外は調整後のconfig.ymlをご確認ください。

Orbのコマンド(python/load-cacheなど)を書き換え

python/load-cache以降の3行は、2.1での書き方(正確には、Orbという、コマンドなどを再利用可能なパッケージにしたもの)なので、2.0での書き方に直します。

  • python/load-cache
  • python/install-deps
  • python/save-cache

公式ドキュメントのCircleCI Orb Registry
のページにて、当該コマンドのShow Command Sourceをクリックすると、実行されているコマンドの中身を調べることができるので、これを見ながら置き換えていきます。

python/load-cache

コマンドの中身

description: Load cached Pip packages.
parameters:
  dependency-file:
    default: requirements.txt
    description: The file to install dependencies from.
    type: string
  key:
    default: pip
    description: The cache key to use. The key is immutable.
    type: string
steps:
  - restore_cache:
      keys:
        - '<< parameters.key >>-{{ checksum "<<parameters.dependency-file>>" }}'

同様の内容を実行するため、以下のように記載

- restore_cache:
    keys:
      - pip--{{ checksum "requirements.txt" }}

python/install-deps

コマンドの中身

description: Install packages from requirements.txt (or any other file) via Pip.
parameters:
  dependency-file:
    default: requirements.txt
    description: The file to install dependencies from.
    type: string
  local:
    default: true
    description: 'Install packages for local user, not globally. Defaults to true.'
    type: boolean
steps:
  - run:
      command: |
        if << parameters.local >>; then
          pip install --user -r << parameters.dependency-file >>
        else
          pip install -r << parameters.dependency-file >>
        fi
      name: Install Dependencies

同様の内容を実行するため、以下のように記載

- run:
    name: Install dependencies
    command: pip install --user -r requirements.txt

python/save-cache

description: Save Pip packages to cache.
parameters:
  dependency-file:
    default: requirements.txt
    description: The file that the dependencies are installed from.
    type: string
  key:
    default: pip
    description: The cache key to use. The key is immutable.
    type: string
  lib-path:
    default: /home/circleci/.local/lib/
    description: The path where the requirements are saved to.
    type: string
steps:
  - save_cache:
      key: '<< parameters.key >>-{{ checksum "<<parameters.dependency-file>>"  }}'
      paths:
        - /home/circleci/.local/bin/
        - << parameters.lib-path >>

同様の内容を実行するため、以下のように記載

- save_cache:
    key: pip--{{ checksum "requirements.txt" }}
    paths: /home/circleci/.local/bin/

ビルド・テストが通ることを確認

config.ymlの編集が終わったら、$ circleci build --job build-and-testを実行し、Success!となることを確認し、変更内容をCommit, Pushします。

CircleCIのダッシュボードを見ると、ビルド・テストが成功していることが確認できます。

※ローカルCircleCIの注意点

config.yml以外のソースに変更を入れた場合、その変更をリモートリポジトリにPushしてからでないとローカルのCircleCIがうまく動かないことがあります。
ローカルで動かす場合も、仕組みとしてはリモートリポジトリをチェックアウト→ビルドプロセスという流れなので、例えばリモートリポジトリに存在しないファイルがビルドプロセスに含まれていると落ちます。
今回だと、requirements.txtやtestsディレクトリを作成しましたが、それらをPushした上で、ローカルで動作チェックという流れになります。

設定ブランチをmasterにマージ

ここまでで、基本的なビルド・テストの設定は完了できたので、設定ブランチ(circleci-project-setup)をmasterにマージします。

これで、リモートリポジトリに何かをPushされると、そのブランチでCircleCIがビルド・テストを自動でしてくれるようになりました。
これで自動テストの準備は完了です。あとは開発を進めていく中で、必要に応じてカスタマイズをしていこうと思います。

終わりに

今回で自動テストまで構築できました。
次回は自動デプロイの構築をやっていきます。

参考