Flask 单元测试
如果一个软件项目没有经过测试,就像做的菜里没加盐一样。Flask 作为一个 Web 软件项目,如何做单元测试呢,今天我们来了解下,基于 unittest 的 Flask 项目的单元测试。
什么是单元测试
单元测试是软件测试的一种类型。顾名思义,单元测试的对象是程序中的最小的单元,可以是一个函数,一个类,也可以是它们的组合。
相对于模块测试、集成测试以及系统测试等高级别的测试,单元测试一般由软件开发者而不是独立的测试工程师完成,且具有自动化测试的特质,因此单元测试也属于自动化测试。
在实际开发中,有一些测试建议:
-
测试单元应该关注于尽可能小的功能,要能证明它是正确的
-
每个测试单元必须是完全独立的,必须能单独运行
-
修改代码后,需要重新执行一次测试代码,以确保本次修改不会影响到其他部分
-
提交代码前,需要执行一次完整测试,以确保不会将不完整或者错误的代码提交,影响其他开发者
-
测试代码要和正常代码有明显的区分,测试代码文件应该是独立的
unittest 模块
Python 有很多单元测试框架,unittest、nose、pytest 等等,unittest 是 Python 内置的测试库,也是很多测试框架的基础,地位如同 Java 中的 JUnit,所以有时也被称作 PyUnit。
unittest 支持 自动化测试
、可以在多个测试中 共享设置测试环境和撤销测试环境代码
、可以将分散的测试集中起来
,并且可以支持多种测试报告框架
,因此 unittest 有四种重要概念:
-
test fixture 测试前后需要做些准备和清理工作,例如临时数据库连接、测试数据创建、测试用服务器创建,以及测试后的清理和销毁,test fixture 提供了
setUp
和tearDown
接口来完成这些事情,并且可以被多个测试方法所共享 -
test case 测试用例,是最小的测试单元,检测一个特定输入的响应结果,unittest 提供
TestCase
基类,以便开发者创建具体的测试用例类 -
test suite 暂且翻译成测试套餐吧,是多个测试用例、测试套餐的组合,为了将一组相关的测试组织起来的工具
-
test run 测试执行器是按照一定规则执行测试用例,记录并返回测试结果的组件
小试牛刀
unittest 不需要安装,直接导入,例如一个测试字符串方法的测试代码:
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())
def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# check that s.split fails when the separator is not a string
with self.assertRaises(TypeError):
s.split(2)
if __name__ == '__main__':
unittest.main()
-
导入 unittest 模块
-
创建一个测试字符串方法的测试类,继承之 unittest 的
TestCase
-
编写测试方法,注意测试方法必须以 test 作为开头,这样才能被测试加载器识别,同时也是良好的编程习惯
-
TestCase 提供了很多检验方法,例如
assertEqual
、assertTrue
等等,用于对期望结果进行检测 -
最后,如果最为主代码被运行,调用
unittest.main
执行所有测试方法
运行代码:
python testBase.py
或者
python -m unittest testBase.py
结果如下:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
可以看到,执行了三个测试,没有发现异常情况,.
表示测试通过,数量表示执行了的测试方法个数
测试执行器
unittest.main 只给出了概要测试结果,如果需要更详细的报告,可以用测试执行器
来运行测试代码
将 unittest.main() 换成:
suite = unittest.TestLoader().loadTestsFromTestCase(TestStringMethods)
unittest.TextTestRunner(verbosity=2).run(suite)
-
利用测试加载器(TestLoader)创建了一个测试套餐(TestSuite)
-
用测试执行器(TestRunner)执行测试代码
-
TestTestRunner 是将结果作为文本格式输出
-
参数 verbosity=2 表示显示详细的测试报告
或者干脆为 unittest.main 提供参数 verbosity :unittest.main(verbosity=2)
运行结果如下:
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Flask 单元测试
Flask 作为一个 Web 项目,大多数代码需要在 Web 服务器环境下运行
-
所以需要为每个单元测试模拟一个 Web 环境
-
另外有些部分需要使用到数据库,所以还需要为这些测试准备一个数据库环境
-
最后有些业务处理代码,比如加工数据,数据运算等,可以进行独立测试,不需要 Web 环境
创建了一个简单项目,通过工厂方法创建 Flask 应用,有数据库的读写,下面逐步说明下测试脚本,测试代码文件 testApp.py
与项目代码在同一目录下
初始化环境
import unittest
from app import create_app
from model import db
class TestAPP(unittest.TestCase):
def setUp(self):
self.app = create_app(config_name='testing')
self.client = self.app.test_client()
with self.app.app_context():
db.create_all()
def tearDown(self):
with self.app.app_context():
db.drop_all()
-
引入 unittest 模块
-
从 Flask 应用代码文件(
app.py
)中引入工厂方法create_app
-
从模型代码文件(
model.py
)中引入数据库实例db
-
创建测试类
TestAPP
,继承自unittest.TestCase
-
定义
setUp
方法,用工厂方法初始化 Flask 应用 -
Flask 提供了
测试应用
的创建方法test_client
,返回测试应用实例 -
在应用实体环境下,初始化数据库
-
定义
tearDown
方法,在测试结束后销毁数据库中的结构和数据
简单测试
编写两个测试方法,分别对 Flask 应用的配置情况和首页进行测试:
def test_config(self):
self.assertEqual(self.app.config['TESTING'], True)
self.assertIsNotNone(self.app.config['SQLALCHEMY_DATABASE_URI'])
def test_index(self):
ret = self.client.get('/')
self.assertEqual(b'Hello world!', ret.data)
-
定义测试方法 test_config 用来测试 Flask app 的配置是否正常
-
因为测试方法时实体方法,所以从实体引用(self)中的 app 属性中,查看配置属性,注意测试应用 test_client 不能之间获取 Flask app 的配置
-
检测
TESTING
的值是否为 True,另外检查数据库连接是否存在 -
定义方法首页的方法 test_index,通过测试应用的
get
方法访问网站根目录 -
检测访问后的结果,在示例中,首页返回了字符串,确认下是否正确
此时运行测试代码可以得到如下
test_config (__main__.TestAPP) ... ok
test_index (__main__.TestAPP) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.066s
测试表单提交
在 Web 项目中,有很多需要交互的功能,例如表单提交,数据存储和查询,在 unittest 测试框架中,借助 Flask 的测试应用 test_client 可以轻松应对
示例项目中,有模拟用户注册和登录的功能,注册和登录都需要提交数据,并且只有在注册后,才能进行登录,所以将注册和登录编写成单独的功能:
def login(self, username):
params = {'username': username}
return self.client.post('/login', data=params, follow_redirects=True)
def register(self, username):
params = {'username': username}
return self.client.post('/register', data=params, follow_redirects=True)
-
定义登录方法
login
,接受一个用户名的参数(这里忽略了密码等登录凭证) -
利用测试应用 test_client 的 post 方法,访问登录地址,将提交的数据用
词典
数据结构通过 data 参数提交 -
定义注册方法
register
,接受一个用户名的参数(同样忽略了密码等其他信息) -
注册方法和登录类似,除了注册提交地址
-
注意到 post 的参数
follow_redirects
,值为True
的作用是支持浏览器跳转,即收到跳转状态码时会自动跳转,直到不是跳转状态码时才会返回 -
登录和注册方法可以处理更多的业务逻辑,最后将请求结果返回
有了注册和登录的协助,测试方法就更明晰:
def test_register(self):
ret = self.register('bar')
self.assertEqual(json.loads(ret.data)['success'], True)
def test_login(self):
self.register('foo')
ret = self.login('foo')
return self.assertEqual(json.loads(ret.data)['username'], 'foo')
def test_noRegisterLogin(self):
ret = self.login('foo')
return self.assertEqual(json.loads(ret.data)['success'], False)
def test_login_get(self):
ret = self.client.get('/login', follow_redirects=True)
self.assertIn(b'Method Not Allowed', ret.data)
-
定义了 4 个测试方法,分别时单独的注册,注册后登录,未注册时的登录,和用 get 方法请求登录接口
-
每种方法都调用了
login
或者register
方法,所以代码逻辑会更简洁 -
注册和登录接口,返回的时 JSON 格式数据,需要用 json.loads 将其转化为
词典
-
assertIn
类似与indexOf
方法,用来检测给定的字符串是否在结果中
运行上述的是测试,可以得到如下结果:
test_login (__main__.TestAPP) ... ok
test_login_get (__main__.TestAPP) ... ok
test_noRegisterLogin (__main__.TestAPP) ... ok
test_register (__main__.TestAPP) ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.196s
OK
您可能已经发现,测试执行的结果和测试方法定义的顺序不一致
原因是测试加载器是按照测试名称字母顺序加载测试方法的,如果需要按照一定的顺序执行,需要用 TestSuite
设定执行顺序,如:
if __name__ == '__main__':
suite = unittest.TestSuite()
tests = [TestAPP('test_register'), TestAPP('test_login'), TestAPP('test_noRegisterLogin'), TestAPP('test_login_get')]
suite.addTests(tests)
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
-
创建
TestSuite
实例 -
将需要组织的测试方法放在数组中,用
TestSuite
的addTests
方法添加到TestSuite
实例中 -
用
TestRuuner
运行TestSuite
实例
这样就会以设定的顺序执行测试方法了
代码覆盖率
测试中有个重要的概念就是代码覆盖率,如果存在没有被被覆盖的代码,就有可能编写的测试代码不够全面
coverage
Python 的一个测试工具,不仅可以运行测试代码,还可以报告出代码覆盖率
安装
使用前,需要安装:
pip install coverage
执行测试
安装成功后,就可以在命令行中使用了,首先进入到测试代码的所在目录,
请注意 Python 包引用的查找位置,从不同的目录运行,可能会影响到目录下模块的引用,例如在同一目录下,引用模块,如果在上一级目录中运行代码,可能出现找不到模块的错误,此时只需要相对于运行目录,调整下代码中模块引用方式就好了,具体可参见Python Unit Testing – Structuring Your Project
执行如下命令:
coverage run testApp.py
结果如下:
test_config (__main__.TestAPP) ... ok
test_index (__main__.TestAPP) ... ok
test_login (__main__.TestAPP) ... ok
test_login_get (__main__.TestAPP) ... ok
test_noRegisterLogin (__main__.TestAPP) ... ok
test_register (__main__.TestAPP) ... ok
----------------------------------------------------------------------
Ran 6 tests in 0.226s
结果和之间运行测试代码类似,也就是说用 coverage run
命令可以代替 python 命令执行测试代码,例如
python -m unittest discover
将变为
coverage run -m unittest discover
覆盖率
coverage 更大的用处在于查看代码覆盖率,命令是 coverage report
,例如:
coverage report testApp.py
结果如下:
Name Stmts Miss Cover
--------------------------------
testApp.py 41 0 100%
-
Name 指的是代码文件名
-
Stmts 是执行的代码行数
-
Miss 表示没有被执行的行数
-
Cover 表示覆盖率,公式是(Stmts-Miss)/Stmts,即被执行代码所占比例,用百分比表示
如果要看到哪些行被忽略了,加上参数 -m
即可:
coverage report -m testApp.py
结果中会多一列 Missing,内容为执行的行号
代码覆盖率报告,是基于
coverage run
的运行结果的,所以没有测试的运行就无法得到覆盖率报告的
整体覆盖率报告
coverage run
在执行测试时,会记录所有被调用代码文件的执行情况,包括 Python 库中的代码,如果只想记录指定目录下的代码执行情况,需要用 --source
选项指定需要记录的目录,例如只记录当前目录下的执行情况:
coverage run --source . testApp.py
然后查看执行报告,例如:
Name Stmts Miss Cover
--------------------------------
app.py 10 0 100%
config.py 17 1 94%
model.py 17 4 76%
route.py 19 1 95%
testApp.py 41 0 100%
--------------------------------
TOTAL 104 6 94%
如果执行时没有加上 --source 参数,也可以通过通配符文件名,指定要查看的代码文件:
coverage report *.py
结果同上
html 测试报告
如果项目中代码文件众多,在命令行中用文本方式显示测试报告就不太方便了,coverage html
可以将测试报告生成 html 文件,功能强大,显示效果更好:
coverage html -d testreport
参数 -d 用来指定测试报告存放的目录,如果不存在会创建
文件名是个连接,点击可以看到文件内容,并且将执行和未执行的代码标注的很清楚:
总结
今天介绍了 Flask 的单元测试,主要介绍了 Python 自带单元测试模块 unittest 的基本用法,以及 Flask 项目中单元测试的特点和方法,还介绍了 coverage 测试工具,以及代码覆盖率报告的用法。
最后需要强调的是:无论什么软件项目,单元测试是很有必要的,单元测试不仅可以确保项目的高质量交付,而且还为维护和查找问题节省了时间。
相关文章:

Flask 单元测试
如果一个软件项目没有经过测试,就像做的菜里没加盐一样。Flask 作为一个 Web 软件项目,如何做单元测试呢,今天我们来了解下,基于 unittest 的 Flask 项目的单元测试。 什么是单元测试 单元测试是软件测试的一种类型。顾名思义&a…...
前端面试:【前端工程化】CommonJS 与 ES6 模块
嗨,亲爱的前端开发者!在现代Web开发中,模块化是构建可维护和可扩展应用程序的关键。本文将深入探讨两种主要的JavaScript模块系统:CommonJS 和 ES6 模块,以帮助你了解它们的工作原理、用法以及如何选择合适的模块系统。…...

keepalived双机热备,keepalived+lvs(DR)
本节主要学习了keepalivedlvs的作用和配置方法主要配置调度器和web节点,还有keepalived的双击热备,主要内容有概述,安装,功能模块,配置双击热备,验证方法,双击热备的脑裂现象和VIP无法通信。 目…...
unity-ShaderGraph全节点
1.Artistic美术 Adjustment调整 Channel Mixer 混合颜色通道 Contrast 设置对比度 Hue 设置色调 range需要选normalized Invert Colors 反转颜色 Replace Color 设置两个颜色通道互换,可调参数 Saturation 设置饱和度 White Balance 白平衡(调冷暖色调&a…...

C++入门:内联函数,auto,范围for循环,nullptr
目录 1.内联函数 1.1 概念 1.2 特性 1.3 内联函数与宏的区别 2.auto关键字(C11) 2.1 auto简介 2.2 auto的使用细则 2.3 auto不能推导的场景 3.基于范围的for循环(C11) 3.1 范围for的语法 3.2 范围for的使用方法 4.指针空值nullptr(C11) 4.1 C98中的指针空值 1.内联…...

五、多表查询-1.多表关系介绍
一、概述 项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种: 一对多&a…...

Linux:编写编译脚本Makefile文件
一、生成可执行文件 1、一个源文件编译 本例子主要区别.c及.cpp文件及编译该文件时使用的编译链。 1).c文件 // testadd.c #include <stdio.h> int main() {int a 1;int b 2;int sum a b;printf("sum %d\n", sum);return 0; }// Makefie GXX g CC gcc…...
深入浅出Pytorch函数——torch.nn.init.calculate_gain
分类目录:《深入浅出Pytorch函数》总目录 相关文章: 深入浅出Pytorch函数——torch.nn.init.calculate_gain 深入浅出Pytorch函数——torch.nn.init.uniform_ 深入浅出Pytorch函数——torch.nn.init.normal_ 深入浅出Pytorch函数——torch.nn.init.c…...
【PHP】PHP入门指南:从基础到进阶
PHP(Hypertext Preprocessor)是一种广泛使用的服务器端脚本语言,尤其在Web开发领域有着重要的地位。本文旨在为初学者提供一份详尽的PHP入门指南,帮助您了解PHP的基础知识和语法,掌握基本的编程技巧,并熟悉…...
【100天精通python】Day45:python网络爬虫开发_ Scrapy 爬虫框架
目录 1 Scrapy 的简介 2 Scrapy选择器 3 快速创建Scrapy 爬虫 4 下载器与爬虫中间件 5 使用管道Pielines 1 Scrapy 的简介 Scrapy 是一个用于爬取网站数据并进行数据提取的开源网络爬虫框架。它使用 Python 编程语言编写,并提供了一套强大的工具和库࿰…...

怎么写出更好的高质量内容输出
为了更好地输出高质量的内容,不仅仅需要了解写作的基本原则,还需要深入挖掘目标读者的需求、持续的自我提升以及对信息的严格筛选。以下是一些建议,帮助你更好地输出高质量的内容: 1.充分了解你的受众 调查和了解你的目标读者&am…...

HJ31 单词倒排 题解
题目描述:单词倒排_牛客题霸_牛客网 (nowcoder.com) 对字符串中的所有单词进行倒排。 1、构成单词的字符只有26个大写或小写英文字母; 2、非构成单词的字符均视为单词间隔符; 3、要求倒排后的单词间隔符以一个空格表示;如果原字符…...

LeetCode42.接雨水
这道题呢可以按列来累加,就是先算第1列的水的高度然后再加上第2列水的高度……一直加到最后就是能加的水的高度,我想到了这里然后就想第i列的水其实就是第i-1列和i1列中最小的高度减去第i列的高度,但是其实并不是,比如示例中的第5…...

优化时间流:区间调度问题的探索与解决
在浩如烟海的信息时代,时间的有效管理成为了一门不可或缺的艺术。无论是生活中的琐事,还是工作中的任务,时间都在无声地流逝,挑战着我们的智慧。正如时间在日常生活中具有的宝贵价值一样,在计算机科学领域,…...

【Python】强化学习:原理与Python实战
搞懂大模型的智能基因,RLHF系统设计关键问答 RLHF(Reinforcement Learning with Human Feedback,人类反馈强化学习)虽是热门概念,并非包治百病的万用仙丹。本问答探讨RLHF的适用范围、优缺点和可能遇到的问题ÿ…...
设计模式——合成复用原则
文章目录 合成复用原则设计原则核心思想合成案例聚合案例继承案例优缺点 合成复用原则 原则是尽量使用合成/聚合的方式,而不是使用继承 设计原则核心思想 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。…...

基于OpenCV实战(基础知识一)
目录 简介 1.计算机眼中的图像 2.图片的读取、显示与保存 3.视频的读取与显示 简介 OpenCV是一个流行的开源计算机视觉库,由英特尔公司发起发展。它提供了超过2500个优化算法和许多工具包,可用于灰度、彩色、深度、基于特征和运动跟踪等的图像处理和…...
如何高效的接入第三方接口
作为程序员的我们,经常会接到领导的安排,接入某某的接口,方面我们如何如何, 例如:领导在1号时给作为员工的你说,最近系统需要增加一个新的支付方式,一会和对方技术组建一个群,有什么问题,可以直接在群里说,最近还说,尽快接入,客户等着用,让你在5号前,完成接入工…...
docker pip下载依赖超时或失败问题解决
Docker容器使用pip安装Python库时超时,可能是由于多种原因。以下是一些建议和解决方法: 使用国内镜像源: 如果你位于中国,可以尝试更换到国内的镜像源。例如,可以使用阿里云、腾讯云、清华大学提供的镜像。 你可以在Dockerfile中添…...

python并发编程
一、程序提速的方法 二、python对并发编程的支持 多线程:threading,利用CPU和IO可以同时执行的原理,让CPU不会干巴巴等待IO完成;多进程:multiprocess,利用多核CPU的能力,真正的并行执行任务&am…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
Python网页自动化Selenium中文文档
1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API,你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...
【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?
FTP(File Transfer Protocol)本身是一个基于 TCP 的协议,理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况,主要原因包括: ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...

PLC入门【4】基本指令2(SET RST)
04 基本指令2 PLC编程第四课基本指令(2) 1、运用上接课所学的基本指令完成个简单的实例编程。 2、学习SET--置位指令 3、RST--复位指令 打开软件(FX-TRN-BEG-C),从 文件 - 主画面,“B: 让我们学习基本的”- “B-3.控制优先程序”。 点击“梯形图编辑”…...
el-amap-bezier-curve运用及线弧度设置
文章目录 简介示例线弧度属性主要弧度相关属性其他相关样式属性完整示例链接简介 el-amap-bezier-curve 是 Vue-Amap 组件库中的一个组件,用于在 高德地图 上绘制贝塞尔曲线。 基本用法属性path定义曲线的路径,可以是多个弧线段的组合。stroke-weight线条的宽度。stroke…...

使用ch340继电器完成随机断电测试
前言 如图所示是市面上常见的OTA压测继电器,通过ch340串口模块完成对继电器的分路控制,这里我编写了一个脚本方便对4路继电器的控制,可以设置开启时间,关闭时间,复位等功能 软件界面 在设备管理器查看串口号后&…...

2025-06-01-Hive 技术及应用介绍
Hive 技术及应用介绍 参考资料 Hive 技术原理Hive 架构及应用介绍Hive - 小海哥哥 de - 博客园https://cwiki.apache.org/confluence/display/Hive/Home(官方文档) Apache Hive 是基于 Hadoop 构建的数据仓库工具,它为海量结构化数据提供类 SQL 的查询能力…...

Modbus转Ethernet IP深度解析:磨粉设备效率跃升的底层技术密码
在建材矿粉磨系统中,开疆智能Modbus转Ethernet IP网关KJ-EIP-101的应用案例是一个重要的技术革新。这个转换过程涉及到两种主要的通信协议:Modbus和Ethernet IP。Modbus是一种串行通信协议,广泛应用于工业控制系统中。它简单、易于部署和维护…...