pytestとtravis CIを試す


この記事はSFC-RG Advent Calendar 2017の4日目です。

テストをする

あまりテストとかをしてこなかったんですが、いろいろ開発するにあたって、テストをしっかりやって、機能要件を満たしつつ開発を進めていくということが必要だなと思いました。そこでテストツールであるpytestと、その自動実行と検査ができるCIサービスである、travis CIを試そうと思う。

なんでテストするの?

なぜテストをするのか。基本的に開発はある要件を満たすようにプログラムを書いていくが、その中でリファクタリングしたりする。そうすると仕様が少し変わったりして、インターフェースが変わったりすると本質ではないエラーが出てきてしまうことがよくある。そこで、テストをすることである動作をの基本的な検証を自動化し、そういったエラーなどを効率よく取り除いていくことができる。

テスト駆動開発とか言ったりするらしいが、テストコードを先に書いてしまって、そのテストを通過するように開発を進めていく、なんてこともするらしい。やったことないけど。

テストツール

今回はpythonのテストツールであるpytestを試す。

pip install pytest

でインストール。

テストの実行方法は、テストのコードを書いて、

pytest testcode.py

で実行できる。

例えば以前の記事のオレオレブロックチェーンでテストを書くとしよう。

とりあえずテストとして、ブロックを生成することができているか、をチェックする。

tests.py
# -*- coding: utf-8 -*-                                                                                                                      
import pytest                                                                                                                                

import json                                                                                                                                  
import hashlib                                                                                                                               
from main import BlockchainService                                                                                                           

def test_makeblock():                                                                                                                        
    bcs = BlockchainService()                                                                                                                
    for a in range(0,5):                                                                                                                     
        bcs.make_tx()                                                                                                                        
        bcs.make_block(100)                                                                                                                  

    for a in range(1,len(bcs.bc.chain)):                                                                                                     
        previous = json.dumps(bcs.bc.chain[a-1])                                                                                             
        assert bcs.bc.chain[a]["previous_hash"] == hashlib.sha256(previous.encode('utf-8')).hexdigest()                                                                                                                                                  

5回ブロックを生成して、その後チェーンが正しくハッシュが連鎖しているかどうかをチェックするコードだ。
最後のassert文でチェックを行っている。実行結果は以下。

実行結果
ryosuke[blockchain]$pytest tests.py
============================= test session starts ==============================
platform darwin -- Python 3.6.0, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/ryosuke/Documents/workspace/blockchain, inifile:
plugins: populus-1.7.0
collected 1 items 

tests.py .

=========================== 1 passed in 0.35 seconds ===========================

ここで、テスト中にブロックを書き換えてみる。
テストコードを以下に変更。

# -*- coding: utf-8 -*-                                                                                                                      
import pytest                                                                                                                                

import json                                                                                                                                  
import hashlib                                                                                                                               
from main import BlockchainService                                                                                                           

def test_makeblock():                                                                                                                        
    bcs = BlockchainService()                                                                                                                
    for a in range(0,5):                                                                                                                     
        bcs.make_tx()                                                                                                                        
        bcs.make_block(100)                                                                                                                  

    bcs.bc.chain[2]["previous_hash"] = hashlib.sha256("hoge".encode("utf-8")).hexdigest()                                                    

    for a in range(1,len(bcs.bc.chain)):                                                                                                     
        previous = json.dumps(bcs.bc.chain[a-1])                                                                                             
        assert bcs.bc.chain[a]["previous_hash"] == hashlib.sha256(previous.encode('utf-8')).hexdigest()      

3番目のブロック中のハッシュ値を適当なものに書き換えてブロックの改ざんを行っている。
テストを実行すると、最後のassert文でエラーになるのでfailと表示され、どこでどんなエラーが出たのか表示される。その場合標準出力の結果も表示される。

ryosuke[blockchain]$pytest tests.py
============================= test session starts ==============================
platform darwin -- Python 3.6.0, pytest-3.0.5, py-1.4.32, pluggy-0.4.0
rootdir: /Users/ryosuke/Documents/workspace/blockchain, inifile:
plugins: populus-1.7.0
collected 1 items 

tests.py F

=================================== FAILURES ===================================
________________________________ test_makeblock ________________________________

    def test_makeblock():
        bcs = BlockchainService()
        for a in range(0,5):
            bcs.make_tx()
            bcs.make_block(100)

        bcs.bc.chain[2]["previous_hash"] = hashlib.sha256("hoge".encode("utf-8")).hexdigest()

        for a in range(1,len(bcs.bc.chain)):
            previous = json.dumps(bcs.bc.chain[a-1])
>           assert bcs.bc.chain[a]["previous_hash"] == hashlib.sha256(previous.encode('utf-8')).hexdigest()
E           assert 'ecb666d77872...ea25b1e926825' == '8446760c50abf...d70cacd88f665'
E             - ecb666d778725ec97307044d642bf4d160aabb76f56c0069c71ea25b1e926825
E             + 8446760c50abf402bd7ccd25d17b385f1de6af38bbb723c23e6d70cacd88f665

tests.py:18: AssertionError
----------------------------- Captured stdout call -----------------------------
Transaction is generated
Block is generated
....
Transaction is generated
Block is generated
=========================== 1 failed in 0.56 seconds ==========================

testコードに各メソッドで期待する動作を記述し、pytestを実行することで、こうしたエラーの確認が自動で行える。

CIツール(テストの自動実行)

テストを書いたら今度は自動で実行したくなるだろう。そこでTravis CIという自動テストを行ってくれるサービスがある。Githubと連携し、リポジトリに実行するテストを指定することで、Pushする度に自動で実行し、その結果をメールで通知してくれたりする。

TravisCIには課金制のプライベートリポジトリ用のものと、無料で利用できるパブリックリポジトリ用がある。それぞれhttps://travis-ci.com(有料)https://travis-ci.org(無料)になっている。

今回は無料のorgの方を使ってみる。
トップページからGithubのアカウントを用いてSign Upする。

Sign UpするとGithubとの同期が始まり、パブリックリポジトリが表示される。

設定ファイルの設置

リポジトリのトップディレクトリに.travis.ymlを設定ファイルとして設置する。
ここでは簡単に以下のファイルを設置した。

.travis.yml
language: python
python:
    - 3.6
script:
    - pytest tests.py

ここでpipなどを用いてモジュールをインストールする必要があれば、この設定ファイルの中に記述していく。ここではその書き方などは割愛する。

自動実行の開始

Travis CIのマイページがGithubと同期すると以下のようにパブリックリポジトリが表示される。

真ん中にあるトグルをオンにすることで、pushされた時に自動で.travis.ymlに記述されている内容が実行される。

実行結果の標準出力などは下のJob Logの部分に表示されるため、テストがもし通らなければその部分を参照すれば良い。

注意点

travisの環境下で実行するので、自分の環境では読み込めてるパッケージがなかったりするので環境をしっかり作るようにしないと、自分の環境では通ってたテストが思わぬところでテストこけたりする。

テストをして効率の良い開発を

大学に何日も篭って開発してたりすると疲れてきて、意味のわからないエラーでこけたりします。テストを導入してしょうもないエラーを取り除きつつ進捗を作りましょう。