注释:运行环境linux+python3.7.3+pytest5.2.2+postgresql+flask-sqlalchemy2.4.1python
公司以前用的NoSQL做数据管理,最近让我把数据库使用关系型数据库翻译一下,老大决定使用postgresql并采用ORM管理数据库,数据库翻译完,老大说为了保证数据稳定迁移,让我用pytest写一下测试用例,第一次写啊,遇到各类坑,第一就是测试项目的目录搭建,由于以前会一点unittest,unittest实现自动化测试能够写在同一个类中进行测试,当时不知道怎么搭合适,各类实验,最终采用下边的目录结构,很好的作到解耦。第二坑就是转化过来的数据表各类外键约束,直接使用pytest运行测试他会自动的搜索test开头的测试用例,这样没有顺序,我某些表中用到的外键这张表输入尚未插入,就会测试失败,后来又使用pytest按照表的外键关系一个个的添加,等价于手动排序linux
1:搭建测试项目结构(目录结构)这里每一个文件都是包,是由于你在使用pytest执行用例的时候他会自动搜索包中test开头的文件sql
. ├── manage.py # 项目启动文件 ├── moduls # 模型类存放包 │ ├── __init__.py │ └── moduls.py # 模型类py文件 └── test # 单元测试包 ├── conftest.py # pytest配置(初始化及测试后的清理工做) ├── data # 测试用例使用到的数据管理包 │ ├── __init__.py │ └── test_db_add_delete # 测试用例使用到的数据管理包(名字以test开头+什么测试+测试了什么功能) │ ├── __init__.py │ └── test_user.json # 数据json文件 (名字以test开头+测试的那个模型类) ├── __init__.py ├── tests # 测试用例管理包 │ ├── __init__.py │ └── test_db_add_delete # 测试用例管理包(名字以test开头+什么测试+测试了什么功能, 与测试数据管理包名字一致) │ ├── __init__.py │ └── test_user.py # 测试用例py文件 (名字以test开头+测试的那个模型类,与json文件一致) └── utils # 工具管理包 ├── get_data.py # 用例与数据的解耦(获取测试用例使用到的数据) └── __init__.py
2:文件解释及demo数据库
2-1:conftest.pyjson
注释:这个文件我用了pytest.fixture(scope="session", autouse=True)这个装饰器内的scope参数能够设置被装饰的函数何时执行及执行多少次(session:整个测试开始到所有测试完成只执行一次。->class:表示你测试用例若是写在类中,那么每执行一个类中的用例就会执行一次这个函数。->function:表示你用例是写在函数中,那么每执行一个函数用的用例,这个函数就会被执行一次。->model:表示你执行这个模块中的全部用例的时候,这个函数就会执行一次),我这里要使用数据库的关联关系,全部我用的是session,意思就是在我使用pytest执行用例的时候,这个函数只执行一次,由于我使用这个被装饰的函数作了数据库的初始化和测试完成后的清理工做。这个函数中的yield相似于unittest.TestCase中的setUp和tearDown方法yield以前的等价于setUp方法中的逻辑,以后的等价于tearDown中的逻辑flask
import os import pytest @pytest.fixture(scope="session", autouse=True) def app_db_session(): """ 数据库fixture :return: """ # 引入项目中的app和db from manage import app, db # 测试数据库与开发数据库的解耦 app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql+psycopg2://dbname:password@127.0.0.1:5432/test" app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # 配置数据库(数据库的开销设置为False) app.testing = True db.drop_all() # 执行数据库的清理工做确保测试使用的是新的数据库,若是要用到原始数据请忽略 db.create_all() # 执行数据表的建立工做 yield db.session.remove() # 关闭pytest数据库链接 db.drop_all() # 清理数据表
2-2:moduls.py 我就不贴代码了,这个就是你建立的模型类,唉仍是贴一个简单的demo吧session
# -*- coding:utf-8 -*- import datetime from sqlalchemy import Column, TIMESTAMP, SmallInteger, String, func, Integer from sqlalchemy.dialects.postgresql import ARRAY, array from manage import db class BaseModel(db.Model): __abstract__ = True create_time = Column(TIMESTAMP, default=datetime.datetime.now(), comment="建立时间") update_time = Column(TIMESTAMP, default=datetime.datetime.now(), onupdate=func.now(), comment="更新时间") def to_dict(self): """ 单对象序列化 :return: """ _dict = {} _dict.update(self.__dict__) if "_sa_instance_state" in _dict: del _dict['_sa_instance_state'] return _dict class User(BaseModel): id = Column(Integer, primary_key=True, comment="主键ID") name = Column(String(32), comment="名字") age = Column(SmallInteger, comment="年龄")
2-3:utils->get_data.py 这个里边的代码仍是要贴出来的,这个里边我定义了2个方法,一个方法是获取data中的数据路径和tests中的测试用例路径,另一个方法是读取测试数据用的数据,将json数据处理成为元祖套列表的形式返回架构
import os import json def get_data_path(case_path: os.path) -> os.path: file_name = os.path.dirname(case_path).split(os.sep + 'tests' + os.sep, 1) return os.sep.join([file_name[0], 'data', file_name[1], os.path.basename(case_path).replace('.py', '.json')]) def get_test_data(case_path: os.path) -> list: test_data_path = get_data_path(case_path) with open(test_data_path, encoding='utf-8') as f: dat = json.loads(f.read()) return [tuple(i.values()) for i in dat['test']]
2-4:test_user.py文件,这个文件编写测试用例,我以为要明白一点,你要测试什么,拿数据库来讲,我要测试的是正确的数据类型能够插入到数据库中,数据类型、约束、外键不对就没法插入数据,我这里测试的就是数据插入功能和删除功能,那么我这里测试用例的编写就明确了,我初始化一个模型类对象,给对象添加属性,而后在add->commit插入这条数据,而后我可以查询到刚才插入的这条数据,assert 取出来的值和我存入的值一直,那么我就认为是测试成功的。下边来看代码app
这里我要用红字标明一些注意事项,必定要注意,要了解更详细的操做,请关注我pytest框架讲解框架
1:parametrize第一个参数是字符串,参数用逗号分隔
2:parametrize第二个参数是[(), (), ()] 有几个元祖就表示这个用例执行几回
3:被装饰的函数中的个数要和注意事项1的参数个数同样
4:注意事项1,和注意事项2,他们的参数位置要对应起来和后边要写的json文件同样,若是使用个人架构来测试,只要第一个参数的位置和json文件内的位置对应就能够了
import pytest import datetime from manage import db from moduls.moduls import User from test.utils.get_data import get_test_data data = get_test_data(__file__) # 这里咱们使用了数据与用例的解耦,咱们就使用paramtrize来传参 # 注意 1:它的第一个参数是字符串, 第二个参数是[(), ()] 这个类型的数据,列表中几个元祖就会执行几回测试(注意:他要和字符串参数位置对应) # 2:字符串内的参数要和函数中的参数个数一致 @pytest.mark.parametrize("name, age", data) def test_user(name, age): obj = User() obj.name = age obj.age = age db.session.add(obj) db.session.commit() obj = User.query.filter_by(name=name).first() # 最好这里用unique类型的字段查询,我这里demo中只有2个字段,因此就这样写了
assert obj.age == age
2-5:test_user.json文件,上边也提了一下,这个json文件你key的顺序要和你paramtrize的第一个参数位置一致, 我这里test中[ ] 中有5个字典,那么我用例在运行起来的时候就会执行5次测试
{ "test": [ { "name": "5ee4ef28013f48f69a76bc16c7089245", "age": 30 }, { "name": "5ee4ef28013f48f69a76bc16c7089246", "age": 40 }, { "name": "5ee4ef28013f48f69a76bc16c7089247", "age": 50 }, { "name": "5ee4ef28013f48f69a76bc16c7089248", "age": 60 },{ "name": "5ee4ef28013f48f69a76bc16c7089249", "age": 70 } ] }
3:运行 在test文件内直接pytest就能够执行用例了
到这里本次测试就讲完了,若是想要更详细的讲解,后续我会出一个pytest框架的讲解