在Python的Flask框架中实现单元测试的教程
|
概要 在前面的章节里我们专注于在我们的小应用程序上一步步的添加功能上。到现在为止我们有了一个带有数据库的应用程序,可以注册用户,记录用户登陆退出日志以及查看修改配置文件。 在本节中,我们不为应用程序添加任何新功能,相反,我们要寻找一种方法来增加我们已写代码的稳定性,我们还将创建一个测试框架来帮助我们防止将来程序中出现的失败和回滚。 让我们来找bug 在上一章的结尾谈到,我故意在应用程序中引入一个bug。接下来让我描述一下它是什么样的bug,然后看看当我们的程序不按照我们意愿执行的时候,它在其中又起了什么样的影响。 应用程序的问题在于,没有保证用户昵称的唯一性。用户昵称是由应用程序自动初始化的。我们首先会考虑使用OpenID provider给出的用户的昵称,然后再考虑使用Email信息中的用户名部分作为用户的昵称。但如果出现重复的昵称,则后面的用户将无法注册成功。更糟糕的是,在修改用户配置的表单中,我们允许用户任意更改他们的昵称,但我们仍然没有对昵称冲突进行检查。 当我们分析完错误产生时应用程序的行为之后,我们将会定位这些问题。 Flask 的调试功能 那么让我们看看当bug被触发时,会出现什么现象。 让我们从创建一个崭新的数据库,在linux下,执行: rm app.db ./db_create.py 在Windows下,执行: del app.db flask/Scripts/python db_create.py我们需要两个OpenID的账号来重现这个bug。当然这两个账号最理想的状态是来自来个不同的拥有者,那样可以避免他们的cookie把情况搞的更复杂。通过如下步骤创建冲突的昵称:
lalchemy.exc.IntegrityError IntegrityError: (IntegrityError) column nickname is not unique u'UPDATE user SET nickname=?,about_me=? WHERE user.id = ?' (u'dup',u'',2) 错误的后面是这个错误的堆栈信息,事实上,这是一个相当不错的错误提示,你可以转向任何框架检查代码或者在浏览器里执行正确的表达式。 这个错误信息相当明确,我们试图在数据插入一个重复的昵称,数据库的昵称字段是一个卫衣键,因此这样的操作是无效的。 除了实际的错误,在我们手头上还有一个次要的错误。如果一个用户不注意在我们应用程序里引起了一个错误(这一个错误或者任何其他原因引起的异常),应用程序将向他/她暴漏错误信息和堆栈信息,而不是暴露给我们。对于我们开发者来说这是个很好的特性,但是很多时候我们不想让用户看到这些信息。 这么长时间以来,我们一直在debug模式下运行我们的应用程序,我们通过设置debug=True的参数来启用应用程序的debug模式。这里我们在运行脚本run.py里配置。 当我们这样开发应用是方便的,但是我们需要在生产环境上关闭debug模式。 让我们创建另一个启动脚本文件设置关闭dubug模式(filerunp.py): #!flask/bin/python from app import app app.run(debug = False) 现在重新启动应用: ./runp.py 并且现在再尝试重命名第二个账号nickname成‘dup' 这次我们没有获取到一个错误信息,取而代之,我们得到了一个HTTP 500错误码,这是个内部服务器错误。虽然这不容易定位错误,但至少没有暴露我们应用程序的任何细节给陌生人。当调试关闭后出现一个异常时,Flask会产生一个500页面。 虽然这样好些了,但现在仍存在两个问题。首先美化问题:默认的500页面很丑陋。第二个问题更重要些,当用户操作失败时,我们无法获取到错误信息了,因为错误在后台默默的处理了。幸运的是有个简单方式来处理这两个问题。 定制HTTP错误处理程序 Flask为应用程序提供了一个机制来安装他们自己的错误页面,作为例子,让我们定义两个最常见的HTTP 404和500错误的自定义页面。定制其他错误页面也是同样的方式。 使用一个修饰来声明一个定制的错误处理程序 (fileapp/views.py):
@app.errorhandler(404)
def internal_error(error):
return render_template('404.html'),404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'),500
这地方无需多言,因为他们都是不言而喻的。唯一有趣的地方时错误500处理中的rollack语句,这个地方是不可缺少的因为这个方法会被当做一个异常调用。如果因为数据库错误导致一个异常,那么数据库的会话将变成一个无效状态,因此我们需要回滚它,以防止一个会话转向一个500错误的模板。 这是一个404错误在模版
<!-- extend base layout -->
{% extends "base.html" %}
{% block content %}
<h1>File Not Found</h1>
<p><a href="{{url_for('index')}}">Back</a></p>
{% endblock %}
这是一个500错误的模版
<!-- extend base layout -->
{% extends "base.html" %}
{% block content %}
<h1>An unexpected error has occurred</h1>
<p>The administrator has been notified. Sorry for the inconvenience!</p>
<p><a href="{{url_for('index')}}">Back</a></p>
{% endblock %}
注意,我们会继续使用我们base.html 布局, 这样我们的错误页看起来比较舒服 通过email发送错误日志 为了处理第二个问题我们需要配置应用的错误报告机制。 第一个是每当有错误发生时把错误日志通过邮件发送给我们。 首先,我们需要在我们的应用配置邮件服务器和管理员列表 (fileconfig.py): # mail server settings MAIL_SERVER = 'localhost' MAIL_PORT = 25 MAIL_USERNAME = None MAIL_PASSWORD = None # administrator list ADMINS = ['you@example.com'] 当然,你要把上面的配置改成你自己的才有意义 Flask 使用通用的Python logging模块,所以设置发送错误日志邮件非常简单. (fileapp/__init__.py):
from config import basedir,ADMINS,MAIL_SERVER,MAIL_PORT,MAIL_USERNAME,MAIL_PASSWORD
if not app.debug:
import logging
from logging.handlers import SMTPHandler
credentials = None
if MAIL_USERNAME or MAIL_PASSWORD:
credentials = (MAIL_USERNAME,MAIL_PASSWORD)
mail_handler = SMTPHandler((MAIL_SERVER,MAIL_PORT),'no-reply@' + MAIL_SERVER,'microblog failure',credentials)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
Note that we are only enabling the emails when we run without debugging. 在没有邮件服务器的pc上测试邮件功能也很容易,幸好Python有SMTP的测试排错的服务器(SMTP debugging server)。打开一个控制台窗口,并且运行下面的命令: python -m smtpd -n -c DebuggingServer localhost:25 当程序运行的时候,应用接收和发送邮件会在控制台窗口中显示出来。 打印日志到文件 通过邮件接收错误日志非常不错,但是,这是不够的。有些导致失败的条件不会触发异常并且不是主要的问题,所以我们需要将日志保存到log文件中,在某些情况下,需要日志来进行排错。 出于这个原因,我们的应用需要一个日志文件。 开启文件日志和邮件日志很相似(fileapp/__init__.py):
if not app.debug:
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler('tmp/microblog.log','a',1 * 1024 * 1024,10)
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
app.logger.setLevel(logging.INFO)
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.info('microblog startup')
当我们没有使用日志时,调试一个在线和使用中的web服务时是件非常困难的事,把日志信息写入到文件中,将是我们诊断和解决问题的一个有用工具,所以现在让我们准备好使用这个功能吧。 bug修复 让我们来修复下昵称重复的bug. (编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
