はじめに
全5回(予定なので変わる可能性あり)にかけて、Flaskでテスト駆動開発を行うために必要なノウハウを伝授
する。
3回目の本記事では、GETパラメータを加算処理するAPIに対して、テスト用クライアントを利用して自動でテストを実行例を紹介する。
1回目 Flaskでテスト駆動開発(TDD)を行うためのチュートリアル - 1 テスト用クライアント編
2回目 Flaskでテスト駆動開発(TDD)を行うためのチュートリアル - 2 デコレータ編
3回目 本記事
4回目 執筆中
5回目 執筆中
対象読者
- FlaskでこれからWebアプリまたはAPIを開発する方
- テスト自動化の勉強をしたい方
概要
アプリケーションに対して複数のGETパラメータを送る方法とそれらを効率よくテストする方法を解説する。
ディレクトリ構造
本記事で利用するサンプルコードは以下のようなディレクトリ構造で配置してください。
flask_03/
├── Dockerfile
└── app
├── flask_app.py
└── test
└── decorators.py
└── test.py
dockerのバージョン
$ docker --version
Docker version 19.03.12, build 48a66213fe
コードの準備
Dockerfile
FROM python:3.6
USER root
RUN apt update
RUN /usr/local/bin/python -m pip install --upgrade pip
RUN pip install flask==1.1.2
COPY ./app /root/
WORKDIR /root/test
flask_app.py(テスト対象)
今回は、2つのGETパラメータ(var1
とvar2
)を受け取り、加算処理を行い、その結果を返すAPIを考える。
また、GETパラメータが対応していない型(例えば、数字以外の文字列)出会った場合の例外処理も考える。
from flask import Flask, request
app = Flask(__name__)
@app.route('/sum', methods=['GET'])
def sum():
try:
var1 = float(request.args.get("var1"))
var2 = float(request.args.get("var2"))
data = str(var1 + var2)
return data, 200
except Exception as e: # 例外処理
exception_message = "Invalid Parameter"
return exception_message, 200
if __name__ == '__main__':
app.run(host="0.0.0.0",port=5000)
test.py(テストコード)
今回は正常系と異常系の2種類のクラスを用意する。
また詳細は最後のセクションで説明する。
import sys
sys.path.append('../')
import flask_app # flaskアプリケーションのファイル名のから.pyを消したもの
from decorators import get_test
import unittest
class Test_flask_app_正常系(unittest.TestCase): # 正常系試験
def setUp(self):
# 1.GETパラメータを設定できるようにURLの末尾に?を付け、key={キーワード引数}の形式で記述
self.ENDPOINT = "http://localhost:5000/{route}?var1={var1}&var2={var2}"
self.STATUS = "200 OK"
self.STATUS_CODE = 200
self.ROUTE = "sum"
self.data = None
@get_test()
def test_sumにアクセスし計算結果を受け取れること(self):
# 2.テストケース特有の変数をリスト内に定義
self.var1 = [1.0, 100, -1000, 0.5, -50.5, 33.3, 0]
self.var2 = [100, -1000, 0.5, -50.5, 33.3, 0, 1.0]
return
class Test_flask_app_異常系(unittest.TestCase): # 異常系試験
def setUp(self):
self.ENDPOINT = "http://localhost:5000/{route}?var1={var1}&var2={var2}"
self.STATUS = "200 OK"
self.STATUS_CODE = 200
self.ROUTE = "sum"
self.data = None
# 3.デコレータのパラメータを設定
@get_test(exception_message=b"Invalid Parameter")
def test_sumにアクセスしexceptionの処理が実行されること(self):
# 2.テストケース特有の変数をリスト内に定義
self.var1 = ["", "hello", "python", 100, -1000, 0.5, "", "hello", "python", None, 100]
self.var2 = [100, -1000, 0.5, "", "hello", "python", "hello", "python", "", 100, None]
return
if __name__ == '__main__':
unittest.main()
decorators.py(デコレータ)
test.pyで利用するデコレータ。
import sys
sys.path.append('../')
import flask_app
# 1.デコレータの引数
def get_test(exception_message=None):
def recv_func(test_func):# テスト用の関数の受け取り
def wrapper(self):# 受け取ったテスト用の関数をデコレートする
test_func(self)
# 2.for文を利用して複数のGETパラメータでテストを実施
for var1, var2 in zip(self.var1, self.var2):
with flask_app.app.test_client() as client:
response = client.get(self.ENDPOINT.format(route = self.ROUTE,
var1 = var1,
var2 = var2))
# 3.デコレータの引数を用いた分岐
if exception_message:
self.data = exception_message
else:
self.data = str(float(var1 + var2)).encode("UTF-8")
assert response.data == self.data
assert response.status == self.STATUS
assert response.status_code == self.STATUS_CODE
response.close()
return wrapper
return recv_func
テストを実行
ディレクトリ構造を確認し、以下のコマンドを実行する。
$ ls
Dockerfile app
$ docker build -t pytest3 .
~ 省略 ~
$ docker run --rm -it pytest3 /usr/local/bin/python /root/test/test.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.016s
OK
test.py(テストコード)の解説
1.GETパラメータを設定できるようにURLの末尾に?を付け、key={キーワード引数}の形式で記述
self.ENDPOINT
にURLとその末尾に?とkey={キーワード引数}
の形式の文字列を格納する。
key={キーワード引数}
の形式にすることで、様々なGETパラメータに対するテストを行い易くなる。
2.テストケース特有の変数をリスト内に定義
GETパラメータをリスト内に格納することで、for文を利用し易くなる。
for文で取り出した値を、self.ENDPOINT
の{キーワード引数}
に埋め込むことで少ないコード量で多くのテストを行うことができる。
また今回のtest.pyでは、Test_flask_app_正常系
のテストケースで正常に処理が行われているパラメータの組み合わせを定義し、Test_flask_app_異常系
のテストケースで例外処理(exception)の処理に入るパラメータを設定してある。
3.デコレータのパラメータを設定
Test_flask_app_異常系
のテストケースにデコレータでは、引数を定義している。
これは、デコレータ内の処理で利用することができる。
今回は、例外処理の際に格納されるエラーメッセージを引数として渡す。
decorators.pyの解説
1.デコレータの引数
デコレータ内で利用する引数の初期化を行うことができる。
exception_message=None
と初期化することで、test.py内で引数を渡さなかった場合に必ずNoneになる。
例えばTest_flask_app_正常系
のtest_sumにアクセスし計算結果を受け取れること
では引数の渡していないためexception_message=None
がデコレータ内に渡される。
一方で、Test_flask_app_異常系
のtest_sumにアクセスしexceptionの処理が実行されること
では、exception_message=b"Invalid Parameter"
を引数として渡しているため、これをデコレータ内で利用できる。
2.for文を利用して複数のGETパラメータでテストを実施
test.pyの各テストケース内で格納したGETパラメータをfor文を利用して取り出す。
取り出した値(var1
とvar2
)をformatメソッドを利用してキーワード引数に埋め込み、アプリケーション側にGETパラメータを送る。
アプリケーションで処理した結果をassert文で評価することによって、テストを行う。
その後、また新たなパラメータで同様の処理を繰り返す。
3.デコレータの引数を用いた分岐
for文の中のif文はtest.pyからexception_message
に値が渡された場合に実施される。
exception_message
に値が渡された場合は、Noneで初期化していることからelse文の処理が行われる。
まとめ
・GETパラメータをアプリケーションに送る方法を説明した。
・GETパラメータをテスト用クライアントからアプリケーションに送る際、キーワード引数とformatメソッドを組み合わせるとテストコードのコード量を圧縮できる。
・デコレータの引数を利用することで、アプリケーションの例外処理に対応し易くなる。
次回
次はPOSTの話を近いうちに。