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']='1' 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)