flask-sqlalchemy、pytest 的单元测试和事务自动回滚
flask-sqlalchemy、pytest 的单元测试和事务自动回滚
使用 flask-sqlalchemy 做数据库时,单元测试可以帮助发现一些可能意想不到的问题,像 delete-cascade 、数据长度、多对多关联等等。如果使用 alembic 管理数据库版本,还可以写些跟迁移相关的单元测试。在团队中实现规范的单元测试,再配合 flake8 / pep8 之类的代码规范工具,有助于提高代码的质量,让开发人员有意识去主动发现问题,在新功能进行回归测试时可重复使用单元测试的代码,避免中断已有功能。
在数据库的单元测试前,先要把数据库表结构创建起来。有两种方式来做这个事,一是用一个专门用于单元测试的数据库,重新进行 create_all 的建表操作;或者直接用开发用的数据库, upgrade 到最新的版本,然后在单元测试完成后再进行 downgrade 。两种方法都可以使用 pytest 的 fixture 实现,都可以使用。
做数据库的单元测试需要注意的是事务回滚。通常每个单元测试会针对某个单独的类,而为了避免单元测试所插入或删除的数据对其它单元测试的影响,需要把整个单元测试包含在一个事务中,单元测试结束时自动回滚。事务所针对的层次可以是一个模块,或者是单元测试函数。
假如项目的目录结构如下 ::
.
├── config.py
├── db.sqlite
├── manage.py
├── maze
├── migrations
├── requirements-dev.txt
├── requirements-test.txt
├── requirements.txt
├── tests
├── tox.ini
db.sqlite 为开发用的 sqlite 数据库,migrations 为数据库迁移文件,tests 中放单元测试。在配置 sqlite 路径时,需要注意使用绝对路径,否则会把 sqlite 文件写在 maze 目录下。
现在有 model 类的定义如下 ::
class User(db.Model, Base, UserMixin):
"""User model."""
__tablename__ = 'users'
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(64), unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
@property
def password(self):
raise AttributeError('Not readable')
@password.setter
def password(self, value):
self.password_hash = generate_password_hash(value)
def save(self):
db.session.add(self)
db.session.flush()
为了测试这个类,需要先编写一些可行的测试框架代码,以在 pytest 中支持对 flask 的测试。
在 tests 中创建 conftest.py 文件,包含我们在整个测试中都需要使用的 fixture 。首先,需要创建一个 flask 的 fixture ,并创建一个测试上下文。
# -*- coding: utf-8 -*-
import pytest
@pytest.yield_fixture(scope='session',autouse=True)
def app():
from maze import create_app
app = create_app()
ctx = app.test_request_context()
with ctx:
yield app
如果使用单独测试数据库的方式进行单元测试,那么加一个创建数据库的 fixture ,并在测试::
@pytest.yield_fixture(scope='session', autouse=True)
@pytest.mark.usefixtures('app')
def create_schema():
from maze import db
db.create_all()
try:
yield
finally:
db.drop_all()
那么在单元测试中,就已经可以通过 @pytest.mark.usefixtures('app') ,flask 的相关代码就能进行测试。为了支持自动回滚,还需要加一个 rollback 的 fixture ,如下 ::
@pytest.yield_fixture(autouse=True)
def rollback(app):
from maze import db
ctx = app.test_request_context()
with ctx:
db.session.begin(subtransactions=True)
try:
yield
finally:
db.session.rollback()
db.session.remove()
rollback 可使用默认的 function scope ,如果需要在模块范围内才进行 rollback ,那么设置 scope='module' 。但要注意,原始代码中应用 db.session.flush() 的方式刷新 session 数据,而不是用 commit 。 rollback 对本事务上次 commit 之后的数据才生效,因此如果单元测试中有 commit 的代码,部分数据会被写入数据库,从而达不到 rollback 的目的。
OK,现在在 test_user.py 的单元测试模块中写些测试代码 ::
@pytest.usefixtures('rollback')
def test_user():
user = User(username='test', password='test123')
user.save()
assert user.id is not None
user2 = User.query.filter_by(username='test').first()
assert user2 is not None
assert user == user2
with pytest.raises(AttributeError):
p = user.password # noqa:F841
最后,执行 pytest 单元测试 ::
pytest tests
整个过程有几点要注意的:
(1) 为了支持自动回滚,原始代码不使用 commit 而要用 flush ,但要在 flask 中用 @app.teardown_request 注册一个处理函数,在函数中进行最终的 commit ;
(2) fixture 需要写在 conftest.py 文件中(或在 conftest.py 中进行 import),而单元测试模块不需要 import 这些 fixture ,否则会出现找不到 fixture (尤其在 pycharm IDE 直接调用 pytest 测试时非常容易出现);
(3) 注意 flask 的上下文处理,可使用 pytest-flask 这个插件处理一些问题。
pytest 的 fixture 编写方式和 unittest 、 nose 、 xUnit 等有点区别,它是以函数的方式出现,在一开始用的时候会不习惯,也没有用 setup 、 teardown 等常见的约定函数来注入依赖,而是用简单的函数参数的方式,用过一小段时间后,感觉也还挺好,简单明了处理的方式在编写代码时少了很多麻烦。
flask-sqlalchemy、pytest 的单元测试和事务自动回滚的更多相关文章
- MockMvc 进行 controller层单元测试 事务自动回滚 完整实例
package com.ieou.ms_backend.controller; import com.google.gson.Gson; import com.ieou.ms_backend.dto. ...
- Spring事务为什么不会自动回滚?Spring事务怎样才会自动回滚?事务自动回滚条件及手动回滚
原文:https://blog.csdn.net/qq_32331073/article/details/76508147 更多Spring事务问题请访问链接:Spring事务回滚问题疑难详解 在此, ...
- mysql 事务中如果有sql语句出错,会导致自动回滚吗?
事务,我们都知道具有原子性,操作要么全部成功,要么全部失败.但是有可能会造成误解. 我们先准备一张表,来进行测试 CREATE TABLE `name` ( `id` int(11) unsigned ...
- spring事务什么时候会自动回滚
在java中异常的基类为Throwable,他有两个子类xception与Errors.同时RuntimeException就是Exception的子类,只有RuntimeException才会进行回 ...
- 事务框架之声明事务(自动开启,自动提交,自动回滚)Spring AOP 封装
利用Spring AOP 封装事务类,自己的在方法前begin 事务,完成后提交事务,有异常回滚事务 比起之前的编程式事务,AOP将事务的开启与提交写在了环绕通知里面,回滚写在异常通知里面,找到指定的 ...
- DDL, DML不是所有SQL都是可以自动回滚的
因为DDL没有事务性,所以DDL不能回滚. 要实现自动回滚.(begin,commit,rollback),则SQL语句中只能包括DML. 这样,自动化发布就会受限规范格式. 故而,一刀切的办法是,假 ...
- Spring,SpringMvc配置常见的坑,注解的使用注意事项,applicationContext.xml和spring.mvc.xml配置注意事项,spring中的事务失效,事务不回滚原因
1.Spring中的applicationContext.xml配置错误导致的异常 异常信息: org.apache.ibatis.binding.BindingException: Invalid ...
- Spring声明式事务不回滚问题
疑问,确实像往常一样在service上添加了注解 @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚.于是就对相关代码进行了 ...
- spring 在service中需要抛出异常才能自动回滚
在spring 事务配置中,如果service方法捕获了异常,则程序报错也不会自动回滚, 1.手动开启关闭事务 2.抛出异常,可以先捕获异常,然后自定义runtime异常,可不用声明
随机推荐
- 创建型模式之Builder模式及实现
建造者(Builder)模式 GOF给出的定义为:建造者模式是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 应用场景 使用建造者模式是为了将构建复杂对象的过程和它的部件 ...
- $().click(function(){}) 不管用 live()替代品 append之后
根据jQuery的官方描述,live方法在1.7中已经不建议使用,在1.9中删除了这个方法.并建议在以后的代码中使用on方法来替代. on方法可以接受三个参数:事件名.触发选择器.事件函数. 需要特别 ...
- Django【进阶篇 】
Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接数据库,并编写数据访问层代码 业务逻辑层去调用数据访问层执行 ...
- Oracle监听的静态注册和动态注册
静态注册:通过解析listene.ora文件 动态注册:由PMON进程动态注册至监听中 在没有listener.ora配置文件的情况下,如果启动监听,则监听为动态注册.用图形化netca创建的监听,默 ...
- WPF MVVM中在ViewModel中关闭或者打开Window
这篇博客将介绍在MVVM模式ViewModel中关闭和打开View的方法. 1. ViewModel中关闭View public class MainViewModel { public Delega ...
- Python学习笔记(二)——HelloWorld
一.交互式化环境下书写代码 二.使用文本编辑器编辑.py文件 1.建议,使用Nodepad++,好看,免费,度娘直接搜素即可. 2.编写代码 3.保存为.py结尾的文件 4.使用cmd,打开到文件所在 ...
- 【转载】科研ppt制作的体会
转载自实验室陈家雷学长发在bbs 上的帖子,讲解了自己制作ppt的心得体会.学习下. 附件中是我昨天晚上我的组会ppt的pdf版本,另外我对ppt的制作有点自己的理解,基本上都是去年暑假在Harvar ...
- spark API 介绍链接
spark API介绍: http://homepage.cs.latrobe.edu.au/zhe/ZhenHeSparkRDDAPIExamples.html#aggregateByKey
- Websocket简单例子
websocket是Html5的一个协议,也就是说距离我们2016年就几年时间,其他原理我就不说了,直接讲例子 一.准备材料:1.一个开发工具必须支持javaEE7的,原因是javaEE6或以下不支持 ...
- tornado 学习笔记10 Web应用中模板(Template)的工作流程分析
第8,9节中,我们分析Tornado模板系统的语法.使用以及源代码中涉及到的相关类,而且对相关的源代码进行了分析.那么,在一个真正的Web应用程序中,模板到底是怎样使用?怎样被渲染? ...