- tl;dr
- 作ったもの
- 知見
— requests.get() を mock で置き換える
— S3 への put_object を moto で置き換える
— invoke コマンド
— Travis CI を使って, 複数の Python バージョンでテスト出来るようにする - 以上
tl;dr
inokara.hateblo.jp
前回の記事の続きというか, 前回, 突貫で作った python スクリプトを自分なりに作り直してみました.
スクリプトを作り直すにあたって, テストを書いたり, その上で Python 3 系の複数のバージョンでテストを Travis CI で回すようにしてみたり, モックを使ったり, 色々と経験出来たので覚書として残しておきたいと思います.
尚, あくまでも「自分なりに」なので, 誤り等あればご指摘頂けると幸いです.
作ったもの
github.com
使い方とかは README をご一読下さい.
内閣府が提供している祝日・休日 csv データですが, 以前はそのフォーマットがとても使いづらいと話題に上がっていたようで, すごく苦労するんだろうなあと思っていましたが, 現在では shift-jis 形式で保存されている以外, ネガティブな感情を抱くことはありませんでした.
知見
requests.get() を mock で置き換える
csv データを取得する為に, requests モジュールを利用していますが, テストの度に内閣府のサーバーにアクセスするのはイケていません. そこで, テストを実行する際には, requests.get()
のレスポンスをモックオブジェクトに置き換えることにしました.
以下, csv データを取得する関数です.
def getHolidayCsv(): '''祝日 csv データ内閣府より取得する ''' try: res = requests.get('http://www8.cao.go.jp/chosei/shukujitsu/syukujitsu_kyujitsu.csv', timeout=3) res.raise_for_status() except requests.exceptions.HTTPError as err: print(err) sys.exit(1) return res.content
以下, そのテストです.
@mock.patch('requests.get') def test_get_holiday_csv(self, mock_get): res = requests.Response() res.status_code = 200 res._content = '' mock_get.return_value = res self.assertEqual(holidays.getHolidayCsv(), '')
上記のように, デコレータで requests.get()
にパッチを当てることで, モックオブジェクトを検証するようにします.
実際にテストを実行すると, 以下のようにテストは通ります.
$ python -m unittest tests.test_holidays.HolidaysPyTest.test_get_holiday_csv -v test_get_holiday_csv (tests.test_holidays.HolidaysPyTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.002s OK
S3 への put_object を moto で置き換える
S3 へのアクセスも requests.get()
と同様に, テストの度に S3 にアクセスするのは筋が悪いので, お馴染みの moto に boto3 の put_object 関数を振る舞わせたいと思います.
github.com
実施には, holidays.saveYearsData()
が holidays.putObject()
を呼んでいるので, 少し分かり辛い感じになってしまいましたが, holidays.putObject()
の中で呼ばれている s3.put_object()
の振舞いを moto が肩代わりするイメージです.
@mock_s3 def test_save_years_data(self): s3 = boto3.resource('s3', region_name='ap-northeast-1') s3.create_bucket(Bucket=os.getenv('BUCKET_NAME')) contents = {'2017-01-01': '元日', '2018-12-24': '振替休日', '2019-01-14': '成人の日'} holidays.saveYearsData(contents) data = '{"2017-01-01": "元日"}' body = s3.Object('holiday-py', '2017/data.json').get()['Body'].read().decode("utf-8") self.assertEqual(body, data) data = '{"2018-12-24": "振替休日"}' body = s3.Object('holiday-py', '2018/data.json').get()['Body'].read().decode("utf-8") self.assertEqual(body, data) data = '{"2019-01-14": "成人の日"}' body = s3.Object('holiday-py', '2019/data.json').get()['Body'].read().decode("utf-8") self.assertEqual(body, data)
moto については, 以前にも触れたことがありますが, ユニットテストで使う場合に便利だと思います.
invoke コマンド
以前にもちょっと触れたことがあるけど, Ruby の Rake っぽいタスクランナーが欲しくて invoke を試してみました.
github.com
task.py は以下のように書いておきます.
import sys from invoke import run, task @task def readme(context): try: run("gh-md-toc --insert README.md && rm -f README.md.*.*") except Exception: sys.exit(1) @task def test(context): try: run("python -m unittest tests.test_holidays -v") except Exception: sys.exit(1)
以下のようにコマンドラインから実行します.
invoke test
以下のように run
関数で指定した unittest が走ります.
$ invoke test test_convert_dict (tests.test_holidays.HolidaysPyTest) ... ok test_decode_content (tests.test_holidays.HolidaysPyTest) ... ok test_get_holiday_csv (tests.test_holidays.HolidaysPyTest) ... ok test_get_years (tests.test_holidays.HolidaysPyTest) ... ok test_save_all_data (tests.test_holidays.HolidaysPyTest) ... ok test_save_years_data (tests.test_holidays.HolidaysPyTest) ... ok ---------------------------------------------------------------------- Ran 6 tests in 0.824s OK
Travis CI を使って, 複数の Python バージョンでテスト出来るようにする
今回の主旨とは離れてしまいますが, ちょっとした思いつきで Travis CI でテストを回してみようと思い, 以下のように .travis.yml を用意しました
sudo: false language: python python: - 3.4 - 3.5 - 3.6 script: - invoke test
これだけの設定 (実際には Travis CI の Web コンソールからリポジトリを指定する必要がありますが) で, 簡単に複数の Python バージョンでテストを流すことが出来ました.
実際にテストを流した状態は下図ような感じになります.
イイ感じですね.
以上
メモでした.