share facebook facebook twitter menu hatena pocket slack

2018.05.09 WED

内閣府が提供する祝日・休日 csv データをよしなに JSON フォーマットに変換して Amazon S3 に保存する Python スクリ…

川原 洋平

WRITTEN BY 川原 洋平

tl;dr

追記:後日談を書きました inokara.hateblo.jp tl;dr 日本の祝日や休日を JSON で返してくれる Holidays JP API というサービスがありますよね. holidays-jp.github.io すごく便利で有難いと思います. 今回は, ちょっとした時間が出来たので Holidays JP API と同様...

inokara.hateblo.jp

前回の記事の続きというか, 前回, 突貫で作った python スクリプトを自分なりに作り直してみました.

スクリプトを作り直すにあたって, テストを書いたり, その上で Python 3 系の複数のバージョンでテストを Travis CI で回すようにしてみたり, モックを使ったり, 色々と経験出来たので覚書として残しておきたいと思います.

尚, あくまでも「自分なりに」なので, 誤り等あればご指摘頂けると幸いです.

作ったもの

holidays.py - 内閣府が提供する祝日・休日情報の csv を JSON フォーマットで生成して Amazon S3 のバケットに保存する Python スクリプトです.

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 関数を振る舞わせたいと思います.

moto - Moto is a library that allows your python tests to easily mock out the boto library

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 を試してみました.

invoke - Pythonic task management & command execution.

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 バージョンでテストを流すことが出来ました.

実際にテストを流した状態は下図ような感じになります.

イイ感じですね.

以上

メモでした.

元記事はこちら

内閣府が提供する祝日・休日 csv データをよしなに JSON フォーマットに変換して Amazon S3 に保存する Python スクリプトを書いてみたのと, そこで得たイイ感じでテストを書く, テストを回す為の知見を幾つか

川原 洋平

川原 洋平

cloudpack

cloudpackは、Amazon EC2やAmazon S3をはじめとするAWSの各種プロダクトを利用する際の、導入・設計から運用保守を含んだフルマネージドのサービスを提供し、バックアップや24時間365日の監視/障害対応、技術的な問い合わせに対するサポートなどを行っております。
AWS上のインフラ構築およびAWSを活用したシステム開発など、案件のご相談はcloudpack.jpよりご連絡ください。