複数バージョンのPython向けにCI環境を構築してテストする

  • 14
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

Python Advent Calendar25日目を担当します@giginetです。最終日なので遅れないように何とか書き上げました。

先日、Django向けに簡単なプラグインを開発しました。
そこで、実装したプラグインをPython2と3の違いを吸収して複数環境でテストするようにCI環境を構築したところ、ハマりどころが多かったため、この機会にまとめてみます。

この記事ではTravis CIを使って、複数のPythonバージョンにまたがるテストを実行して、カバレッジを計測する方法をご紹介します。

モジュールを実装する

まず、テストしたい簡単なモジュールを実装しましょう。今回はcalculatorモジュールにcalculator.pyを置き、Calculatorクラスを実装しました。

今回は全てのコードをPython3で実装することを想定しています。

calculator/calculator.py
class Calculator(object):
    @staticmethod
    def power(number):
        return number * number

テストケースを実装する

次にテストケースを実装します。ここでは単純にCalculator#powerが動作するかをテストしています。

tests/test_calculator.py
from unittest import TestCase
from calculator.calculator import Calculator


class CalculatorTestCase(TestCase):
    def setUp(self):
        self.calculator = Calculator()

    def test_power(self):
        self.assertEqual(self.calculator.power(12), 144)

Test Runnerを実装する

今度は記述したテストケースを探索して、テストを実行するスクリプトを記述します。

Python2.7以上であれば、以下のように簡単にテストケースを探索してテストを実行させることができます。

$ python -m unittest discover

Python2.6以下もテストする場合、標準のunittestモジュールに、テストケースを探索するdiscoverが含まれていないため、代わりにunittest2というパッケージを導入する必要があります。

requirements-test.txt
unittest2
$ pip install -r requirements-test.txt

unittest2を用いて、以下のようにテストケースを探索して実行するスクリプトを実装しました。

runtest.py
#!/usr/bin/env python

import unittest2 as unittest

if __name__ == "__main__":
    suite = unittest.TestLoader().discover('tests', pattern = "test_*.py")
    unittest.TextTestRunner().run(suite)

これを実行すると下記のようにテストが走ります。

$ chmod +x runtest.py
$ ./runtest.py
.
----------------------------------------------------------------------
Ran 1 tests in 0.001s

OK

toxを使って複数のバージョンのPythonでテストする

次はいよいよ、このテストケースを複数の環境で走らせてみましょう。

一つのソースを複数バージョンのPythonやDjangoで簡単にテストする方法として、 toxを利用することがよく知られています。

Welcome to the tox automation project — tox 2.3.1 documentation

toxは様々な条件を変えて、テスト環境を構築して、複数の環境でまとめてテストを実行することができるツールです。

tox.iniという設定ファイルを定義することでテスト環境を構築することができるので、今回は以下のように定義します。

tox.ini
[tox]
skipsdist = True
envlist =
    py26,
    py27,
    py33,
    py34,
    py35

[testenv]
deps=
    -rrequirements-test.txt
commands=
  ./runtest.py

詳しくはドキュメントを参照してください。

tox configuration specification — tox 2.3.1 documentation

最後に、toxを実行すると全てのテスト環境を同時に走らせることができます。

今回は動作確認のためにPython3.5でのみテストを実行してみます。

$ tox -r -e 'py35'

これで複数のPythonバージョンに対応したテスト環境の構築ができました。

tox-travisでTravis CI上でtoxを走らせる

今度はこれをTravis CI上で走らせてみましょう。

今回はtox-travisというモジュールを使います。

ryanhiebert/tox-travis

これを導入すると、Travis CI上でtoxを動かすための設定を省略することができます。

例えば下記のように.travis.ymlを用意するだけで複数の環境でテストが実行できます。

.travis.yml
sudo: false
language: python
python:
  - 2.6
  - 2.7
  - 3.3
  - 3.4
  - 3.5

install:
  - pip install tox tox-travis
  - pip install coverage coveralls

script:
  - tox -r

これをTravis CI上で実行すると、以下のように複数のコンテナが立ち上がり、簡単にテストすることができます。便利ですね。

Screen Shot 2015-12-25 at 01.30.33.png

__future__モジュールを使ってPython2と3の互換性を保つ

Python3向けに書いたコードをPython2で動かすためには、__future__モジュールなどを使い、後方互換性を保った書き方をしなくてはなりません。

この記事では詳しく述べないため、詳しくはドキュメントを参照してください。

Easy, clean, reliable Python 2/3 compatibility — Python-Future documentation

Python2でmockテスト

モックやスタブを使ったテストを行いたい場合、Python3には標準ライブラリにmockモジュールが含まれています。

26.5. unittest.mock — mock object library — Python 3.5.1 documentation

一方でPython2以前の環境においてはmockを標準で使うことができません。

Python3のmockモジュールを2以前にバックポートしたパッケージが存在するので、それを導入しましょう。

requirements-test.txt
mock

まずunittest.mockがimportできるかを試し、標準ライブラリに含まれていなかった場合、mockライブラリからimportするようにしましょう。

from unittest import TestCase

try:
    from unittest.mock import MagicMock
except ImportError:
    from mock import MagicMock


class CalcTestCase(TestCase):
    def test_mock(self):
        mock_object = MagicMock()
        mock_object.__str__.return_value = 'hello'
        self.assertEqual(str(mock_object), 'hello')

このようにすると、Python2でも3同様にmockテストを行うことができます。

mockの詳しい使い方については以下を参照すると良いでしょう。

Python - もっくもっく~unittest.mock, mock.mock revisit~ - Qiita

カバレッジを計測する

全ての環境でテストが通るようになったら、最後にカバレッジを計測して、coverailsで管理しましょう。

まず、tox.iniで以下のようにcoverageを実行してカバレッジを計測します。

tox.ini
deps=
    -rrequirements-test.txt
    coverage
commands=
  {envbindir}/coverage run --append --source=calculator runtest.py

このとき、.coveragercというファイルを定義しておくと、カバレッジ計測のルールを設定することができます。以下では、カバレッジ計測を行わないファイルや行を定義しています。

詳しくは以下をご覧ください。

Configuration files — Coverage.py 4.0.3 documentation

.coveragerc
[report]
include =
    calculator/*.py
omit =
    calculator/__init__.py
exclude_lines =
    pragma: no cover
    if __name__ == .__main__.:

最後に.travis.ymlに以下を追記し、テスト終了後にcoverallsにログを送るようにします。

.travis.yml
install:
  - pip install tox tox-travis
  - pip install coverage coveralls

after_success:
  - coverage report
  - coveralls

これでCoverallsでカバレッジを記録できるようになりました。

Screen Shot 2015-12-25 at 01.24.24.png

まとめ

この記事では、Pythonのライブラリを複数バージョンでテストする方法について簡単にまとめました。

今回紹介したTest Suiteを以下のリポジトリに簡単にまとめてあります。

https://github.com/giginet/python-ci-suite

また、toxを上手く使えば、あるライブラリについて複数のバージョンでテストすることもできます。

以下のDjangoプラグインでは、複数バージョンのPythonとDjangoの組み合わせでテストしています。
ご興味のある方はぜひ参考にしてみてください。

giginet/django-debug-toolbar-vcs-info

この投稿は Python Advent Calendar 201525日目の記事です。