Flask 学习 十四 测试
获取代码覆盖报告
安装代码覆盖工具
pip install coverage
manage.py 覆盖检测
COV = None
if os.environ.get('FLASK_COVERAGE'):
import coverage
COV = coverage.coverage(branch=True,include='app/*')
COV.start()
@manager.command
def test(coverage=False):
'''启动单元测试'''
if coverage and not os.environ.get('FLASK_COVERAGE'):
import sys
os.environ['FLASK_COVERAGE']=''
os.execvp(sys.executable,[sys.executable] + sys.argv) # 设定完环境变量重启脚本 import unittest
tests=unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests) if COV:
COV.stop()
COV.save()
print('覆盖总计:')
COV.report()
basedir = os.path.abspath(os.path.dirname(__file__))
covdir = os.path.join(basedir,'tmp/coverage')
COV.html_report(directory=covdir)
print('HTML 版本:file://%s/index.html'% covdir)
COV.erase()
Flask测试客户端
测试web程序
tests/test_client.py 使用Flask测试客户端编写的测试框架
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import unittest
from app import create_app,db
from app.models import User,Role
from flask import url_for
import re
class FlaskClientCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
Role.insert_roles()
# 测试客户端对象,use_cookies可保存cookies记住上下文
self.client = self.app.test_client(use_cookies=True) def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop() def test_home_page(self):
response = self.client.get(url_for('main.index'))
# as_text = True得到易于处理的字符串而不是字节数组
self.assertTrue('访客' in response.get_data(as_text = True)) def test_register_and_login(self):
# 注册新账户
respose = self.client.post(url_for('auth.register'),data = {'email':'papapa@qq.com','username':'papapa','password':'abc','password2':'abc'})
self.assertTrue(respose.status_code ==302) # 使用新注册的账户登陆
respose = self.client.post(url_for('auth.login'),data ={'email':'papapa@qq.com','password':'abc'},follow_redirects=True)
data = respose.get_data(as_text=True)
self.assertTrue(re.search('你好,\s+papapa!',data))
self.assertTrue('你还没有确认你的邮件信息' in data) # 发送确认令牌
user = User.query.fliter_by(email ='papapa@qq.com').first()
token = user.generate_confirmation_token()
respose = self.client.get(url_for('auth.confirm',token=token),follow_redirects = True)
data = respose.get_data(as_text=True)
self.assertTrue('你已经确认了你的账户' in data) # 退出
respose = self.client.get(url_for('auth.logout'),follow_redirects = True)
data = respose.get_data(as_text=True)
self.assertTrue('你已经退出' in data)
config.py 在测试配置中禁用CSRF保护
class Config:
WTF_CSRF_ENABLED=False # 测试中禁用CSRF保护
测试web api服务
tests/test_api.py 使用Flask 测试客户端测试REST API
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import unittest
from app import create_app,db
from app.models import User,Role
from flask import url_for
import re,json
import base64 class APITestCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
Role.insert_roles()
self.client = self.app.test_client() def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop() def get_api_headers(self,username,password):
return {
'Authorization':'Basic' + base64.b64encode((username+':'+password).encode('utf-8')).decode('utf-8'),
'Accept':'application/json',
'Content-Type':'application/json'
}
def test_no_auth(self):
response = self.client.get(url_for('api.get_posts'),content_type='application/json')
self.assertTrue(response.status_code ==401) def test_posts(self):
# 添加一个用户
r = Role.query.fliter_by(name = 'User').first()
self.assertIsNotNone(r)
u = User(email = 'bababa@qq.com',password = 'abc',confirmed = True,role = r)
db.session.add(u)
db.session.commit() # 写一篇文章
response = self.client.post(url_for('api.new_post'),
headers = self.get_api_headers('bababa@qq.com','abc'),
data = json.dumps({'body':'body of the blog'}) )
self.assertTrue(response.status_code ==201)
url = response.headers.get('Location')
self.assertIsNotNone(url) # 获取刚发布文章
response = self.client.get(url,headers = self.get_api_headers('bababa@qq.com','abc'))
self.assertTrue(response.status_code ==200)
# 测试客户端不会自动json编码解码
json_response = json.loads(response.data.decode('utf-8'))
self.assertTrue(json_response['url']==url)
self.assertTrue(json_response['body']=='body of the blog')
self.assertTrue(json_response['body_html'=='<p>body of blog post</p>'])
使用Selenium 进行端到端测试
pip install selenium
app/main/views.py 关闭服务器的路由
# 当所有测试完成之后关闭服务器的路由
@main.route('/shutdown')
def server_shutdown():
# 只有在测试环境中,当前路由可用
if not current_app.testing:
abort(404)
shutdown = request.environ.get('werkzeug.server.shutdown')
if not shutdown:
abort(500)
shutdown()
return 'Shutting down....'
tests/test_selenium.py 使用Selenium运行测试的框架
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import unittest
from selenium import webdriver
from app import create_app,db
from app.models import User,Role,Post
from flask import url_for
import re,json,time
import base64
import threading class SeleniumTestCase(unittest.TestCase):
client =None @classmethod
def setUpClass(cls):
# 启动Firefox
try:
cls.client = webdriver.Firefox()
except:
pass
# 如果无法启动浏览器则跳过这些测试
if cls.client:
# 创建程序
cls.app = create_app('testing')
cls.app_context = cls.app.app_context()
cls.app_context.push() # 禁止日志,保持输出简洁
import logging
logger = logging.getLogger('werkzeug')
logger.setLevel('ERROR') # 创建数据库,并使用一些虚拟数据填充
db.create_all()
Role.insert_roles()
User.generate_fake(10)
Post.generate_fake(10) # 添加管理员
admin_role = Role.query.filter_by(permission=0xff).first()
admin = User(email = 'bababa@qq.com',username = 'bababa',password = 'abc',role = admin_role,confirmed = True)
db.session.add(admin)
db.session.commit() # 在一个线程中启动Flask服务器
threading.Thread(target=cls.app.run).start() time.sleep(1) @classmethod
def tearDownClass(cls):
if cls.client:
# 关闭Flask服务器和浏览器
cls.client.get('http://localhost:5000/shutdown')
cls.client.close() # 销毁数据库数据
db.drop_all()
db.session.remove() # 删除程序上下文
cls.app_context.pop() def setUp(self):
if not self.client:
self.skipTest('web 浏览器无效')
def tearDown(self):
pass def test_admin_home_page(self):
# 进入首页
self.client.get('http://localhost:5000/')
self.assertTrue(re.search('你好,\s+访客',self.client.page_source)) # 进入登陆页面
self.client.find_element_by_link_text('登陆').click()
self.assertTrue('<h1>登陆</h1>' in self.client.page_source) # 登陆
self.client.find_element_by_name('邮箱').send_keys('bababa.qq.com')
self.client.find_element_by_name('密码').send_keys('abc')
self.client.find_element_by_name('提交').click()
self.assertTrue(re.search('你好,\s+bababa!',self.client.page_source)) # 进入用户个人资料页面
self.client.find_element_by_link_text('个人资料').click()
self.assertTrue('<h1>bababa</h1>' in self.client.page_source)
Flask 学习 十四 测试的更多相关文章
- Flask 学习(四)静态文件
Flask 学习(四)静态文件 动态 web 应用也需要静态文件,一般是 CSS 和 JavaScript 文件.理想情况下你的服务器已经配置好提供静态文件的服务. 在开发过程中, Flask 也能做 ...
- 强化学习(十四) Actor-Critic
在强化学习(十三) 策略梯度(Policy Gradient)中,我们讲到了基于策略(Policy Based)的强化学习方法的基本思路,并讨论了蒙特卡罗策略梯度reinforce算法.但是由于该算法 ...
- Scala学习十四——模式匹配和样例类
一.本章要点 match表达式是更好的switch,不会有意外调入下一个分支 如果没有模式能够匹配,会抛出MatchError,可以用case _模式避免 模式可以包含一个随意定义的条件,称做守卫 你 ...
- android学习十四(android的接收短信)
收发短信是每一个手机主要的操作,android手机当然也能够接收短信了. android系统提供了一系列的API,使得我们能够在自己的应用程序里接收和发送短信. 事实上接收短信主要是利用我们前面学过的 ...
- Java学习十四
学习内容: 1.Junit 2.maven安装配置环境 一.Junit实例演示步骤 1.引入jar包 junit包需要引入hamcrest-core包,否则会报错 2.测试如下代码 package c ...
- Java开发学习(十四)----Spring整合Mybatis及Junit
一.Spring整合Mybatis思路分析 1.1 环境准备 步骤1:准备数据库表 Mybatis是来操作数据库表,所以先创建一个数据库及表 create database spring_db cha ...
- Flask 学习 十六 部署
部署流程 manage.py 部署命令 每次安装升级只需运行deploy命令即可完成操作 @manager.command def deploy(): """执行部署任务 ...
- Flask 学习 十 博客文章
提交和显示博客文章 app/models.py 文章模型 class Post(db.Model): __tablename__ = 'posts' id = db.Column(db.Integer ...
- Flask最强攻略 - 跟DragonFire学Flask - 第十四篇 Flask-SQLAlchemy
前不久刚刚认识过了SQLAlchemy,点击这里复习一下 当 Flask 与 SQLAlchemy 发生火花会怎么样呢? Flask-SQLAlchemy就这么诞生了 首先要先安装一下Flask-SQ ...
随机推荐
- 【BZOJ2555】SubString(后缀自动机,Link-Cut Tree)
[BZOJ2555]SubString(后缀自动机,Link-Cut Tree) 题面 BZOJ 题解 这题看起来不难 每次要求的就是\(right/endpos\)集合的大小 所以搞一个\(LCT\ ...
- 我也不知道什么是"莫比乌斯反演"和"杜教筛"
我也不知道什么是"莫比乌斯反演"和"杜教筛" Part0 最近一直在搞这些东西 做了将近超过20道题目吧 也算是有感而发 写点东西记录一下自己的感受 如果您真的 ...
- Vue-小demo、小效果 合集(更新中...)
(腾讯课堂学习小demo:https://ke.qq.com/course/256052) 一.简单的指令应用 --打击灭火器 图片素材点击腾讯课堂的链接获取 html: <!DOC ...
- linux系统文件扩展名介绍
1.源码tar.tar.gz .tgz.zip.tar.bz 表示压缩文件,创建命令等 2.sh表示shell脚本文件,通过shell语言开发的程序. 3.pl 表示perl语言文件,通过perl语言 ...
- [.NET Core] 简单读取 json 配置文件
简单读取 json 配置文件 背景 目前发现网上的 .NET Core 读取配置文件有点麻烦,自己想搞个简单点的. .NET Core 已经不使用之前的诸如 app.config 和 web.conf ...
- C++与Java通过WebService通信(下)
一. 前言 本篇讲述如何通过Java客户端访问C++服务端发布的SOAP模式的WebService接口.文档中的样例代码拷贝出去即可运行,所有的代码都是本地测试OK的:本文不但解决了接口调用的问题,同 ...
- 关于new,delete,malloc,free的一些总结
首先,new,delete都是c++的关键字并不是函数,通过特定的语法组成表达式,new可以在编译的时候确定其返回值.可以直接使用string *p=new string("asdfgh&q ...
- Raid 配置
清除所有外部设备 /opt/MegaRAID/MegaCli/MegaCli64 '-CfgForeign -Clear' -aAll 修改盘的jbod状态 /opt/MegaRAID/MegaCli ...
- c++ 指针总结 函数参数指针调用和堆栈内存的分配原理
c++中的char指针 这个char指针很有意思,char指针通常有两种初始化形式.一个是使用char数组初始化,一个是使用char变量初始化. c++当中使用双引号括起来的字符串起始已经被编译器初始 ...
- 从循环添加事件谈起对JS闭包的理解
1.引子 相信很多初学js的人,都遇到这样一种情况:想要给一堆按钮添加各自的事件,比如点击第i个按钮时,弹出i这个值.理所当然地,我们会这样写: var buttons = document.getE ...