Jenkinsさんにお世話になるPython開発 - ユニットテスト編

# 本記事の概要
Pythonを用いた開発において、ユニットテストをJenkinsさんにやってもらう方法を解説する。
Pythonのユニットテストフレームワークとしては、noseかpy.testが候補になりますが
今回は、py.testを使います。

Jenkinsさんには、
* テストの自動実行
* テストレポートの出力
* カバレッジの出力と視覚化
をやってもらいます。

環境は以下の通り。

|項目|バージョンなど|
|:--|:--|
|Python|3.5.1|
|Jenkins|1.6...?|
|テストフレームワーク|py.test|

# py.test
`pip install pytest`でインストール

## テストコードの例
テスト対象コード - calculator.py

```python
class Calculator():
    def add(self, a, b):
        return a + b

    def sub(self, a, b):
        return a - b

    def mul(self, a, b):
        return a * b

    def div(self, a, b):
        return a / b
```

テストコード - test_calculator.py

```python
import pytest
from calculator import Calculator

def pytest_funcarg__calc():
    return Calculator()

@pytest.mark.parametrize("a, b, r", [(9, 8, 17), (7, 6, 13), (5, 4, 9), (3, 2, 5), (1, 0, 1)])
def test_add(calc, a, b, r):
    assert calc.add(a, b) == r

@pytest.mark.parametrize("a, b, r", [(9, 8, 1), (7, 6, 1), (5, 4, 1), (3, 2, 1), (1, 0, 1)])
def test_sub(calc, a, b, r):
    assert calc.sub(a, b) == r

@pytest.mark.parametrize("a, b, r", [(9, 8, 72), (7, 6, 42), (5, 4, 20), (3, 2, 6), (1, 0, 0)])
def test_mul(calc, a, b, r):
    assert calc.mul(a, b) == r

@pytest.mark.parametrize("a, b, r", [(9, 3, 3), (8, 4, 2), (6, 2, 3), (4, 2, 2), (0, 1, 0)])
def test_div(calc, a, b, r):
    assert calc.div(a, b) == r

def test_div_error(calc):
    with pytest.raises(ZeroDivisionError):
        calc.div(1, 0)
```

## テスト実行
`py.test`で実行可能
実行したディレクトリ配下を再帰的に探索し、
testで始まるpyファイルを実行してくれる。

テスト結果
成功例

```
============================= test session starts ==============================
platform darwin -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /Users/rotasuke/git/python_test/others, inifile: 
collected 21 items

test_calculator.py .....................

========================== 21 passed in 0.03 seconds ===========================
```
失敗例

```
============================= test session starts ==============================
platform darwin -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /Users/rotasuke/git/python_test/others, inifile: 
collected 21 items

test_calculator.py ...................F.

=================================== FAILURES ===================================
_______________________________ test_div[0-1-1] ________________________________

calc = <calculator.Calculator object at 0x10c9567f0>, a = 0, b = 1, r = 1

    @pytest.mark.parametrize("a, b, r", [(9, 3, 3), (8, 4, 2), (6, 2, 3), (4, 2, 2), (0, 1, 1)])
    def test_div(calc, a, b, r):
>       assert calc.div(a, b) == r
E       assert 0.0 == 1
E        +  where 0.0 = <bound method Calculator.div of <calculator.Calculator object at 0x10c9567f0>>(0, 1)
E        +    where <bound method Calculator.div of <calculator.Calculator object at 0x10c9567f0>> = <calculator.Calculator object at 0x10c9567f0>.div

test_calculator.py:21: AssertionError
===================== 1 failed, 20 passed in 0.04 seconds ======================
```

# Jenkinsジョブ作成
Gitのチェックアウトなどの細かい設定は割愛する。

[ビルド]の[シェルの実行]に
`py.test <実行するディレクトリ>`
を指定する。

## テストレポートの出力
先ほど入力した、実行するシェルスクリプトに
`--junitxml=<出力先xmlファイル>`
を指定することで、JUnit形式でテストレポートを出力することができる。

[ビルド後の処理]に、[JUnit テスト結果の集計]を追加し
先ほど出力したxmlファイルを指定する。

## カバレッジの出力
`pip install pytest-cov`でカバレッジ出力用のモジュールをインストール

先ほど入力した、実行するシェルスクリプトに
`--cov-report=xml --cov`
を指定することで、コマンド実行したディレクトリ配下にcoverage.xmlを出力することができる。

JenkinsのCobertura Pluginにcoverage.xmlを読ませることで可視化できる。