Edited at
PythonDay 25

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

More than 3 years have passed since last update.

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