pytestを使用したFlask APIテスト メモ

* python用テストフレームワーク`pytest`、`pytest-cov`(カバレッジ計測用)を用いたFlask製APIのテスト方法についてメモする。

## 事前準備

* `pytest`インストール

  ```shell
  pip install pytest
  ```

* `pytest-cov`インストール

  ```shell
  pip install pytest-cov
  ```

  

## 実装

### ディレクトリ構成

```
Project
├─ sampleapi
│  ├─app.py
│  │
│  │
│  └─api
│    │  __init__.py
│    │
│    └─ views
│        └─sample.py
└─ test
    ├─  unit_test.py
    └─  __init__.py

```

* `sampleapi`ディレクトリ：テスト対象APIのコードを格納。
* `test`ディレクトリ：テストコードを格納。

### テスト対象API

* `sampleapi/app.py`

  ```python
  from api import app
  
  if __name__ == '__main__':
      app.run()
  ```

  

* `sampleapi/api/init.py`

  ```python
  from flask import Flask
  from .views.sample import sample_router
  
  
  def create_app():
  
      app = Flask(__name__)
  
      app.register_blueprint(sample_router, url_prefix='/api')
  
      return app
  
  
  app = create_app()
  ```

  

* `sampleapi/views/sample.py`

  * GET http://localhost:5000/api/sample リクエストを受け、クエリパラメータ`q`の指定有無に応じたJSONレスポンスを返却する。

  ```python
  from flask import Flask, Blueprint, request
  import json
  
  # Routing Settings
  sample_router = Blueprint('sample_router', __name__)
  
  app = Flask(__name__)
  
  
  @sample_router.route("/sample",  methods=['GET'])
  def get():
  
      res_body = {}
      query = request.args.get('q')
      if query is not None:
          res_body = {
              "id": "XXXYYYZZZ",
              "name": "Sample",
              "query": query
          }
      else:
          res_body = {
              "id": "XXXYYYZZZ",
              "name": "Sample",
              "query": "None"
          }
      return json.loads(json.dumps(res_body))
  ```

  

### テストコード

* `test/__init__.py`:空ファイル

* `test/unit_test.py`:テストコード

  ```python
  import pytest
  # テスト対象APIコードのappをインポート
  from sampleapi.api import app
  
  def test_flask_N001():
      # テスト用コンフィグをtrueに設定
      app.config['TESTING'] = True
      # テスト対象API呼び出し用テストクライアント生成
      client = app.test_client()
  
      # テスト対象API実行
      result = client.get('/api/sample')
      body = result.get_json()
  
      # レスポンス結果と期待値の比較
      assert 'XXXYYYZZZ' == body['id']
      assert 'Sample' == body['name']
      assert 'None' == body['query']
  
  
  def test_flask_N002():
      # テスト用コンフィグをtrueに設定
      app.config['TESTING'] = True
      # テスト対象API呼び出し用テストクライアント生成
      client = app.test_client()
  
      # テスト対象API実行(クエリパラメータ指定あり)
      result = client.get('/api/sample?q=Test')
      body = result.get_json()
  
      # レスポンス結果と期待値の比較
      assert 'XXXYYYZZZ' == body['id']
      assert 'Sample' == body['name']
      # クエリパラメータに指定した値が設定されていることを確認
      assert 'Test' == body['query']
  
  ```

  

## 動作確認

* テスト実行

  ```shell
  pytest -v unit_test.py
  ======================================= test session starts ========================================
  platform win32 -- Python 3.8.3, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- c:\python38\python.exe     
  cachedir: .pytest_cache
  rootdir: C:\Users\user\Desktop\FlaskApi\test
  plugins: cov-2.11.1
  collected 2 items
  
  unit_test.py::test_flask_N001 PASSED                                                          [ 50%]
  unit_test.py::test_flask_N002 PASSED                                                          [100%] 
  
  ======================================== 2 passed in 0.91s ========================================
  ```

* カバレッジ計測

  * C0

    ```shell
    pytest --cov -v unit_test.py
    ~~ 省略 ~~
    ```

  * C1

    ```shell
    pytest --cov --cov-branch -v unit_test.py
    ~~ 省略 ~~
    ```

    



## 参考情報

* [Full pytest documentation](https://docs.pytest.org/en/latest/contents.html)
* [Flaskアプリケーションのテスト](https://msiz07-flask-docs-ja.readthedocs.io/ja/latest/testing.html)
* [pythonのカバレッジをpytest-covで調べる](https://qiita.com/mink0212/items/34b9def61d58ab781714)