たそらぼ

日頃思ったこととかメモとか。

CI初心者なので、CircleCIを使ってローカルでpytestを動かしてみた。

年末に挙げた目標通り『テスト駆動Python』を読んで、pytestをCIツールで使ってみたくなりました。
興味があった点がわかったので、早速ローカルのCircleCIでpytestを試してみたメモです。
極力手短に、ほかの資料で分かりにくかった点をまとめたので、ご了承ください。

『テスト駆動Python』以外に、ネット記事2件も参考にさせていただきました。ありがとうございました。
qiita.com
blog.vtryo.me

環境

macOS Sierra 10.12.5
Homebrew 2.2.2

pytestについて

公式で挙げられている特徴としては以下の6点です。

Features

  • Detailed info on failing assert statements (no need to remember self.assert* names);
  • Auto-discovery of test modules and functions;
  • Modular fixtures for managing small or parametrized long-lived test resources;
  • Can run unittest (including trial) and nose test suites out of the box;
  • Python 3.5+ and PyPy 3;
  • Rich plugin architecture, with over 315+ external plugins and thriving community;

pytest: helps you write better programs — pytest documentation

個人的には、fixtureにより、シンプルにUnitテストを書くことができるのが最大の特徴だと思いました。
fixtureを使うと、Unitテストを実施する前に、前処理的に実行しておかないといけないテストの条件を、関数としてまとめておくことができます。
実行するテストは条件を満たすと自動的にpytestが発見して実行してくれます("test_xxx"や"xxx_test"という関数名になっているなど)。

pytestの使い心地

(当たり前ですけど、)パラメータを振ってテストを作成することが多いので、特にどのようにパラメータを決定できるかに興味がありました。

テストにパラメータを渡す場合

@pytest.mark.parametrizeデコレータでテストに渡すパラメータを設定することができます。

import pytest

@pytest.mark.parametrize('item',[(1,2,3),(4,5,6)])
def test_defaults(item):
    assert 1 in item

fixtureを使って前処理のパラメータを変える

fixtureにも@pytest.fixtureデコレータの引数としてparams=xxxを渡すことでパラメータを渡すことができます。

import pytest

@pytest.fixture(params=[(1,2,3),(4,5,6)])
def default(request):
    return request.param

def test_defaults(default):
    assert 1 in default

これで様々な前処理条件でテストを実行することが可能です。

ローカルでのCircleCIの実行

次に、CircleCIで実行してみます。
CircleCIはSaaSですが、動作確認用にローカル環境で動くものが提供されています。
クラウド版でやった方が実践的ですが、pytestと合わせると話が長くなってしまって自分で勉強した時につらかったので、今回はローカルでの実行に留めます。

ローカルでのCircleCIのインストール

macだとhomebrewでインストールが可能です。
※Docker for Desktopはインストール済みの前提です。

brew update
brew install circleci

CircleCI用のymlの作成

ルートに.circleci/config.ymlを作成する必要があります。
これはdockerfileやembulkなどのconfigファイルに慣れているとだいぶ分かりやすいので、ちんぷんかんぷんの場合はこの辺を色々やってみると良さそうでした。

version: 2
jobs:
  build:
    docker:
      - image: circleci/python:3.6.3
    steps:
      - checkout
      - run:
          name: install dependencies
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install pytest
            pip install pytest-cov
      - run:
          name: run test
          command: |
            . venv/bin/activate
            pytest --junitxml=test-reports/junit.xml
      - run:
          name: run test-cov
          command: |
            . venv/bin/activate
            pytest --cov=src

実行

"テストにパラメータを渡す場合"のコードをtest_sample.pyとして保存し、CircleCIにpytestを実行してもらいます。
ディレクトリ構成は以下のようにしておけばひとまず動きます。

.
├── .circleci
│   └── config.yml
├── README.md
└── test_sample1.py


まずconfig.yml自体に構文ミスがないことを確認します。

circleci config validate
#大丈夫だったら"Config file at .circleci/config.yml is valid."が出力される。


続いて、コードをcommitし、CircleCiを実行します。

circleci local execute
#テストが成功だった場合、最後に"Success!"を出力する。
#失敗だった場合は、"Task failed"などが出力される。


ちなみに、コードをcommitしないとCircleCIで使うDockerイメージをビルドする際にコードが反映されないようでした。
初めて試してみる用に、スクラッチディレクトリを作った人は注意が必要です。
また、circleci local executeはテストが失敗だった場合は"Task failed"などが出力されます。
初めてだったので、なんか設定が間違っているのかと思って右往左往しましたが、これで正常みたいですね。。。

次のステップ

ともあれ、これで自分で書いたUnitテストをpytestで実行し、これを(localですが)CircleCIで動かすことができるようになりました。
次のステップは、

を目指します。

CircleCIの方は、localでできているのでクラウド版でももうちょい頑張ればできそうです。
テスト駆動開発の方は学習済みですが、全然実践でできるレベルではないので、カリカリコードを書いたり、『テスト駆動開発』を読んでやっていこうかなと思っています。

参考

『テスト駆動Python』:

テスト駆動Python

テスト駆動Python

  • 作者:Brian Okken
  • 出版社/メーカー: 翔泳社
  • 発売日: 2018/08/29
  • メディア: 単行本(ソフトカバー)
pytestのオプションなど非常に詳細に使い方を学ぶことが出来ました。ただ、細かすぎる点もあり、これを読んで使いこなせるようになるというよりは、ざっと目を通して、辞書的に使う方が向いているかもという印象でした。

テスト駆動開発』:

テスト駆動開発

テスト駆動開発

  • 作者:Kent Beck
  • 出版社/メーカー: オーム社
  • 発売日: 2017/10/14
  • メディア: 単行本(ソフトカバー)
TDDの有名な本。