一、数据库及数据库操作概念
1.数据库概述
概念
存数据的仓库,程序中数据的载体
数据库和变量都可以存储数据,二者有什么区别?
持久性不同:数据库可以持久性存储数据(将数据写入磁盘文件),而变量不能(运行在内存中)
分类:
- 关系型数据库(MySQL、Oracle、SQLite):安全
- 由行和列组成
- 非关系性数据库(Redis、MongoDB):高效
- 数据库存储结构多样,松散:键值对、列表、hash表、字符串
2.python操作数据库相关实现
驱动:
- MySQLdb
- MySQLclient
- PyMySQL
二、PyMySQL基础
1.总体介绍
安装
命令行运行:
pip install pymysql
出现 Successfully install XXX字样,安装成功
校验
命令行运行
pip list
2.基本操作框架
使用pymysql链接数据库,无论增删改查哪种操作,操作流程基本一致
1.导包
2.创建链接
3.创建游标
4.核心操作
5.释放资源
import pymysql
myConn = pymysql.Connect(host="127.0.0.1", port=3306, database="test", user="root", password="root", charset="utf8")
lv = myConn.cursor()
lv.close()
myConn.close()
三、PyMySQL核心操作–增删
1.查
import pymysql
myConn = pymysql.Connect(host="127.0.0.1", port=3306, database="test", user="root", password="root", charset="utf8")
lv = myConn.cursor()
# 发送sql
sql = "select * from t_area"
lv.execute(sql) # 执行语句
print(lv.rowcount) # 获取行数
# row1 = lv.fetchone() # 逐行获取
# row2 = lv.fetchone()
# row3 = lv.fetchone()
# row4 = lv.fetchone()
# row5 = lv.fetchone()
# print(row1)
# print(row2)
# print(row3)
# print(row4)
# print(row5)
rows = lv.fetchall() # 获取所有数据
print(rows)
for row in rows:
print("每一条", row)
lv.close()
myConn.close()
2.增删改
新增
import pymysql
myConn = pymysql.Connect(host="127.0.0.1", port=3306, database="test", user="root", password="root", charset="utf8")
lv = myConn.cursor()
# 发送sql
sql = "INSERT INTO `test`.`t_area` (`area_id`, `area_name`) VALUES (2, '北京')"
lv.execute(sql) # 执行语句
myConn.commit()
print(lv.rowcount) # 获取行数
lv.close()
myConn.close()
修改
import pymysql
myConn = pymysql.Connect(host="127.0.0.1", port=3306, database="test", user="root", password="root", charset="utf8")
lv = myConn.cursor()
# 发送sql
sql = " UPDATE `t_area` SET `area_name` = '上海', `priority` = '0', `create_time` = null, `update_time` = null WHERE `area_id` = 2;"
lv.execute(sql) # 执行语句
myConn.commit()
print(lv.rowcount) # 获取行数
lv.close()
myConn.close()
删除
import pymysql
myConn = pymysql.Connect(host="127.0.0.1", port=3306, database="test", user="root", password="root", charset="utf8")
lv = myConn.cursor()
# 发送sql
sql = " DELETE from t_area where area_id = 2"
lv.execute(sql) # 执行语句
myConn.commit()
print(lv.rowcount) # 获取行数
lv.close()
myConn.close()
注意
- 增删改三种语句都是修改数据库,操作类似
- 增删改执行完毕之后,需要执行提交操作,否则修改失败
- 提交方式:
- 手动提交,连接对象.commit()
- 自动提交 autocommit = true
四、PyMySQL核心操作–事务
1.概念
事务:一台完整的业务逻辑,在这套业务逻辑中可能包含多条SQL,这些SQL执行时,要么成功要么失败
2.特征(面试常见)
- 原子性:不可分割
- 事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败
- 一致性:
- 事务的结果保留不变,即事务的运行并不改变数据的一致性
- 隔离性:
- 又称孤立性,事务的中间的中间状态对其他事务是不可见的
- 持久性:
- 指一个事务一旦提交成功,他对数据库中数据的改变就是永久性的
3.事务提交机制
- 概念
- 提交:commit,将修改写入数据库
- 回滚:rollback,拒绝将修改写入数据库
- 方式
- 自动提交:创建连接时 设置autocommit = True
- 手动提交: 连接对象.commit()
4.事务操作
前提
需要数据库类型为innoDB
-- 将引擎修改为innoDB
ALTER TABLE t_area ENGINE = INNODB
需求
异常相关分支可以由try语句来控制
流程
异常相关分支可以由try语句来控制
'''
伪代码:
try:
sql1 = "" # sql语句
lv.execute(sql1) # 执行代码
myConn.commit()
except Exception as e:
myConn.rollback() # 事务回滚
'''
import pymysql
myConn = pymysql.Connect(host="127.0.0.1", port=3306, database="test", user="root", password="root", charset="utf8")
lv = myConn.cursor()
try:
sql1 = "INSERT INTO `test`.`t_area` ( `area_name`, `priority`) VALUES ('咚咚咚', '1');"
sql2 = "INSERT INTO `test`.`t_area` ( `area_name`, `priority`) VALUES ( '嘻嘻嘻', '22','45');"
lv.execute(sql1)
lv.execute(sql2)
myConn.commit()
except Exception as e:
print(e)
myConn.rollback() # 事务回滚
finally:
lv.close()
myConn.close()
面试题
事务有哪些特征?
- 原子性:不可分割
- 事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败
- 一致性:
- 事务的结果保留不变,即事务的运行并不改变数据的一致性
- 隔离性:
- 又称孤立性,事务的中间的中间状态对其他事务是不可见的
- 持久性:
- 指一个事务一旦提交成功,他对数据库中数据的改变就是永久性的
五、PyMySQL工具类封装
需求
将获取连接,获取游标,以及资源释放这些重复性操作进行封装,需要时再调用
'''
实现封装
将pymysql 的常见的用法实现封装进一个专门的工具类
封装的功能: 1.获取连接 2.获取游标 3.释放资源
'''
import pymysql
# myConn = None
class DBUtils:
# 工具函数1.获取连接
@classmethod
def get_conn(cls):
myConn = pymysql.Connect(host="127.0.0.1", port=3306, database="test", user="root", password="root",
charset="utf8")
# 工具函数2.获取游标
@classmethod
def get_cursor(cls, conn):
return conn.cursor()
# 工具函数3.释放资源
@classmethod
def close(cls, cursor, conn):
if cursor:
cursor.close()
if conn:
conn.close()
注意
封装的工具类所在的py文件的命名要规范,不然引用不到
调用
'''
实现调用工具类中封装的功能
以查询数据库为例
'''
from pymysql_19 import DBUtils
# 导包
# 调用获取连接的工具函数
conn = DBUtils().get_conn()
# 调用获取游标的工具函数
lv = DBUtils().get_cursor(conn)
# 核心:查询为例
sql = "select * from t_area"
lv.execute(sql)
print(lv.fetchall())
# 调用释放资源的工具函数
DBUtils().close(lv, conn)
六、接口自动化测试
概念
程序驱动代替人工驱动实现的接口测试
实现方式
- 工具(jmeter、POSTman、restclient)
- 编码(python,Java)
比较 - 工具
- 优点:1.不需要编程基础2.功能 都是封装好的,直接调用
- 缺点:不灵活
- 编码
- 优点:灵活(扩展性强)
- 缺点:1.需要编程基础2.功能需要自实现、效率偏低
七、Requests库
1.概述
概念
Request库是使用python编写的,可以调用该库的函数直接向服务发送请求,并接收响应
角色定位
jmeter的HTTP请求
安装
pip install requests
校验
pip list
2.GET请求
'''
访问查询接口
流程:
1.根据URL 定位接口资源
2.提交测试资源
3.发送请求,提交并处理响应结果
'''
# 导包
import requests
# 一行搞定
response = requests.get("http://192.168.10.131:1234/hello?a=1")
print("状态码:", response.status_code)
print("文本信息:", response.text)
3. POST请求
# 导包
import requests
# 三要素
date = {"username": "admin", "password": "123456"}
response = requests.post("http://192.168.10.131:8888/api/private/v1/login", data=date)
print("状态码:", response.status_code)
print("文本信息:", response.text)
4.PUT请求
# 导包
import requests
# 三要素
# date = {"username": "admin", "password": "123456"}
myjson = {"id": "500"}
token = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE3MjczNDE3NDIsImV4cCI6MTcyNzQyODE0Mn0.MByFjY0rIuP1TiLwuHtTf941RgmxdiIcWDuN6xH75zg"
response = requests.put("http://192.168.10.131:8888/api/private/v1/users/:id", json=myjson)
print("状态码:", response.status_code)
print("文本信息:", response.text)
5.DELETE请求
# 导包
import requests
# 三要素
response = requests.delete("http://192.168.10.131:8888/api/private/v1/users/:id", params={"id": "500"})
print("状态码:", response.status_code)
print("文本信息:", response.text)
小结
- 四种操作代码结构基本一致
- 区别1:函数名不同(对应请求方式)
- 区别2:提交测试数据和参数名不同
- Get和Delete使用params
- post 和PUT 使用JSON (提交的是JSON数据)或data(提交的是键值对)
为什么Get、Delete、Post、PUT提交数据使用参数不一致?
- 因为提交数据格式不同,所以使用的数举使用参数不一致
- Get Delete 请求格式:在请求行
- Post和PUT 请求格式:在请求体
6.response响应
相应由行头体三部分组成
import requests
response = requests.get("http://www.baidu.com")
# 行解析
print("URL", response.url)
print("URL", response.status_code)
print("-" * 80)
# 头
print("响应头", response.headers)
print("响应头", response.cookies)
print("响应头", response.encoding)
print("-" * 80)
# 体
print("文本响应体", response.text) # 服务器传来的是文本信息如html
# print("二进制响应体", response.content) # 服务器传来的是图片/视频/音频/ 非文本数据
# print("JSON响应体", response.json())
7.登录实现思路
7.1 cookie
import requests
# cookie PHPSESSID
response1 = requests.get("http://192.168.10.139/index.php?m=Home&c=User&a=verify") # 获取验证码
print("响应码", response1.status_code)
print(response1.cookies)
id = response1.cookies.get("PHPSESSID")
print(id)
print("-"*80)
data = {"username": "1912703877@qq.com", "password": "c1912703877", "verify_code": "8888"}
cookice = {"PHPSESSID": id}
# 登录接口
response2 = requests.post("http://192.168.10.139/index.php?m=Home&c=user&a=do_login", data=data, cookies=cookice)
print(response2.status_code)
print(response2.text)
print("-"*80)
# 查询订单接口
response3 = requests.get("http://192.168.10.139/Home/Order/order_list.html",cookies=cookice)
print(response3.status_code)
print(response3.text)
7.2 session
import requests
# 创建session对象
mySession = requests.session()
response1 = mySession.get("http://192.168.10.139/index.php?m=Home&c=User&a=verify") # 获取验证码
print("响应码", response1.status_code)
print("-"*80)
data = {"username": "1912703877@qq.com", "password": "c1912703877", "verify_code": "8888"}
# 登录接口
response2 = mySession.post("http://192.168.10.139/index.php?m=Home&c=user&a=do_login", data=data)
print(response2.status_code)
print(response2.text)
print("-"*80)
# 查询订单接口
response3 = mySession.get("http://192.168.10.139/Home/Order/order_list.html")
print(response3.status_code)
print(response3.text)
八、Pytest(初级讲解–讲三次)
1.概述
pytest是python的一种第三方的单元测试框架
特点
同自带的unittest测试框架类似,相比于unittest框架使用起来更简洁,更高效
安装
推荐使用3.10
pip install pytest==3.10
卸载
pip uninstall pytest
校验
pip list
pytest --version
2.运行
class TestLogin:
def test_a(self): # 以test_ 开头的是测试函数
print("aaaa")
def test_b(self):
print("bbb")
# 运行方式:终端命令行中 输入pytest 文件名
# 如果想让命令行支持打印 需要使用 -s 参数 具体方法:
# pytest -s .\test_login.py
3. setup 和 teardown
注意
这里的mytest版本过低则无法正常使用setup和teardown,因此更新了版本,但是在pytest8.0以后 setup与teardown就弃用了,需要使用setup_method
和teardown_method
def setup_method(self):
print("测试用例执行前的初始化,如:打开浏览器,加载网页...")
def setup_class(self):
print("类执行前的初始化,如:创建日志对象,创建数据库的连接,创建接口的请求对象...")
def teardown_method(self):
print("\n测试用例执行后的收尾操作")
def teardown_class(self):
print("类执行后的收尾的工作,比如:销毁日志对象,销毁数据库的连接,销毁接口的请求对象")
练习
class TestLogin:
def setup_method(self):
print("---setup---")
def teardown_method(self):
print("---teardown---")
def test_a(self): # 以test_ 开头的是测试函数
print("aaaa")
def test_b(self):
print("bbb")
# 运行方式:终端命令行中 输入pytest 文件名
# 如果想让命令行支持打印 需要使用 -s 参数 具体方法:
# pytest -s .\test_login.py
4.配置文件
4.1 配置文件概述
应用场景
使用配置文件,可以通过配置项来选择执行哪些文件目录下的哪些测试模块
使用方式
- 项目下新建的script模块
- 将测试脚本房嫂script中
- pytest的配置文件放到自动化项目目录下
- 名称为pytest.ini
- 第一行内容为[pytest],后面写具体的配置参数
- 命令行运行时会使用该配置文件中的配置
4.2 配置文件实现
[pytest]
addopts = -s
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_function = test*
参数解释:
addopts = -s 表示命令行参数
testpaths , python_files , python_classes ,python_function
表示执行哪一个包下面的哪些.py结尾的文件,以及哪些前缀开头的类,以及哪些前缀开头的测试函数
注意点
- 配置文件是否正常的加载?
- 通过控制台的inifile进行查看
- Windows中可能出现"gdk" 错误?
- 删除ini文件中的所有中文
- 在工作中这个文件也需要复制粘贴?
- 是的,一个项目只会用一次,只需要理解,会修改即可
5.数据参数化
应用场景
数据参数化可以使代码更简洁,可读性更好
import pytest
class TestLogin:
@pytest.mark.parametrize("params",
[{"username": "zhangsan", "password": "123"}, {"username": "lisi", "password": "123"}])
def test_a(self, params):
print(params)
print(params["username"])
print(params["password"])
6.测试报告插件
应用场景
自动化测试脚本最终执行是通过还是不通过,需要通过测试报告进行体现
安装
pip install pytest-html == 1.21.1 在最新的2.0上有错误
使用
在配置文件总的命令参数中增加 --html=用户路径/report.html
示例
addopts = -s --html=report/report.html
7.集成思路
目的
框架化测试用例的执行
非规范化实现
import requests
class TestDemo:
# 初始化函数
def setup_method(self):
self.mySession = requests.Session()
def teardown_method(self):
self.mySession.close()
def test_login(self): # 以test_ 开头的是测试函数
data = {"": "", "": ""}
response = self.mySession.post("http://ip:port", data)
# 断言
assert 200 == response.json().get("status")
def test_order(self):
data = {"": "", "": ""}
# 登录
response1 = self.mySession.post("http://ip:port", data)
# 获取订单
response2 = self.mySession.get("http://ip:port", data)
# 断言
assert 200 == response2.json().get("status")
小结:存在的问题
- 参数化:没有使用参数化动态的导入数据
- 封装:测试函数中的请求数据业务相关的视线高度重复
九、接口自动化框架实现
1. 接口自动化流程
- 需求分析
- 挑选需求做自动化测试的功能
- 设计测试用例
- 搭建自动化测试环境[可选]
- 设计自动化测试项目的框架[可选]
- 编写代码
- 执行测试用例
- 生成测试报告并分析结果
2.测试用例设计
3.图解
4. 创建框架结构
--api #封装请求
--script #编写测试脚本
--data #存放测试数据
--utils #存放工具类
--report #测试报告生成目录
--app.py #存放常量
--pytest.ini #pytest配置文件
5.测试脚本部分实现
用自动化实现对案例增删改查接口的自动化测试
import requests
from api.AreaAPI import AreaAPI
from api.AreaDBAPI import AreaDBAPI
class TestArea:
def setup_method(self):
self.session = requests.session()
self.area_api = AreaAPI(self.session)
def teardown_method(self):
self.session.close()
# 查询
def test_list_area(self):
params = {"pagenum": "1", "pagesize": "10"}
response = self.area_api.list_area(params=params)
print("状态码", response.status_code)
print("响应体", response.test)
# 新增
def test_add_area(self):
data = {"username": "admin", "password": "123456"}
response = self.area_api.add_area(data=data)
print("状态码", response.status_code)
print("响应体", response.test)
# 修改(编辑用户提交)
def test_modify_area(self):
id = AreaDBAPI.select_id_by_name("admin")
myjson = {"id": id}
response = self.area_api.modify_area(myjson=myjson)
print("状态码", response.status_code)
print("响应体", response.test)
# 删除(删除单个用户)
def test_remove_area(self):
id = AreaDBAPI.select_id_by_name("admin")
params = {"id": id}
response = self.area_api.remove_area(params=params)
print("状态码", response.status_code)
print("响应体", response.test)
6.API实现
import requests
from app import BASE_URL, SERVER_URL
class AreaAPI:
def __int__(self, session):
self.session = session
self.list_area_url = BASE_URL + SERVER_URL + "users"
self.add_area_url = BASE_URL + SERVER_URL + "login"
# 编辑用户提交
self.modify_area_url = BASE_URL + SERVER_URL + "users/:id/state/:type"
# 删除单个用户
self.remove_area_url = BASE_URL + SERVER_URL + "users/:id"
# 查询
def list_area(self, params):
response = self.session.get(self.list_area_url, params=params)
return response
# 新增
def add_area(self, data):
response = self.session.Post(self.add_area_url, data=data)
return response
# 修改
def modify_area(self, myjson):
response = self.session.put(self.modify_area_url, json=myjson)
return response
# 删除
def remove_area(self, params):
response = self.session.delete(self.remove_area_url, params=params)
return response
7.API和测试脚本优化
import requests
from api.AreaAPI import AreaAPI
from api.AreaDBAPI import AreaDBAPI
class TestArea:
def setup_method(self):
self.session = requests.session()
self.area_api = AreaAPI(self.session)
def teardown_method(self):
self.session.close()
# 查询
def test_list_area(self):
params = {"pagenum": "1", "pagesize": "10"}
response = self.area_api.list_area(params=params)
print("状态码", response.status_code)
print("响应体", response.test)
# 新增
def test_add_area(self):
data = {"username": "admin", "password": "123456"}
response = self.area_api.add_area(data=data)
print("状态码", response.status_code)
print("响应体", response.test)
# 修改(编辑用户提交)
def test_modify_area(self):
id = AreaDBAPI.select_id_by_name("admin")
myjson = {"id": id}
response = self.area_api.modify_area(myjson=myjson)
print("状态码", response.status_code)
print("响应体", response.test)
# 删除(删除单个用户)
def test_remove_area(self):
id = AreaDBAPI.select_id_by_name("admin")
params = {"id": id}
response = self.area_api.remove_area(params=params)
print("状态码", response.status_code)
print("响应体", response.test)