[Advanced Python] RESTful Routes for pytest

上一回说道:

$ docker-compose exec api python manage.py recreate_db

# 那么自然也可以触发pytest的命令

$ docker-compose exec api python -m pytest "src/tests"
$ docker-compose exec api-db psql -U postgres

 

Ref: https://testdriven.io/courses/tdd-flask/restful-routes/【本篇对应教程】

Following RESTful best practices, with TDD.

 

 

基本的套路

一、注册一个 Blueprint

首先是 ./src/__init__.py 入口函数。

 import os
 
 from flask import Flask
 from flask_sqlalchemy import SQLAlchemy
 
 
 # instantiate the db
 db = SQLAlchemy()
 
 
 # new
 def create_app(script_info=None):
 
     # instantiate the app
     app = Flask(__name__)
 
     # set config
     app_settings = os.getenv('APP_SETTINGS')
     app.config.from_object(app_settings)
 
     # set up extensions
     # 与app结合起来
     db.init_app(app)
 
     ########################################
 
     # register blueprints
     from src.api.ping import ping_blueprint
     app.register_blueprint(ping_blueprint)
 
     from src.api.users import users_blueprint
     app.register_blueprint(users_blueprint)
 
     # shell context for flask cli
     @app.shell_context_processor
     def ctx():
         return {'app': app, 'db': db} 
 
     return app 

 

 

二、通过 Blueprint 定义一个API

定义好一个REST API。

 # src/api/user.py

from
flask import Blueprint, request from flask_restx import Resource, Api from src import db from src.api.models import User users_blueprint = Blueprint('users', __name__) api = Api(users_blueprint) class UsersList(Resource): def post(self): print("In UsersList.post()") post_data = request.get_json() username = post_data.get('username') email = post_data.get('email') db.session.add( User(username=username, email=email) ) db.session.commit()
# 测试的内容 response_object
= { 'message': f'{email} was added!' } return response_object, 201 api.add_resource(UsersList, '/users')

 

 

三、测试 Blueprint

直接对 这个接口进行测试。

test_app 从哪里来?从fixture那里来。

# src/tests/test_users.py

import json

def test_add_user(test_app, test_database):
client
= test_app.test_client() resp = client.post( '/users', data=json.dumps({ 'username': 'michael', 'email': 'michael@testdriven.io' }), content_type='application/json', )
data
= json.loads(resp.data.decode())
assert resp.status_code == 201 assert 'michael@testdriven.io was added!' in data['message']

 

    • Exception --> REST API

What about errors and exceptions? Like:

    1. A payload is not sent
    2. The payload is invalid -- i.e., the JSON object is empty or it contains the wrong keys
    3. The user already exists in the database

 

[例如]

在上面的测试函数例子的所在文件中,可以继续添加更多的测试函数。

先 invoke rest api 一次,然后再invoke一次,就会导致 already exists。

def test_add_user_duplicate_email(test_app, test_database):
    client = test_app.test_client()
client.post(
'/users', data=json.dumps({ 'username': 'michael', 'email': 'michael@testdriven.io' }), content_type='application/json', )
resp
= client.post( '/users', data=json.dumps({ 'username': 'michael', 'email': 'michael@testdriven.io' }), content_type='application/json', )
data
= json.loads(resp.data.decode()) assert resp.status_code == 400 assert 'Sorry. That email already exists.' in data['message']

 

    • REST API --.> DB

针对“”用户已经存在”的测试脚本 写对应的 REST API。

这里有定义user_param,对参数的类型进行了检测,叫做:Validation

 class UsersList(Resource):
 
     @api.expect(user_param, validate=True)  # new
     def post(self):
         print("In UsersList.post()")
         post_data = request.get_json()
 
         username  = post_data.get('username')
         email     = post_data.get('email')
 
         #-----------------------------------------------
 
         response_object = {}
# 要添加新用户 by email,但已经被注册了。 user
= User.query.filter_by(email=email).first() if user: response_object['message'] = 'Sorry. That email already exists.' return response_object, 400 db.session.add(User(username=username, email=email)) db.session.commit() response_object = { 'message': f'{email} was added!' } return response_object, 201

 

增加 HTTP GET 的话,示范如下代码。

先提一句,marshal 的意义,参见:https://www.codenong.com/cs106272440/

person = api.model('Person', {
    'name': fields.String(
        attribute="private_name",
        default="John",
        required=True,
        readonly=True,
        title="person_title",
        description="person_description",
    ),
    'age': fields.Integer,
})

school = api.model('School', {
    'name': fields.String,
    'students': fields.List(fields.Nested(person)),
    'teachers': fields.List(fields.Nested(person)),
})

@api.route('/my-resource/<id>', endpoint='my-resource')
class MyResource(Resource):
    @api.marshal_with(school, as_list=True)     # 作为输出model
    @api.expect(school)                         # 作为输入model
    def get(self, id):
        return {}

 

    • GET single user Route

[REST API --> DB]

获取某一个 user id的内容。

 class Users(Resource):
 
     @api.marshal_with(user_param)
     def get(self, user_id):
         user = User.query.filter_by(id=user_id).first()
         if not user:
             api.abort(404, f"User {user_id} does not exist")
         return user, 200 

api.add_resource(Users, '/users/<int:user_id>')

 

    • GET all users Route

[Client --> REST API]

首先,我们需要先添加若干用户;然后再get获得所有用户。

def test_all_users(test_app, test_database, add_user):
test_database.session.query(User).delete()
add_user('michael', 'michael@mherman.org') add_user('fletcher', 'fletcher@notreal.com')
client
= test_app.test_client() resp = client.get('/users') data = json.loads(resp.data.decode())
assert resp.status_code == 200 assert len(data) == 2 assert 'michael' in data[0]['username'] assert 'michael@mherman.org' in data[0]['email'] assert 'fletcher' in data[1]['username'] assert 'fletcher@notreal.com' in data[1]['email']

 

 

 

Pytest Commands

一、参考命令 

# normal run
$ docker-compose exec api python -m pytest "src/tests"

# disable warnings
$ docker-compose exec api python -m pytest "src/tests" -p no:warnings

# run only the last failed tests
$ docker-compose exec api python -m pytest "src/tests" --lf

# run only the tests with names that match the string expression
$ docker-compose exec api python -m pytest "src/tests" -k "config and not test_development_config"

# stop the test session after the first failure
$ docker-compose exec api python -m pytest "src/tests" -x

# enter PDB after first failure then end the test session
$ docker-compose exec api python -m pytest "src/tests" -x --pdb

# stop the test run after two failures
$ docker-compose exec api python -m pytest "src/tests" --maxfail=2

# show local variables in tracebacks
$ docker-compose exec api python -m pytest "src/tests" -l

# list the 2 slowest tests
$ docker-compose exec api python -m pytest "src/tests" --durations=2

 

End.

posted @ 2020-11-29 07:54  郝壹贰叁  阅读(129)  评论(0编辑  收藏  举报