falconとはpythonの軽量webAPIフレームワークで,手軽にちゃちゃっとパフォーマンスの良い(らしい)APIを書けます.手軽さで言うと正直Flaskでもそんな変わらないのではと今となっては思いますが,公式曰くFlaskやBottleと比べて高速に動作するそうです.
環境
- python==3.5.2
- falcon==1.1.0
- gunicorn==19.4.5
基本的にPyCharmで書いています.
APIを書く
テスト駆動開発という観点から見ればまずテストから書くべきなのですが,今回は都合上先にプログラムを書いていきます.
詳しいリファレンスは本家参照*1
ライブラリのインストール
$ pip install falcon gunicorn
gunicornは書いたAPIをwebから叩くために用います.
Hello Worldする
# sample.py import falcon import json def create_json_resp(d: dict) -> str: return json.dumps(d, indent=2) class SampleResource(object): @staticmethod def on_get(req, resp): resp.body = create_json_resp({ "title": "hello world", "sentence": "This is a test program." }) resp.status = falcon.HTTP_200 # デフォルトで200なので無くて良い app = falcon.API() app.add_route("/hello", SampleResource())
これで/hello
へGET
するとレスポンスを返すAPIができました.動かしてみましょう.
$ gunicorn sample:app
これで127.0.0.1:8000/hello
を叩くと設定したjsonが返ってくると思います.
日付を与えると曜日を返すAPI
helloだけでは寂しいので,タイトルそのままのAPIを書きます.
# sample.py import falcon from datetime import datetime # 省略 class WeekResource(object): @staticmethod def on_get(req, resp, _date): week_data = ["月", "火", "水", "木", "金", "土", "日"] try: d = datetime.strptime(_date, "%Y-%m-%d") resp.body = create_json_resp({ "input": _date, "week": week_data[d.weekday()] }) except ValueError: resp.body = create_json_resp({ "error": "ValueError", "detail": "invalid value", "input": _date }) resp.status = falcon.HTTP_400 app = falcon.API() app.add_route("/hello", SampleResource()) app.add_route("/week/{_date}", WeekResource())
これで127.0.0.1:8000/week/2016-12-25
とかを叩くとレスポンスのweek
には日
と入っているはずです.
テストする
テストを書く
公式のリファレンスによるとunittest式の書き方と,pytest式の書き方ができるようです.今回はunittestで書いていきます.
from falcon import testing import sample class MyTest(testing.TestCase): def setUp(self): super(MyTest, self).setUp() # sample.pyからappを渡す self.app = sample.app class TestMySample(MyTest): def test_hello(self): hello_resp = self.simulate_get("/hello") self.assertEqual(hello_resp.status_code, 200) self.assertEqual(hello_resp.json["title"], "hello world") def test_week(self): param = "2016-12-25" week_resp = self.simulate_get("/week/" + param) self.assertEqual(week_resp.status_code, 200) self.assertEqual(week_resp.json["input"], param) self.assertEqual(week_resp.json["week"], "日") def test_invalid_value(self): # 間違った形式のパラメータを与えて正しいエラーが返ってくるかを確認 param = "20161225" week_resp = self.simulate_get("/week/" + param) self.assertEqual(week_resp.status_code, 400) self.assertEqual(week_resp.json["input"], param) self.assertEqual(week_resp.json["error"], "ValueError")
公式に書かれているように
class falcon.testing.TestCase(methodName='runTest')
Extends unittest to support WSGI functional testing.
なので,assertEqual
などはunittestのリファレンス*2を参照すると良いと思います.
simulate_get
等のメソッドについてはfalconのリファレンスが参考になります*3.
テストを実行する
僕はPyCharmで書いているので,このtest_sample.py
を走らせると自動的にunittestで走らせてくれるようです.
実行すると以下の様に表示されます.
成功する場合は何も表示されませんが,例えば曜日を計算するときのメソッドにはdatetime.weekday()
とdatetime.isoweekday()
の二種類があり,返ってくる値が異なります*4.
このメソッドを間違って使用してしまったと仮定し,sample.py
の該当箇所を書き換えてもう一度テストを実行してみます.
このようにどこでfailしたのかを教えてくれます.
コンソールから叩く場合は以下の様にします.
$ python -m unittest test_sample.py
この場合も全てのテストに成功すると以下の様に
失敗するとこのようにエラーを吐いてくれます.
カバレッジも計測できますが,それは他の優良なサイトにお任せします*5.
まとめ
webAPIはいちいち立ち上げて,自分でパラメータ打ち込んで,ブラウザとにらめっこして〜なんてしなくてもデバッグできますし,こうすべきでしょう.これまで手動でやってたなんてとても言えない.
テストケースはこれで十分なのかとか,むしろそっちの方が難しいので,せめてc0カバレッジくらいは…まあせいぜい「思ったように動いてくれる」ことを確かめられる,程度の気持ちでいた方が良さそうです.