2018年7月20日金曜日

flasgger の Marshmallow Schemas を使ってみた

概要

flasgger には swagger ファイルをいろいろな形式で定義することができます
その中に Marshmallow Schemas というものがあります
Pythonのライブラリに marshmallow というものがありこれはオブジェクトのシリアライズを簡単に行うことができます
flasgger で marshmallow を使うでき、これを使うと Python でクラスを定義するように swagger を定義することができます
今回はサンプルを動かしてみました

環境

  • macOS X 10.13.6
  • Python 3.6.5
  • flasgger 0.9.0
  • apispec 0.38.0

インストール

  • pipenv install flasgger marshmallow apispec=="0.38.0"

apispec というモジュールに依存しているのですが、これの最新版が 0.39.0 になっています
まだ flasgger 側が 0.39.0 に対応していないため一つ前のバージョンを明示的に指定します
Pull request はすでに出ているようでこれがマージされれば apispec の最新版が使えます

アプリ作成

Marshmallow Schemas を使ったサンプルアプリを作成します
swagger 定義をコードに記載するので既存の定義ファイルは使いません

  • vim test_app.py
from flask import Flask, jsonify
from flasgger import Swagger, SwaggerView, Schema, fields


class Color(Schema):
    name = fields.Str()

class Palette(Schema):
    pallete_name = fields.Str()
    colors = fields.Nested(Color, many=True)

class PaletteView(SwaggerView):
    parameters = [
        {
            "name": "palette",
            "in": "path",
            "type": "string",
            "enum": ["all", "rgb", "cmyk"],
            "required": True,
            "default": "all"
        }
    ]
    responses = {
        200: {
            "description": "A list of colors (may be filtered by palette)",
            "schema": Palette
        }
    }

    def get(self, palette):
        """
        Colors API using schema
        This example is using marshmallow schemas
        """
        all_colors = {
            'cmyk': ['cian', 'magenta', 'yellow', 'black'],
            'rgb': ['red', 'green', 'blue']
        }
        if palette == 'all':
            result = all_colors
        else:
            result = {palette: all_colors.get(palette)}
        return jsonify(result)

app = Flask(__name__)
swagger = Swagger(app)

app.add_url_rule(
    '/colors/<palette>',
    view_func=PaletteView.as_view('colors'),
    methods=['GET']
)

app.run(debug=True)

definitions の役割は Schema クラスを継承した子クラスとして定義します
paramters と responses の部分は SwaggerView クラスを継承した子クラスとして定義します
ルーティングは app.add_url_rule で定義します

アプリ起動

  • pipenv run python3 test_app.py

localhost:5000 で起動します

動作確認

  • curl http://localhost:5000/colors/all/

で動作します
/apidocs/ で swagger UI が確認できます

最後に

参考サイト

2018年7月19日木曜日

flasgger 入門

概要

直接 swagger.yml の情報を Python のコードにも書けるのですが今回は YAML ファイルは分離して動かしてみたいと思います

環境

  • macOS X 10.13.6
  • Python 3.6.5
  • flasgger 0.9.0

インストール

  • pipenv install flasgger

swagger.yml 作成

  • vim test.yml
parameters:
  - name: palette
    in: path
    type: string
    enum: ['all', 'rgb', 'cmyk']
    required: true
    default: all
definitions:
  Palette:
    type: object
    properties:
      palette_name:
        type: array
        items:
          $ref: '#/definitions/Color'
  Color:
    type: string
responses:
  200:
    description: A list of colors (may be filtered by palette)
    schema:
      $ref: '#/definitions/Palette'
    examples:
      rgb: ['red', 'green', 'blue']

アプリ作成

  • vim test.py
from flask import Flask, jsonify
from flasgger import Swagger
from flasgger.utils import swag_from

app = Flask(__name__)
Swagger(app)

@app.route('/colors/<palette>/')
@swag_from('test.yml')
def index(palette):
    all_colors = {
        'cmyk': ['cian', 'magenta', 'yellow', 'black'],
        'rgb': ['red', 'green', 'blue']
    }
    if palette == 'all':
        result = all_colors
    else:
        result = {palette: all_colors.get(palette)}
    return jsonify(result)

app.run(debug=True)

単純な JSON を返却するだけのアプリです
定義した test.yml はデコレーションを使って参照します

@swag_from('test.yml')

Swagger(app) することで swagger UI と JSON を参照できるようにしています
あとは @app.route('/colors/<palette>/') デコレーションでルーティングを定義します
<palette> は関数内で変数として参照することができます

実行

  • pipenv run python3 test.py

localhost:5000 でアプリ起動します

動作確認

  • curl http://localhost:5000/colors/all/

で以下のレスポンスが返ってきます

{
  "cmyk": [
    "cian", 
    "magenta", 
    "yellow", 
    "black"
  ], 
  "rgb": [
    "red", 
    "green", 
    "blue"
  ]
}

http://localhost:5000/apidocs/ にアクセスすると swagger UI が表示されます

最後に

Python3 + flasgger を使って swagger.yml ファイルからアプリを作成してみました
これが基本的な使い方になると思います

少し気になったのはコントローラの分離方法です
やり方がわかったらこの辺りも紹介したいと思います

参考サイト

2018年7月18日水曜日

Fabric2 で Mock テストをやる方法

概要

Fabric2 はデプロイツールで対象のサーバに SSH してコマンドを実行することができます
fabric を含むコードをテストする場合対象のサーバにアクセスできない状況などあると思います
その場合に unittest.Mock を使うことで擬似的に SSH してテストすることができます
今回はその方法を紹介します

環境

  • macOS 10.13.5
  • Python 3.6.5
  • fabric 2.1.3

テスト対象のコード

  • vim my_fab.py
from fabric import Connection


class MyFab(object):
    def __init__(self):
        self.conn = Connection("root@172.28.128.3", connect_kwargs = { "password": "xxxxxxx" })

    def call(self):
        return self.conn.run('uname -s')


if __name__ == "__main__":
    f = MyFab()
    res = f.call()
    print(res)

これで実行すると当然ですが指定の IP アドレスに SSH 接続しにいきます
これを Mock 化して実際には SSH しないでテストしたいと思います

Mock を使ったテストコード

  • vim test_my_fab.py
import unittest
from my_fab import MyFab
from unittest.mock import patch, MagicMock


class TestMyFabClass(unittest.TestCase):

    @patch('my_fab.Connection')
    def test_call(self, mock):
        mock_res = MagicMock(return_value='Linux')
        mock.return_value.run = mock_res
        f = MyFab()
        res = f.call()
        print(res)
        self.assertEqual(res, 'Linux')


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

ポイントは @patch('my_fab.Connection') です
fabric.Connection ではなく自分で作成したモジュールを指定します

あとは Connection はメソッドなので mock.return_value.run で返り値を設定します

最後に

fabric2 で Mock を使ったテスト方法を紹介しました
run と同じように put なども Mock 化すればテストできると思います

2018年7月17日火曜日

Python3 でオブジェクトがどのモジュールかどのクラスかを探す方法

概要

例えばライブリを使っている場合にどのモジュールから作成されたオブジェクトなのかわからなくなることがあると思います
そんな場合に便利かなと思います

環境

  • macOS 10.13.5
  • Python 3.6.5

サンプルコード

  • vim car.py
class Car(object):
    def __init__(self, name , color):
        self.name = name
        self.color = color
        self.dist = 0

    def run(self):
        self.dist += 10

if __name__ == "__main__":
    c = Car('fit', 'color')
    print(c.__module__ + "." + c.__class__.__name__)

最後の print(c.__module__ + "." + c.__class__.__name__) で表示しています

  • python3 car.py

上記の場合は __main__.Car と表示されます

別のコードから参照すると

  • vim car_main.py
from car import Car

c = Car('fit', 'black')
print(c.__module__ + "." + c.__class__.__name__)

これだと car.Car と表示されます

参考サイト

2018年7月16日月曜日

Python3 の unittest で Mock を使ってみる

概要

unittest は Python3 に標準で搭載されているテストモジュールです
簡単なクラスを作成してテストしてみました

環境

  • macOS 10.13.5
  • Python 3.6.5

テスト対象のコード

  • vim mock.py
import requests
import json

class TestClient(object):
    def req_get(self):
        return json.loads(requests.get('https://kaka-request-dumper.herokuapp.com/').text)

    def req_post(self):
        return json.loads(requests.post('https://kaka-request-dumper.herokuapp.com/').text)

普通のテストコード

  • vim test_mock.py
import unittest
from mock import TestClient


class TestMockClass(unittest.TestCase):

    def test_get(self, mock):
        cli = TestClient()
        res = cli.req_get()
        self.assertEqual(res['method'], 'GET')

    def test_post(self):
        cli = TestClient()
        res = cli.req_post()
        self.assertEqual(res['method'], 'POST')


if __name__ == '__main__':
    unittest.main()
  • python3 -m unittest test_mock.py

これでテストすると実際にサーバにアクセスしてテストします
これを Mock 化してサーバにアクセスしないで各メソッドをテストします

Mock 化する

  • vim test_mock.py
import unittest
import json
from mock import TestClient
from unittest.mock import patch, MagicMock


class TestMockClass(unittest.TestCase):

    @patch('mock.requests')
    def test_get(self, mock):
        mock_res = MagicMock(text=json.dumps({'method': 'GET'}))
        mock.get.return_value = mock_res
        cli = TestClient()
        res = cli.req_get()
        self.assertEqual(res['method'], 'GET')

    @patch('mock.requests')
    def test_post(self, mock):
        mock_res = MagicMock(text=json.dumps({'method': 'POST'}))
        mock.post.return_value = mock_res
        cli = TestClient()
        res = cli.req_post()
        self.assertEqual(res['method'], 'POST')


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

説明

MagicMock クラスを使ってモックを作成します
ポイントは以下の 2 行です

mock_res = MagicMock(text=json.dumps({'method': 'GET'}))
mock.get.return_value = mock_res

MagicMock でモックが返却するレスポンスを作成します
今回は requests.get が JSON を返却して、その JSON 内の method というキーをチェックします
request.get.text で JSON 文字列が受け取れなければいけません
なので MagicMock(text=json.dumps({'method': 'GET'})) という感じでレスポンスを作成します
text=request.get.text の最後の .text になります
.text を参照したときに JSON 文字列が返ってきますよという宣言になります
そして作成したレスポンスを mock.get.return_value = mock_res に設定します
mock オブジェクトはテスト用の関数 (test_get) の引数として受け取っています
これは何かと言うとモック化した requests になります
@patch('mock.requests') のデコレータを付与することでそれを実現しています
mock オブジェクト (requests) の get の return_value を先ほど作成したモックレスポンスとして設定しています

こうすることで実際にサーバにアクセスすることなくサーバと同等のレスポンスを返却する Mock を定義することができます

  • python3 -m unittest test_mock.py

これで実行すると実際にサーバにアクセスせずテストが成功することが確認できると思います

Tips

1 つのテスト中で複数のメソッドやクラスに対して @patch を当てることも可能です
例えば今回のサンプルであれば

    @patch('mock.requests')
    @patch('mock.json')
    def test_get(self, jmock, reqmock):

こんな感じで複数のモジュールに対してモックを当たられます
複数のモックを受け取るためにテストに対して複数の引数を指定しましょう
上に指定したモックが後ろの引数に入る点に注意しましょう

最後に

unittest の Mock 機能を使ってみました
基本的にはレスポンスを Mock でエミュレートして擬似的なレスポンス情報を返却させる感じだと思います
MagicMock には他にもたくさんの機能があるので詳細は参考サイトにある公式のページを参考にしてください

参考サイト

2018年7月15日日曜日

Python3 で unittest 超入門

概要

unittest は Python3 に標準で搭載されているテストモジュールです
簡単なクラスを作成してテストしてみました

環境

  • macOS 10.13.5
  • Python 3.6.5

テスト対象のコード

  • vim car.py
class Car(object):
    def __init__(self, name , color):
        self.name = name
        self.color = color
        self.dist = 0

    def run(self):
        self.dist += 10

テストコード

  • car_test.py
import unittest
from car import Car


class TestCarClass(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print('setUpClass')

    def setUp(self):
        print('setUp')

    @classmethod
    def tearDownClass(cls):
        print('tearDownClass')

    def tearDown(self):
        print('tearDown')

    def test_run(self):
        c1 = Car('note', 'black')
        c1.run()
        self.assertEqual(c1.dist, 10)

    def test_name(self):
        self.c = Car('fit', 'silver')
        self.assertEqual(self.c.name, 'fit')


if __name__ == '__main__':
    unittest.main()
  • python3 -m unittest test.py

テストを実行するときは -m で unittest を指定します
テストのクラスは unittest.TestCase を継承する必要があります
setUp と tearDown は各テストが実行される前と後で毎回呼ばれます
setUpClass と tearDownClass はこのテストが実行される際に最初と最後で一度だけ呼ばれます

テストには命名規則があり test_xxx という感じで test が先頭に付与される必要があります

他に使える assert は公式のページに記載があります

最後に

他にも minitest や pytest というライブラリがあります
標準の unittest もかなり機能はそろっているので正直これで十分なケースがほとんどかなと思います

2018年7月14日土曜日

simctl コマンドを使って Xcode を起動しないでシミュレータだけを起動する方法

概要

simctl コマンドはシミュレータを制御できるコマンドです
Xcode9 で使おうとしたら使えなかったので対処しました
なお、Xcode Command Line Tools はインストール済みになります

環境

  • macOS 10.13.5
  • Xcode 9.4.1 (9F2000)

起動方法

  • xcrun simctl list

で起動したいシミュレータの UUID を取得します

  • xcrun simctl boot 90885073-EAD2-4DE8-B813-E289A878C6AC

で起動します
これだとまだ GUI で開きません

  • open "/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/"

これでシミュレータが起動します

参考サイト