1 学习目标
- 能够使用代码实现蓝图对项目进行模块化
- 能够说出断言的作用
- 能够说出实现单元测试步骤
- 能够说出单元测试所执行方法的定义规则
2 蓝图(Blueprint)
2.1 模块化
随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过python的模块化管理,于是针对一个简单的flask程序进行模块化处理。但是发现.py文件直接报错,代码无法继续写下去,所以在flask程序中,使用传统的模块化是行不通的,需要flask提供一个特有的模块化处理方式,lask内置了一个模块化处理的类,即Blueprint
2.2 Blueprint 概念
简单来说,Blueprint 是一个存储操作方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。
Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
- 一个应用可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下,比如 “/”、“/sample”或者子域名
- 在一个应用中,一个模块可以注册多次
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个应用初始化时,就应该要注册需要使用的Blueprint
但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
2.3 初识蓝图
蓝图/Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效
使用蓝图可以分为三个步骤
- 1. 创建一个蓝图对象
admin=Blueprint('admin',__name__)
- 2. 在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
@admin.route('/')
def admin_home():
return 'admin_home'
- 3. 在应用对象上注册这个蓝图对象
app.register_blueprint(admin,url_prefix='/admin')
当这个应用启动后,通过/admin/可以访问到蓝图中定义的视图函数
2.4 蓝图例子
2.4.1 循环引入
新建项目:
新建demo:(main.py 主文件)
如果将所有的视图函数都定义在一个py文件中,那以后将难以维护,所以我们这里可以考虑分模块,如下:
将order_list放入另一个py文件中:
但是出问题了:我们运行main.py 结果url_map中没有order_list的url映射:
那怎么办,导入呗:
再次运行,发现报错了:
因为出现了循环导入(循环引用)问题:main中导入order,并且 order中导入main,如下图:
2.4.2 使用蓝图
使用蓝图解决问题:
main中导入蓝图,注册蓝图:
运行:发现ok
2.4.3 总结
2.5 模块形式使用蓝图
一般蓝图我们都是一个模块。
新建一个包:cart (python package)
将main中的cart_list分离到cart模块中
cart模块中是有蓝图的,我们就在init中新建蓝图:
注意:这里要将视图函数中的所有内容导入,要不然当前蓝图是不知道都有哪些视图函数的。
那将函数放哪呢? 我们再新建一个views.py 专门放视图函数:
在main中注册蓝图:
注:从 cart 中导入 cart_blu 时,可能会报错,解决方案:将Flask_day04_03_blueprint 文件夹设置为 Sources Root
(选中文件夹右击 --> Mark Directory as --> Sources Root)
运行:
2.6 运行机制
- 蓝图是保存了一组将来可以在应用对象上执行的操作,注册路由就是一种操作
- 当在应用对象上调用 route 装饰器注册路由时,这个操作将修改对象的url_map路由表
- 然而,蓝图对象根本没有路由表,当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项
- 当执行应用对象的 register_blueprint() 方法时,应用对象将从蓝图对象的 defered_functions 列表中取出每一项,并以自身作为参数执行该匿名函数,即调用应用对象的 add_url_rule() 方法,这将真正的修改应用对象的路由表
2.7 蓝图的url前缀
- 当我们在应用对象上注册一个蓝图时,可以指定一个url_prefix关键字参数(这个参数默认是/)
-
在应用最终的路由表 url_map中,在蓝图上注册的路由URL自动被加上了这个前缀,这个可以保证在多个蓝图中使用相同的URL规则而不会最终引起冲突,只要在注册蓝图时将不同的蓝图挂接到不同的自路径即可
-
url_for
url_for('admin.index') # /admin/
2.8 蓝图的静态文件及静态文件访问(注册静态路由)
新建static,并且放入图片a.png
那如何访问呢?
我们来看下蓝图的源码(点击BluePrint()):(发现蓝图没有默认指定static_folder和其他的)
但是flask默认指定了(点击Flask()):
而url_map中也只有一个static路径,指向的也是flask的静态文件夹:
所以我们去访问的时候,是找不到的:
那既然Blueprint没有默认指定static_folder,那我们就指定呗:
你会发现虽然有了,但是蓝图的与app的静态url竟然一样的:
再次访问还是找不到(因为先匹配的app的static,但是没有a.png):
怎么办? 我们需要区分app和蓝图的url,创建蓝图时指定一个url前缀(url_prefix):
既然有前缀了,绑定url映射的时候,就不用再加前缀了:
运行:(发现终于不一样了)
访问:ok
2.9 模板
接下来我们顺便讨论一下模板,蓝图中新建模板文件夹templates,模板文件cart.html:
视图函数渲染:
访问:但是找不到
这是因为蓝图也没有默认指定模板文件夹,指定一下即可(和静态文件访问类似):
访问,没问题:
模板的优先级
如果app中也有相同的模板:
访问:
发现app的优先级大于蓝图的。
注:如果在 templates 中存在和 cart/templates 同名文件,则系统会优先使用 templates 中的文件 参考链接:https://stackoverflow.com/questions/7974771/flask-blueprint-template-folder
3 单元测试
3.1 为什么要测试?
Web程序开发过程一般包括以下几个阶段:[需求分析,设计阶段,实现阶段,测试阶段]。其中测试阶段通过人工或自动来运行测试某个系统的功能。目的是检验其是否满足需求,并得出特定的结果,以达到弄清楚预期结果和实际结果之间的差别的最终目的。
3.1.2 测试的分类:
测试从软件开发过程可以分为:
- 单元测试
- 对单独的代码块(例如函数)分别进行测试,以保证它们的正确性
- 集成测试
- 对大量的程序单元的协同工作情况做测试
- 系统测试
- 同时对整个系统的正确性进行检查,而不是针对独立的片段
在众多的测试中,与程序开发人员最密切的就是单元测试,因为单元测试是由开发人员进行的,而其他测试都由专业的测试人员来完成。所以我们主要学习单元测试。
3.1.3 什么是单元测试?
程序开发过程中,写代码是为了实现需求。当我们的代码通过了编译,只是说明它的语法正确,功能能否实现则不能保证。 因此,当我们的某些功能代码完成后,为了检验其是否满足程序的需求。可以通过编写测试代码,模拟程序运行的过程,检验功能代码是否符合预期。
单元测试就是开发者编写一小段代码,检验目标代码的功能是否符合预期。通常情况下,单元测试主要面向一些功能单一的模块进行。
举个例子:一部手机有许多零部件组成,在正式组装一部手机前,手机内部的各个零部件,CPU、内存、电池、摄像头等,都要进行测试,这就是单元测试。
在Web开发过程中,单元测试实际上就是一些“断言”(assert)代码。
断言就是判断一个函数或对象的一个方法所产生的结果是否符合你期望的那个结果。 python中assert断言是声明布尔值为真的判定,如果表达式为假会发生异常。单元测试中,一般使用assert来断言结果。
3.2 断言的介绍
3.2.1 断言方法的使用
看下图黄箭头:
断言语句类似于:
if not expression:
raise AssertionError
AssertionError
3.2.2 例子
新建项目:
新建demo:
如果我们在调用div方法时传参错误,导致报错.。这时候我们作为优秀的程序员,应该让我们定义的方法更加完善。如果给div传递错误的参数,我们的程序应该给予友好提示,如下图:
看到下边的错误提示,是一个AssertionError,这就是我们给出的断言错误。这样就ok了,使用div的程序员就可以很清楚的知道原来是他传递的参数有问题。
断言解释:
如果断言后边跟上的表达式为真,则断言成功;
如果后边的表达式为假,则断言失败,程序停止运行,返回断言错误: “值类型不正确”
完善一下这个断言:增加除数为0情况
3.3 单元测试编写
3.3.1 准备工作
先写一个要被测试的demo2:
from flask import Flask, jsonify
from flask import request
app = Flask(__name__)
@app.route('/')
def index():
return 'index'
# 用户登录
# 1. 如果传入的参数不足,会返回 errcode = -2
# 2. 如果传入的账号与密码不正确会返回 errcode = -1
# 3. 如果账号与密码都正确, errcode = 0
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
# 判断参数是否为空
if not all([username, password]):
result = {
"errcode": -2,
"errmsg": "params error"
}
return jsonify(result)
# 如果账号密码正确
# 判断账号密码是否正确
if username == 'itheima' and password == 'python':
result = {
"errcode": 0,
"errmsg": "success"
}
return jsonify(result)
else:
result = {
"errcode": -1,
"errmsg": "wrong username or password"
}
return jsonify(result)
if __name__ == '__main__':
app.run(debug=True)
接下来我们来测试一下这些逻辑。
3.3.2 postman 测试
我们先通过postman来测试一下,如果没有参数:
如果用户名密码错误:
如果用户名密码正确:
测试都通过。
3.3.3 单元测试
接下来我们通过代码来做单元测试:
新建单元测试demo2_unittest.py:
上述代码说明:
app.test_client().post() 可以发起post请求:第一个参数是请求url,第二个参数是传递的数据
返回值为response
response.data就是获取到视图函数返回的数据
如下:
视图函数返回的都是json数据,我们就可以把json数据转换为字典使用:
继续分析代码:
assertIsNotNone,是判断内容不为None.
assertIn,是判断errorcode是否在json_dcit中.
assertEqual,是判断errcode是否等于-2.
我们再来看一下单元测试中的setUp方法:
再来看testing:
如果将app.testing 设置为 False:碰巧我们要测试的代码中有bug
测试:
发现报错信息有点不对头,不是应该抛出被测试的代码中具体哪里错误么?
但是这里如果不是testing模式的话,就只会报出自己哪一行报错了,不具体
那怎么办? 设置testing = True
如下:
这是就可以看到具体错误的位置了
3.4 常用的断言方法
assertEqual 如果两个值相等,则pass
assertNotEqual 如果两个值不相等,则pass
assertTrue 判断bool值为True,则pass
assertFalse 判断bool值为False,则pass
assertIsNone 不存在,则pass
assertIsNotNone 存在,则pass
3.5 数据库的测试
将之前的图书作者案例拿过来,如下:
新建测试demo:
要做的事情:
代码:
测试如下: