Flask-单元测试

时光总嘲笑我的痴心妄想 提交于 2019-12-16 13:47:39

Flask-单元测试

敏捷开发(agile development)
scrum
结对编程
测试驱动开发(TDD): Test driven development

单元测试(unit testing)是开发者自己编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。

注意单元测试是开发人员自己负责
unittest

Pytest是 python的一种unittest框架,与python自带的unittest测试框架类似,但是比unittest框架使用起来更简洁,效率更高。

  • 执行测试过程中可以将某些测试跳过,或者对某些预期失败的case标记成失败
  • 能够支持简单的单元测试和复杂的功能测试
  • 支持重复执行失败的case
  • 支持运行由nose, unittest编写的测试case
  • 具有很多第三方插件,并且可以自定义扩展
  • 方便的和持续集成工具集成
  • 支持参数化

pytest fixture用途
1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现
2.测试用例的前置条件可以使用fixture实现
3.支持经典的xunit fixture ,像unittest使用的setup和teardown
4.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题

安装pytest-flask扩展

pip install pytest-flask

Dev - test - demo - product

为了测试时方便传入不同的配置参数 (如使用不同的数据库和其他参数),我们需要使用工厂模式创建flask的app对象,改造代码如下

1、创建school/settings.py文件,统一放置项目配置参数

SECRET_KEY = 'q234asdfad@#$AdfS*UNFs'
# 设置数据库连接字符串
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:Vff123456@127.0.0.1/mouse?charset=UTF8MB4'
# 不跟踪修改,不设置会有警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 每页条数
COUNT_PER_PAGE = 10

2、修改application/init.py文件,加入create_app方法

#application/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

# 初始化LoginManager
login_manager = LoginManager()
# 设为 'strong' 时,Flask-Login 会记录客户端 IP 地址和浏览器的用户代理信息,如果发现异动就登出用户
login_manager.session_protection = 'strong'
# login_view 属性设置登录页面
login_manager.login_view = 'users.login'
# 定制未授权访问提示信息
login_manager.login_message = '访问该功能需要登录'

# 创建数据库连接
db = SQLAlchemy()

def create_app(config=None):
    app = Flask(__name__)

    if config is not None:
        app.config.from_object(config)

    from application.home.views import home
    from application.users.views import users

    # 注册蓝图
    app.register_blueprint(home, url_prefix='')
    app.register_blueprint(users, url_prefix='/users')

    db.init_app(app)
    login_manager.init_app(app)

    return app

3、修改manager.py文件

from application import create_app, db
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand

if __name__ == '__main__':
    app = create_app('settings')

    manager = Manager(app)
    # 数据库迁移相关
    migrate = Migrate(app, db)
    manager.add_command('db', MigrateCommand)

    manager.run()

创建pytest全局文件conftest.py,和application放同级

1.conftest.py文件名字是固定的,不可以做任何修改
2.文件和用例文件在同一个目录下,那么conftest.py作用于整个目录
3.conftest.py文件不能被其他文件导入
4.所有同目录测试文件运行前都会执行conftest.py文件

如果我们在编写测试用例的时候,每一个测试文件里面的用例都需要先登录后才能完成后面的操作,那么们该如何实现呢?这就需要我们掌握conftest.py文件的使用了

# /school/conftest.py

import pytest
from application import create_app, db

@pytest.fixture
def app():
    app = create_app('test_settings')
    return app

创建测试用例模块 /school/tests,放在里面的测试用例文件都要用test_开头,才会被pytest执行

创建_school_tests/test_application.py

import flask

def test_app(app):
    assert isinstance(app, flask.Flask)

执行用例 (在school目录下)

$ python -m pytest tests
# conftest.py

# session表示该fixture作用于整个测试过程,只创建一次
@pytest.fixture(scope='session')
def db(app, request):
    database.app = app
    database.create_all()

    def teardown():
        database.drop_all()

    request.addfinalizer(teardown)
    return database

# function表示该fixture会在每个测试方法都执行
@pytest.fixture(scope='function')
def session(db, request):

    session = db.create_scoped_session()
    db.session = session

    def teardown():
        session.remove()

    request.addfinalizer(teardown)
    return session

测试模型models

创建model测试用例_school_tests/test_user_model.py

from application.users.models import Users, Class

def test_create_user(session):
    username = 'carmack'
    fullname = '肖世荣'
    password = 'ASDFAD@#23232'

    user = Users(username=username, fullname=fullname, password=password)
    session.add(user)
    session.commit()

    assert user.id is not None
    assert Users.query.count() == 1
    assert user.status == 1
    assert user.vote_type == 'go'
    assert user.get_vote_button() == 'stop'
    assert user.get_vote_color() == 'green'  

# 测试user和class关联关系
def test_user_class_relation(session):
   user1 = Users(username='jungle', fullname='林林', password='23434213sSWE')
   user2 = Users(username='ken', fullname='郑冠', password='2343asSWE')
   c1 = Class(name='python1904')
   c2 = Class(name='python1905')
   user1.uclass = c1
   user2.uclass = c1
   assert len(c1.users) == 2
   assert c1.users[0].username == 'jungle'

   session.add_all([user1, user2, c1, c2])
   session.commit()

   c1_obj = Class.query.filter_by(name='python1904').first()
   assert c1_obj is not None
   assert len(c1_obj.users) == 2

测试视图views

from application.users.models import Users
from flask import get_flashed_messages
from werkzeug.security import check_password_hash
import pytest
import sqlalchemy

# 测试成功的注册
# client 是Werkzeug自带的测试客户端
def test_valid_register(client):
    # 测试get请求下register页面是否正确
    response = client.get('/users/register')
    assert response.status_code == 200
    assert '欢迎注册' in bytes.decode(response.data)

    # 测试post请求
    data = {'username': 'morning', 'fullname': '黎明', 'password': '123456'}
    response = client.post('/users/register', data=data)

    assert response.status_code == 302
    assert len(get_flashed_messages()) == 1
    assert '注册成功' in  get_flashed_messages()[0] 

    user = Users.query.filter_by(username=data['username']).one()
    assert user.fullname == data['fullname']
    assert user.password

    assert check_password_hash(user.password, data['password'])

# 注册失败的条件更需要测试
# 测试不满足条件的注册
def test_invalid_register(client):
    response = client.get('/users/register')

    data = {'username': '    ', 'fullname': '黎明' * 200, 'password': '123456'}
    response = client.post('/users/register', data=data)

    assert response.status_code == 200
    assert '用户名必填' in bytes.decode(response.data)
    assert '姓名不能大于128位' in bytes.decode(response.data)

    # 测试抛异常情况
    with pytest.raises(sqlalchemy.orm.exc.NoResultFound):
        Users.query.filter_by(username=data['username']).one()
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!