対象
テストコードを書いたり、CIツールを使ったことが無い人向けです。
(筆者自身、記事を書くにあたって初めて触った技術なので不足してたり間違ったりしてるかもしれないので、その時はコメントで指摘していただけると幸いです)
そもそもCIって
継続的インテグレーションとは、開発サイクルの最後に大きな変更をマージするのではなく、小さなコード変更を頻繁にマージすることです。目的は少しずつ開発およびテストすることによって、より健全なソフトウェアを構築することです。
公式ページより、日本語訳
CIではコードの変更を行った際に自動化されたビルドやテストが実行されます。
より理想を言うとコーディング規約のチェックやコメントによる自動ドキュメント生成など、できることは様々ありますが、今回は「単体テストの実行と外部サービス(slack)との連携」のみを行います。
Travis CI
継続的な統合プラットフォームとして、Travis CIは自動的にコードの変更を構築してテストすることで開発プロセスをサポートし、変更の成功に関する即時のフィードバックを提供します。Travis CIは、展開と通知を管理することによって開発プロセスの他の部分を自動化することもできます。
公式ページより、日本語訳
GitがTravis CIに対応していて比較的簡単に利用することが可能ということで、今回利用しました。
他のCIツールの概要
Jenkins
- Javaで記述されたCIツールで、リポジトリへの登録に連動しビルドやテスト等の登録されたジョブを実行し結果を視覚化できる。
GitLab
- Ruby on Railsで記述された CIツールで、Jenkinsと同じく複数のジョブをコードのpushに連動してまとめて実行できる。
CircleCI
- サポートが充実していて日本語の記事が、他のCIツールと比較した際豊富。
前置きはここまでで、次からは実際に環境を作っていきます。
環境
環境構築に際してpipenv
を使うので、知らないよって人は下の記事で使い方などを解説してるので、良かったら参考にしてください。
Pipenvで始める!新卒エンジニアのPython開発環境構築
$ 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と呼ばれる簡単な処理を行うコードです。
SampleProblemクラスの中に実際に処理を行うfizzbuzz関数があります。
インスタンス生成時に繰り返しの回数と関数内で除算を行う際に使う値を2つ指定します。
関数では引数に数値を持ち、引数で与えられた数値に応じて出力を変化させます。
処理プログラム
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というPython標準のモジュールを使ってテストを行っています。細かい使い方については、ここでは触れないので、詳しく知りたい方は以下の記事を参考にしてみてください。
TestFizzBuzzクラス内に関数が3つありますが関数1つにつき、1回のテストを行います。
テストコード
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関数は、12という数値が引数に渡された場合"fizz"と出力する ことを確認するためのテストが記述されています。
expect
が意図する値で、actual
が関数の出力です。これらを比較することで、正しい処理が行われているかをテストしています。今回は、2つの変数の値が等しいかどうかで判断しましたが、他にも大小の比較や型の確認など様々な尺度があります。
def test_three_five(self) -> None:
expected = "fizz"
sp = SampleProblem(self.LOOP_COUNT, 3, 5)
actual = sp.fizzbuzz(12)
self.assertEqual(expected, actual)
メソッド | 確認事項 | 初出 |
---|---|---|
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を参考にしてください。
$ 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の設定
language: python
python:
- "3.5"
- "3.6"
install:
- pip install nose
script:
- nosetests -v
notifications:
slack: {レポジトリ名}:{トークン}
ここでは実行する言語やそのバージョン、必要なライブラリなどを記述します。
Travis CIにレポジトリを登録
Travis CIの公式ページからアカウントを作成してGtihubのレポジトリの指定をします。

ここまで登録が完了して、レポジトリにpushすると先程記述したテストコードが自動的に実行されます。
Slackと連携
アプリの追加
テスト結果の通知を送りたいワークスペースのAppに「Travis CI」を追加します。
チャンネルの選択
インストールを勧めていくと「チャンネルへの投稿」とあるので、通知を表示させたいチャンネルを選択します。
セットアップ
「シンプルな通知」に記述されている内容を先程クローンしてきた.travis.yml
の内容に上書きします。
notifications:
slack:{自分のレポジトリ名}:{自分のトークン}
以上で、作業は完了です。
まとめ
CIはおろか、テストすら書いたことがなかった筆者ですが、とりあえず試す程度だったらすぐにできました!
テスト自体もまだまだ改良の余地があったり、CIも全然機能を使えきれていない状態だと思いますが、少しずつ慣れていこうと思います。
参考
Core Concepts for Beginners
nose tutorial documentation
継続的インテグレーション(CI)入門
unittest 単体テスト入門 その1 基本的な使い方
Python標準のunittestの使い方メモ
pytestとtravis CIを試す