Flask 单元测试

news2025/1/25 9:04:30

如果一个软件项目没有经过测试,就像做的菜里没加盐一样。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 unittestclass 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 提供了很多检验方法,例如 assertEqualassertTrue 等等,用于对期望结果进行检测

  • 最后,如果最为主代码被运行,调用 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) ... oktest_split (__main__.TestStringMethods) ... oktest_upper (__main__.TestStringMethods) ... ok----------------------------------------------------------------------Ran 3 tests in 0.000sOK

Flask 单元测试

Flask 作为一个 Web 项目,大多数代码需要在 Web 服务器环境下运行

  • 所以需要为每个单元测试模拟一个 Web 环境

  • 另外有些部分需要使用到数据库,所以还需要为这些测试准备一个数据库环境

  • 最后有些业务处理代码,比如加工数据,数据运算等,可以进行独立测试,不需要 Web 环境

创建了一个简单项目,通过工厂方法创建 Flask 应用,有数据库的读写,下面逐步说明下测试脚本,测试代码文件 testApp.py 与项目代码在同一目录下

初始化环境​​​​​​​

import unittestfrom app import create_appfrom model import dbclass 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) ... oktest_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) ... oktest_login_get (__main__.TestAPP) ... oktest_noRegisterLogin (__main__.TestAPP) ... oktest_register (__main__.TestAPP) ... ok----------------------------------------------------------------------Ran 4 tests in 0.196sOK

您可能已经发现,测试执行的结果和测试方法定义的顺序不一致

原因是测试加载器是按照测试名称字母顺序加载测试方法的,如果需要按照一定的顺序执行,需要用 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) ... oktest_index (__main__.TestAPP) ... oktest_login (__main__.TestAPP) ... oktest_login_get (__main__.TestAPP) ... oktest_noRegisterLogin (__main__.TestAPP) ... oktest_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 测试工具,以及代码覆盖率报告的用法。

最后需要强调的是:无论什么软件项目,单元测试是很有必要的,单元测试不仅可以确保项目的高质量交付,而且还为维护和查找问题节省了时间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/924347.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

idea使用tomcat

1. 建立javaweb项目 2. /WEB-INF/web.xml项目配置文件 如果javaweb项目 先建立项目,然后在项目上添加框架支持,选择javaee 3. 项目结构 4.执行测试:

按软件开发阶段的角度划分:单元测试、集成测试、系统测试、验收测试

1.单元测试(Unit Testing) 单元测试,又称模块测试。对软件的组成单位进行测试,其目的是检验软件基本组成单位的正确性。测试的对象是软件里测试的最小单位:模块。 测试阶段:编码后或者编码前(…

服务器(容器)开发指南——code-server

文章目录 code-server简介code-server的安装与使用code-server的安装code-server的启动code-server的简单启动指定配置启动code-server code-server环境变量配置 code-server端口转发自动端口转发手动添加转发端口 nginx反向代理code-servercode-server打包开发版镜像 GitHub官…

Qt --- QTimer

在Qt开发界面的时候,非常多的时候都得使用定时器,定时器具体可以干什么呢?比如:控制时钟、定时改变样式、改变进度等。。。说到这里,经常使用QQ,而不同的时段都会显示不同的背景,我认为如果用Qt…

商城-学习整理-集群-K8S-集群环境部署(二十四)

目录 一、MySQL集群1、mysql集群原理2、Docker安装模拟MySQL主从复制集群1、下载mysql镜像2、创建Master实例并启动3、创建 Slave 实例并启动4、为 master 授权用户来同步数据1、进入 master 容器2、进入 mysql 内部 (mysql –uroot -p)3、查看 master 状…

告别数字化系统“物理叠加”,华为云推动智慧门店价值跃迁

文|智能相对论 作者|叶远风 有大屏幕滚动播放广告; 有人脸识别系统让消费者自助结账; 有订单管理系统综合分析一段时间内总体经营情况; 有全门店监控直连总部机房; …… 以搭载数字化系统的硬件设备为表面特征的智慧门店&a…

Seaborn 基本语法及特点

文章目录 简介图类型关系型图数据分布型图分类数据型图回归模型分析型图多子图网格型图FacetGrid () 函数PairGrid () 函数 绘图风格、颜色主题和绘图元素缩放比例绘图风格颜色主题绘图元素缩放比例 简介 Seaborn 是 Python 中一个非常受用户欢迎的可视化库。Seaborn 在 Matpl…

webpack5 (二)

什么是bable 是 js 编译器,主要的作用是将 ES6 语法编写的代码转换为向后兼容的 js 语法,以便可以运行在当前版本和旧版本的浏览器或其他环境中。 它的配置文件有多种写法: babel.config.*(js/json) babelrc.*(js/json) package.json 中的…

centos7物理机安装并配置外网访问

安装准备工作 安装之前需要准备一下,需要一个U盘,其次需要准备以下内容 1.需要centos7的ISO系统镜像 2.使用UltraISO软件写入ISO镜像 3.一台windows系统 将系统写入到U盘,写入步骤 打开UltraISO点击文件 → 打开,选择Linux镜…

飞天使-k8s基础组件分析-安全

文章目录 名称空间解释访问kubernetes API的控制RBAC的介绍 kubeconfig用户的创建集群默认角色 给组创建授权针对pod配置服务账户参考文档 名称空间解释 名字是啥? 答:集群中每个对象的名称对于该类型的资源都是唯一的。并且每一个对象在整个集群中也有…

『C语言入门』初识C语言

文章目录 前言C语言简介一、Hello World!1.1 编写代码1.2 代码解释1.3 编译和运行1.4 结果 二、数据类型2.1 基本数据类型2.2 复合数据类型2.3 指针类型2.4 枚举类型 三、C语言基础3.1 变量和常量3.2 运算符3.3 控制流语句3.4 注释单行注释多行注释注释的作用 四、 …

【给自己挖个坑】三维视频重建(NSR技术)-KIRI Engine

文章目录 以下是我和AI的对话通过手机拍摄物体的视频,再根据视频生成三维模型,这个可实现吗我想开发类似上面的手机应用程序,如何开发呢 看了以上回答,还是洗洗睡吧NSR技术的实现原理是什么呢有案例吗我是名Java工程师&#xff0c…

Flink_state 的优化与 remote_state 的探索

摘要:本文整理自 bilibili 资深开发工程师张杨,在 Flink Forward Asia 2022 核心技术专场的分享。本篇内容主要分为四个部分: 相关背景state 压缩优化Remote state 探索未来规划 点击查看原文视频 & 演讲PPT 一、相关背景 1.1 业务概况 从…

ABB PP846 3BSE042238R1触摸屏

触摸界面:这款触摸屏设备允许用户通过触摸屏幕来执行操作,如选择、控制和设置。 高分辨率显示:触摸屏可能具有高分辨率的显示屏,以显示图形、数据和控制界面。 多语言支持:它可能支持多种语言,以适应不同…

数据采集:selenium 获取 CDN 厂家各省市节点 IP

写在前面 工作需要遇到,简单整理理解不足小伙伴帮忙指正 对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对…

nacos安装部署

docker下载安装(双端口,支持seata使用) 1、docker run --name nacos -e MODEstandalone -d -p 8848:8848 -p 9848:9848 -p 9849:9849 nacos/nacos-server 2、访问:http://localhost:8848/,账号密码nacos

山西电力市场日前价格预测【2023-08-25】

日前价格预测 预测明日(2023-08-25)山西电力市场全天平均日前电价为314.22元/MWh。其中,最高日前电价为336.17元/MWh,预计出现在18: 30。最低日前电价为283.05元/MWh,预计出现在24: 00。 价差方向预测 1: 实…

千呼万唤!字节首个大模型产品“豆包”面世公测,无需邀请码!

相较其他大厂,字节跳动在大模型领域的进展一直比较神秘,最近相关项目终于露出了冰山一角。 字节跳动旗下首个AI对话产品“豆包”目前已经上线,面向公众开始测试,无需邀请码,直接可以在官网(https://www.do…

Mysql 设置表字段自动赋值创建时间,以及自动更新某一个字段的更新时间

使用场景 一般表设计中记录都有创建时间以及更新时间,而 Mysql 也支持了这种通用的设计需求。 即:可以通过默认值来给时间字段自动赋值,在创建时的默认值就是当前时间也就是记录的创建时间。 记录更新:即某一记录更新时我们要更…

骨传导耳机适合运动时佩戴吗?精选五款适合运动时佩戴的耳机

当专业运动耳机已经成了运动新贵们的常用穿戴拍档,给夜跑、骑行、撸铁增添了更多期待和振奋。而骨传导耳机凭借自身健康、舒适、安全的聆听方式,迅速脱颖而出成为运动健身中最健康的黑科技耳机,但由于市面上的骨传导耳机技术参差不齐,一不留神…