Flaskでテスト駆動開発(TDD)を行うためのチュートリアル - 1 テスト用クライアント編

# はじめに
全5回(予定なので変わる可能性あり)にかけて、`Flaskでテスト駆動開発を行うために必要なノウハウを伝授`する。
本記事では、Flaskで開発したWebアプリやAPIを自動でテストするための最低限のサンプルコードとその実行例を紹介する。

1回目 本記事
2回目 [Flaskでテスト駆動開発(TDD)を行うためのチュートリアル - 2 デコレータ編](https://qiita.com/kikusui6192/items/1665099685f9a05820ce)
3回目 [Flaskでテスト駆動開発(TDD)を行うためのチュートリアル - 3 GET編]
(https://qiita.com/kikusui6192/items/f01573f380d5f4e73456)
4回目 執筆中
5回目 執筆中

# 対象読者
- FlaskでこれからWebアプリまたはAPIを開発する方
- テスト自動化の勉強をしたい方

# 概要
Flaskで作成した`Hello, World!`を戻り値とするAPIの最低限のテストをテスト用クライアントを用いて自動で行う。
 
# ディレクトリ構造
本記事で利用するサンプルコードは以下のようなディレクトリ構造で配置してください。

```
flask_01/
├── Dockerfile 
└── app
    ├── flask_app.py
    └── test
        └── test.py
```
# dockerのバージョン
```
$ docker --version
Docker version 19.03.12, build 48a66213fe
```
# コードの準備
## Dockerfile
```: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(テスト対象)
`Hello, World!`を出力するシンプルなAPIを利用する。

``` flask_app.py
from flask import Flask
app = Flask(__name__)

@app.route('/hello_world')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=5000)
```

## test.py(テストコード)
詳細は最後のセクションで説明する。

```test.py
import sys
sys.path.append('../')
# flaskアプリケーションのファイル名のから.pyを消したもの
import flask_app
import unittest

# クラス名は日本語でも動く
class Test_flask_app_正常系(unittest.TestCase):
    # 1. 共通しているパラメータを設定
    def setUp(self):
        self.ENDPOINT    = "http://localhost:5000/{}"
        self.DATA        = None
        self.STATUS      = "200 OK"
        self.STATUS_CODE = 200
        self.ROUTE       = None
    # テストケースは日本語でも動く
    def test_1_hello_worldにアクセスできること(self):
        # ケースごとに必要なパラメータを設定
        self.DATA  = b"Hello, World!"
        self.ROUTE = "hello_world"
        # 2. with分でテスト用クライアントとコンテキストとを結び付ける
        with flask_app.app.test_client() as client:
            # 3. テスト用クライアントを用いてAPIを実行
            response = client.get(self.ENDPOINT.format(self.ROUTE))
        # 4. assert文を利用してテストを実施
        assert response.data        == self.DATA
        assert response.status      == self.STATUS 
        assert response.status_code == self.STATUS_CODE

        return

if __name__ == '__main__':
    unittest.main()
```

# テストを実行
[ディレクトリ構造](#ディレクトリ構造)を確認し、以下のコマンドを実行する。 

```
$ ls
Dockerfile      app
$ docker build -t pytest1 .
~ 省略 ~
$ docker run -rm -it pytest1 /usr/local/bin/python /root/test/test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.004s

OK
```

# test.py(テストコード)の解説
## 1. 共通しているパラメータを設定
`unittest.TestCase`を継承したクラスは、テストケースを実行前に必ず`setUp(self)`を実行する。
具体的には、`test_1_flask_appのにアクセスできること(self)`を実行する前に`setUp(self)`が実行される。
そのため、テストケースが複数あった場合も同様に実行されるため、class内で利用する共通のパラメータの初期化を行うと良い。
厳密には異なるが、`setUp(self)`内で初期化した変数はclass内のあらゆるテストケースで共通利用できるという認識で良い。

## 2. with文でテスト用クライアントとコンテキストとを結び付ける
with構文を用いることでテスト用クライアントを利用することが可能となる。
`flask_app.app.test_client()`の先頭は、テストの対象であるpythonファイルのベースネームから拡張子を取り除いたものである。
今回はテスト対象が`flask_app.py`なので`flask_app`となる。

## 3. テスト用クライアントを用いてAPIを実行
`2.のテスト用クライアントのgetメソッド`を利用して`flask_app.py`に対してAPIを実行する。
実行結果は`responseのプロパティ`に格納される。

## 4. assert文を利用してテストを実施
assert文を用いて`responseのプロパティ`と`1.で設定した想定された結果`を比較する。
今回のテストコードでは、APIの戻り値(`response.data`)とステータス(`response.status`)、ステータスコード(`response.status_code`)の3種類のみ比較する。
他の実行結果と比較したい場合は、以下のpythonの関数を利用すると`responseのプロパティ`を確認することができる。

```

dir(response)
```
そのため、適宜確認が必要なプロパティを選択し、assert文で比較するとよい。
# まとめ
テストコードでFlaskのテスト用クライアントの怪本的な使い方を紹介した。

# 次回
[Flaskでテスト駆動開発(TDD)を行うためのチュートリアル - 2 デコレータ編](https://qiita.com/kikusui6192/items/1665099685f9a05820ce)