【入門】Pythonで始めるテストコードとTracis CI

# 対象
テストコードを書いたり、CIツールを使ったことが無い人向けです。
(筆者自身、記事を書くにあたって初めて触った技術なので不足してたり間違ったりしてるかもしれないので、その時はコメントで指摘していただけると幸いです)

# そもそもCIって
>継続的インテグレーションとは、開発サイクルの最後に大きな変更をマージするのではなく、小さなコード変更を頻繁にマージすることです。目的は少しずつ開発およびテストすることによって、より健全なソフトウェアを構築することです。
[公式ページより、日本語訳](https://docs.travis-ci.com/user/for-beginners)

CIではコードの変更を行った際に自動化されたビルドやテストが実行されます。
より理想を言うとコーディング規約のチェックやコメントによる自動ドキュメント生成など、できることは様々ありますが、今回は「単体テストの実行と外部サービス(slack)との連携」のみを行います。

# Travis CI
> 継続的な統合プラットフォームとして、Travis CIは自動的にコードの変更を構築してテストすることで開発プロセスをサポートし、変更の成功に関する即時のフィードバックを提供します。Travis CIは、展開と通知を管理することによって開発プロセスの他の部分を自動化することもできます。
[公式ページより、日本語訳](https://docs.travis-ci.com/user/for-beginners)

GitがTravis CIに対応していて比較的簡単に利用することが可能ということで、今回利用しました。

## 他のCIツールの概要
### [Jenkins](https://jenkins.io/)
- Javaで記述されたCIツールで、リポジトリへの登録に連動しビルドやテスト等の登録されたジョブを実行し結果を視覚化できる。

### [GitLab](https://about.gitlab.com/)
- Ruby on Railsで記述された CIツールで、Jenkinsと同じく複数のジョブをコードのpushに連動してまとめて実行できる。

### [CircleCI ](https://circleci.jp/)
- サポートが充実していて日本語の記事が、他のCIツールと比較した際豊富。


前置きはここまでで、次からは実際に環境を作っていきます。

# 環境
環境構築に際して`pipenv`を使うので、知らないよって人は下の記事で使い方などを解説してるので、良かったら参考にしてください。
[Pipenvで始める！新卒エンジニアのPython開発環境構築](https://qiita.com/naoya_s/items/d0c181c58b5c8eeb8613)

```shell
$ git clone https://github.com/naoyasugita/testcode_tutorial.git
$ cd testcode_tutorial
# pipenvがない場合は pip install pipenvを先に叩いてください。
$ pipenv install 
$ pipenv shell
```


# ファイル構成
クローンしてくると、レポジトリ無いのファイルは以下のようになっているかと思います。

```
.
├── Pipfile
├── Pipfile.lock
├── README.md
├── .gitignore
├── .travis.yml
├── fizzbuzz.py
└── test
    └── test_fizzbuzz.py
```

今回注目するのは、`.travis.yml`から下に表示されている3ファイルです。

# テストについて
今回テストするのは、[FizzBuzz](https://ja.wikipedia.org/wiki/Fizz_Buzz)と呼ばれる簡単な処理を行うコードです。
SampleProblemクラスの中に実際に処理を行うfizzbuzz関数があります。
インスタンス生成時に繰り返しの回数と関数内で除算を行う際に使う値を2つ指定します。
関数では引数に数値を持ち、引数で与えられた数値に応じて出力を変化させます。

## 処理プログラム
```python:fizzbuzz.py
from typing import Union


class SampleProblem:
    def __init__(self, loop_count: int, num1: int, num2: int) -> None:
        self.loop_count = loop_count
        self.num1 = num1
        self.num2 = num2

    def fizzbuzz(self, index: int) -> Union[int, str]:
        arr = []
        for i in range(1, self.loop_count + 1):
            if i % self.num1 == 0 and i % self.num2 == 0:
                arr.append("fizzbuzz")
            elif i % self.num1 == 0:
                arr.append("fizz")
            elif i % self.num2 == 0:
                arr.append("buzz")
            else:
                arr.append(i)
        return arr[index - 1]

```



-----
先程の処理が正しく行えているかを確認するためにテストコードを使います。
そのために、今回は[unittest](https://docs.python.org/ja/3/library/unittest.html)というPython標準のモジュールを使ってテストを行っています。細かい使い方については、ここでは触れないので、詳しく知りたい方は以下の記事を参考にしてみてください。

- [Python標準のunittestの使い方メモ](https://qiita.com/aomidro/items/3e3449fde924893f18ca)
- [unittest 単体テスト入門 その1 基本的な使い方](https://www.python.ambitious-engineer.com/archives/828)

TestFizzBuzzクラス内に関数が3つありますが関数1つにつき、1回のテストを行います。

## テストコード

```python:test/test_fizzbuzz.py
import unittest

from fizzbuzz import SampleProblem


class TestFizzBuzz(unittest.TestCase):
    LOOP_COUNT = 300

    def test_three_five(self) -> None:
        expected = "fizz"
        sp = SampleProblem(self.LOOP_COUNT, 3, 5)
        actual = sp.fizzbuzz(12)
        self.assertEqual(expected, actual)

    def test_four_six(self) -> None:
        expected = "fizzbuzz"
        sp = SampleProblem(self.LOOP_COUNT, 4, 6)
        actual = sp.fizzbuzz(24)
        self.assertEqual(expected, actual)

    def test_three_five(self) -> None:
        expected = 11
        sp = SampleProblem(self.LOOP_COUNT, 3, 5)
        actual = sp.fizzbuzz(11)
        self.assertEqual(expected, actual)


if __name__ == "__main__":
    unittest.main()
```

一つ例を挙げて説明してみます。
以下のtest_three_fize関数は、<b>12という数値が引数に渡された場合"fizz"と出力する</b> ことを確認するためのテストが記述されています。
`expect`が意図する値で、`actual`が関数の出力です。これらを比較することで、正しい処理が行われているかをテストしています。今回は、２つの変数の値が等しいかどうかで判断しましたが、他にも大小の比較や型の確認など様々な尺度があります。


```python:test_three_five関数

    def test_three_five(self) -> None:
        expected = "fizz"
        sp = SampleProblem(self.LOOP_COUNT, 3, 5)
        actual = sp.fizzbuzz(12)
        self.assertEqual(expected, actual)
```

[以下の表は、Pythonドキュメントより一部抜粋](https://docs.python.org/ja/3/library/unittest.html?highlight=assert#unittest.TestCase.assertGreater)


|メソッド|確認事項|初出|
|:-:|:-:|:-:|
|assertEqual(a, b)|a == b|
|assertNotEqual(a, b)|a != b|
|assertTrue(x)|bool(x) is True|
|assertFalse(x)|bool(x) is False|
|assertIs(a, b)|a is b|3.1|
|assertIsNot(a, b)|a is not b|3.1|
|assertIsNone(x)|x is None|3.1|
|assertIsNotNone(x)|x is not None|3.1|
|assertIn(a, b)|a in b|3.1|
|assertNotIn(a, b)|a not in b|3.1|
|assertIsInstance(a, b)|isinstance(a, b)|3.2|
|assertNotIsInstance(a, b)|not isinstance(a, b)|3.2|

# 実行
実際にテストを行うのですが、今回はpipenvのscriptの機能を使ってテストコードを実行したいと思います。
ちなみに実際に、実行されているコマンドは`$ nosetests -v`です。
noseの詳細ついては[nose tutorial documentation](http://aohta.github.io/nose_tutorial/tips/install.html)を参考にしてください。

```shell
$ pipenv run test
test_four_six (test_fizzbuzz.TestFizzBuzz) ... ok
test_three_five (test_fizzbuzz.TestFizzBuzz) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.016s

OK
```

これでようやく、テストを行うことができました。
では、次は今行ったテストをGithubにプッシュするたび実行するようにしましょう。

# Travis CIの設定
```yml:.travis.yml
language: python

python:
  - "3.5"
  - "3.6"

install:
    - pip install nose

script:
  - nosetests -v

notifications:
  slack: {レポジトリ名}:{トークン}
```

ここでは実行する言語やそのバージョン、必要なライブラリなどを記述します。


# Travis CIにレポジトリを登録
[Travis CIの公式ページ](https://travis-ci.org/)からアカウントを作成してGtihubのレポジトリの指定をします。

<img width="1076" alt="スクリーンショット 2019-05-25 23.48.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107176/975fb4f3-c0f2-6089-96fa-b166ed350164.png">

ここまで登録が完了して、レポジトリにpushすると先程記述したテストコードが自動的に実行されます。
<img width="1220" alt="スクリーンショット 2019-05-25 23.51.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107176/eb65bd89-8cea-f87c-0bf0-782b63bdfa4e.png">

# Slackと連携
## アプリの追加
テスト結果の通知を送りたいワークスペースのAppに「Travis CI」を追加します。
<img width="724" alt="スクリーンショット 2019-05-26 0.00.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107176/f1a0bfcf-46a1-214a-0d5d-aa03984295ac.png">

## チャンネルの選択
インストールを勧めていくと「チャンネルへの投稿」とあるので、通知を表示させたいチャンネルを選択します。
<img width="938" alt="スクリーンショット 2019-05-26 0.01.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107176/b96ecd60-3fab-76da-556a-f323ab0e56f0.png">

## セットアップ
「シンプルな通知」に記述されている内容を先程クローンしてきた`.travis.yml`の内容に上書きします。

```yml:.travis.yml

notifications:
  slack:{自分のレポジトリ名}:{自分のトークン}
```

以上で、作業は完了です。

# まとめ
CIはおろか、テストすら書いたことがなかった筆者ですが、とりあえず試す程度だったらすぐにできました！
テスト自体もまだまだ改良の余地があったり、CIも全然機能を使えきれていない状態だと思いますが、少しずつ慣れていこうと思います。

# 参考
[Core Concepts for Beginners](https://docs.travis-ci.com/user/for-beginners)
[nose tutorial documentation](http://aohta.github.io/nose_tutorial/tips/install.html)
[継続的インテグレーション（CI）入門](https://tracpath.com/bootcamp/ci/php_tutorial_1.html)
[unittest 単体テスト入門 その1 基本的な使い方](https://www.python.ambitious-engineer.com/archives/828)
[Python標準のunittestの使い方メモ](https://qiita.com/aomidro/items/3e3449fde924893f18ca)
[pytestとtravis CIを試す](https://poyo.hatenablog.jp/entry/2018/03/11/210219)