TreeMind树图在线AI思维导图
当前位置:树图思维导图模板IT互联网产品结构Pytest接口自动化思维导图

Pytest接口自动化思维导图

  收藏
  分享
免费下载
免费使用文件
U659662237 浏览量:792024-02-26 10:15:02
已被使用13次
查看详情Pytest接口自动化思维导图

Pytest接口自动化相关内容讲解

树图思维导图提供 Pytest接口自动化 在线思维导图免费制作,点击“编辑”按钮,可对 Pytest接口自动化  进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:7e361d7fc8a98f17271d7ec8819f8b97

思维导图大纲

Pytest接口自动化思维导图模板大纲

pytest介绍及环境搭建

是Python的一种单元测试框架

可做接口、UI自动化

测试必学的框架

windows环节搭建Python&pycharm

mac环境搭建Python&pycharm

Http接口介绍及实战

Http是什么

是一个协议(服务器传输超文本到浏览器的传送协议),基于TCP/IP通信协议来传递数据(HTML文件、图片文件、查询结果等)

HTTP请求方式

get 查询

post 新增

put 修改

delete 删除

其他:HEAD、CONNECT、OPTIONS、TRACE、PATCH

接口测试基础知识,看接口测试.xmid

requests模块进行get请求

import requests r = requests.get('https://api.github.com/events') print(r.status_code) print(r.json()) print(r.text)

import requests #带参数的get请求 params={'key': 'value'} r=requests.post('https://httpbin.org/post',data=params) print(r.json())

requests模块进行post请求

import requests params={'key': 'value'} r=requests.post('https://httpbin.org/post',data=params) print(r.json())

json传参

import requests json_data = { "title": "foo", "body": "bar", "userId": 1 } r=requests.post(url="https://jsonplaceholder.typicode.com/posts",json=json_data) print(r.status_code,r.json())

requests模块其他请求方式

r = requests.put('https://httpbin.org/put', data={'key': 'value'}) r = requests.delete('https://httpbin.org/delete') r = requests.head('https://httpbin.org/get') r = requests.options('https://httpbin.org/get')

requests请求加入headers

import requests headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" } url = 'https://movie.douban.com/j/search_subjects' params= {"type":"movie","tag":"热门","page_limit":"50","page_start":"0"} r = requests.get(url=url,params=params,headers=headers) print(r.status_code) print(r.json())

不加header,返回418,418是反爬的意思

requests模块session用法

import requests #创建一个会话 req = requests.Session() url = "http://sellshop.5istudy.online/sell/user/login" data={"username":"test01",'password':'123456'} #登录,req保存了cookie和session r = req.post(url,data=data) print(r.text) url2 = 'http://sellshop.5istudy.online/sell/seller/order/list?page=3&size=10' r2= req.get(url2) print(r2.text)

pytest基础用法

文件命名规范

1、.py测试文件必须以test_开头(或_test结尾) 2、测试方法必须以test开头 3、测试类必须以Test开头,并且不能有init方法

def test_one(): expect = 1 actual = 1 assert expect == actual def test_two(): expect = 1 actual = 2 assert expect == actual def two(): expect = 1 actual = 2 assert expect == actual

子主题 2

测试用例执行顺序

默认执行顺序

使用pytest-ordering自定义顺序

安装pytest-ordering包

#1、登录 2、查找商品 3、下单 4、支付 import pytest def testlogin(): print("login...") @pytest.mark.run(order=1) def test_search(): print("search...") def test_order(): print("order...") def testpay(): print("pay...")

常用断言类型

def test_assert(): assert 1 != 2 assert 1<2 assert 2>1 assert 1>=1 assert 1<=1 assert 'a' in 'abc' assert 'a' not in 'bc' assert True is True

pytest+requests练习

import requests def test_getmobile(): params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(): params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value'

pytest指定目录、文件执行用例

指定目录

Treminal里执行命令:pytest xxx/

指定文件

Treminal里执行命令:pytest xxx/xx.py

Terminal里直接执行pytest,查找目录下所有的test项

pytest配置testpaths

主目录创建pytest.ini文件

[pytest] testpaths=./test_requests

然后Terminal里直接命令:pytest,就会只执行test_requests目录下的测试项

pytest运行参数-m -k

pytest -m 执行特定的测试用例

pytest -m 'pro'

示例: 打标签:pytest.ini定义个markers

[pytest] testpaths=./test_requests markers= p0=高优先级 test=测试环境 pro=生产环境

import pytest @pytest.mark.p0 def test_one(): expect = 1 actual = 1 assert expect == actual @pytest.mark.test def test_two(): expect = 1 actual = 2 assert expect == actual def two(): expect = 1 actual = 2 assert expect == actual

然后Treminal里执行: pytest -m 'test'

import pytest @pytest.mark.pro class TestThree: def test_one(self): expect = 1 actual = 1 assert expect == actual def test_two(self): expect = 1 actual = 2 assert expect == actual def two(self): expect = 1 actual = 2 assert expect == actual

pytest -m 'pro'

pytest -k 执行用例包含“关键字”的用例

pytest -k 'mobile'

pytest -q 说明:简化控制台的输出

子主题 1

pytest -v 可以输出用例更加详细的执行信息

pytest -v .\testcase\test_mobile.py

pytest -s 输出我们用例中的调试信息

pytest -s .\testcase\test_mobile.py

pytest使用ini配置指定运行参数

[pytest] testpaths=./testcase markers= p0=高优先级 test=测试环境 pro=生产环境 addopts:-s

然后Terminal里输入 pytest .\testcase\test_mobile.py

setup、teardown详解

模块级

setup_module/teardown_module

开始于模块始末,生效一次

import pytest import requests def setup_module(): print("准备测试数据") def teardown_module(): print("清理测试数据") def test_getmobile(): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main(['-q','test_module.py'])

函数级

setup_function/teardown_function

对每条函数用例生效(不在类中)

import pytest import requests def setup_function(): print("准备测试数据") def teardown_function(): print("清理测试数据") def test_getmobile(): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main(['-q','test_function.py'])

类级

setup_class/teardown_class

只在类中前后运行一次(在类中)

import pytest import requests class TestMobile: def setup_class(self): print("准备测试数据") def teardown_class(self): print("清理测试数据") def test_getmobile(self): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(self): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main(['-q','test_class.py'])

方法级

setup_method/teardown_method

开始于方法始末(在类中)

import pytest import requests class TestMobile: def setup_method(self): print("准备测试数据") def teardown_method(self): print("清理测试数据") def test_getmobile(self): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(self): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main(['-q','test_method.py'])

skip和skipif用法,测试用例跳过

@pytest.mark.skip

@pytest.mark.skipif

示例

import pytest import requests key = '12000000' @pytest.mark.skip def test_getmobile(): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' # @pytest.mark.skipif('1==1') @pytest.mark.skipif('len(key)!=11') def test_postmobile(): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main()

解决接口参数依赖

#用户名登录 #拿到登录的token,获取用户信息 import requests class TestUser: def test_login(self): uname="baobao" passwd="123456" # res = requests.post("/login",json={"username":uname,"password":passwd}) token = "token" assert token =="token" return token,uname def test_userinfo(self): token,username = self.test_login() headers = {"token":token} # res = requests.post('/get_user',headers=headers) assert headers['token'] == token assert username == "baobao"

实际项目用fixture,前置步骤获取到token

pytest进阶之fixture用法

fixture概念fixture是 pytest 用于将测试前后进行预备、清理工作的代码处理机制。 fixture相对于setup和teardown来说有以下几点优势: fixure命名更加灵活,局限性比较小 conftest.py 配置里面可以实现数据共享,不需要import就能自动找到一些配置

fixture夹具,@pytest.fixture scope作用域的意思

(scop="function") 每一个函数或方法都会调用

默认就是function

示例

import pytest import requests #默认scope是function @pytest.fixture() def func(): print("我是前置步骤") def test_getmobile(func): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main()

只有调用了func函数的才会运行func

import pytest import requests @pytest.fixture(autouse=True) def func(): print("我是前置步骤") def test_getmobile(func): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main()

func的autouse是TRUE时,所有函数方法都会调用func

(scop="class") 每一个类调用一次

import pytest import requests @pytest.fixture(scope="class",autouse=True) def func(): print("我是前置步骤") class TestClassFixture: def test_getmobile(self): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(self): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main()

(scop="module") 每一个.py文件调用一次

import pytest import requests @pytest.fixture(scope="module",autouse=True) def func(): print("我是前置步骤") class TestClassFixture: def test_getmobile(self): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' def test_postmobile(self): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main()

(scop="session") 是多个文件调用一次,.py文件就是module

创建py文件conftest.py,必须叫conftest

import pytest @pytest.fixture(scope="session",autouse=True) def test_session(): print("我是session级的fixture")

之后Terminal里执行, pytest -s .\testcase\test_fixtur e\

fixture的作用范围(执行顺序):session>module>class>function

import pytest @pytest.fixture(scope="function") def t_function(): print("我是function fixture") @pytest.fixture(scope="class") def t_class(): print("我是class fixture") @pytest.fixture(scope="module") def t_moudule(): print("我是moudule fixture") @pytest.fixture(scope="session") def t_session(): print("我是session fixture") class TestOrder: def test_order(self,t_function,t_session,t_moudule,t_class): assert 1==1

pytest使用conftest管理fixture

conftest.py conftest.py为固定写法,不可修改名字 使用conftest.py文件方法无需导入 函数作用于当前文件夹及下属文件

conftest.py import pytest @pytest.fixture(scope="session",autouse=True) def test_session(): print("我是session级的fixture") @pytest.fixture(scope="function") def test_func1(): print("我是func1级别的fixture") @pytest.fixture(scope="function") def test_func2(): print("我是func2级别的fixture")

test_function import pytest import requests #默认scope是function # @pytest.fixture(autouse=True) # def func(): # print("我是前置步骤") def test_getmobile(test_func1,test_func2): print("测试get请求") params = {'key1': 'value1', 'key2': 'value2'} r=requests.get('https://httpbin.org/get',params=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2' #可以选择不同fixture方法 def test_postmobile(test_func2): print("测试post请求") params = {'key': 'value'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 print(r.json()) res=r.json() assert res['args'] == {} assert res['data'] == '' assert res['form']['key'] == 'value' if __name__ == '__main__': pytest.main()

conftest可以放在上层目录,但是要注释掉当前目录的方法,如果方法都一样的话

pytest使用fixture返回数据,可以接收返回值

conftest.py @pytest.fixture(scope="function") def get_params(): params = {'key1': 'value1', 'key2': 'value2'} return params

test_fixture_return.py import pytest import requests def test_getmobile(get_params): print("测试get请求") #第1种方法 # r=requests.get('https://httpbin.org/get',params=get_params) #第2种方法 key1 = get_params['key1'] key2 = get_params['key2'] r=requests.get('https://httpbin.org/get',params={'key1':key1, 'key2':key2}) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2'

pytest使用yield做后置处理

@pytest.fixture(scope="function",autouse=True) def func(): print("我是前置步骤") yield "老醒" print("我是后置步骤")

def test_postmobile(func): print(func) print("测试post请求") params = {'key1': 'value1', 'key2': 'value2'} r = requests.post('https://httpbin.org/post', data=params) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/post?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2'

子主题 3

pytest的usefixtures方法,无法接收返回值

conftest.py @pytest.fixture(scope="function") def use_fixture1(): print("我现在使用use_fixtures1") @pytest.fixture(scope="function") def use_fixture2(): print("我现在使用use_fixtures2")

test_usefixture.py import pytest @pytest.mark.usefixtures("use_fixture1") @pytest.mark.usefixtures("use_fixture2") def test_usefixtures(): print("usefixtures")

靠近方法的fixture先执行

@pytest.mark.usefixtures("use_fixture1","use_fixture2") # @pytest.mark.usefixtures("use_fixture2") def test_usefixtures(): print("usefixtures")

先执行fixture1

fixture中params和ids

@pytest.fixture(params=["参数1","参数2"],ids=["用例1"],"用例2"])

request和params\ids是关键字,不能改

import pytest #request和params\ids是关键字,不能改 @pytest.fixture(params=["data1","data2"],ids=["case1","case2"]) def params_fixture(request): return request.param def test_params(params_fixture): print(params_fixture)

子主题 2

实际中不这么调用,调用yaml文件更多

pytest进阶参数化用法

pytest的参数化parametrize参数

parametrize 参数化

参数化可以组装测试数据,在测试前定义好测试数据,并在测试用例中使用

单参数

import pytest #单参数单次循环,参数化里的key和方法里的key要相同 @pytest.mark.parametrize("name",["老醒"]) def test_parametrize(name): print("测试parametrize单次循环:我是"+name)

#单参数多次循环 #运行时,将数组里的值分别赋值给变量,每赋值一次就运行一次 @pytest.mark.parametrize("name1",["老醒","anqila","huagnzhogn"]) def test_parametrize(name1): print("我是"+name1) assert name1 == "anqila"

多参数

import pytest #列表形式 @pytest.mark.parametrize("name,word",[["安琪拉","魔法师"], ["鲁班","射手"],["亚瑟","肉"]]) def test_parametrize02(name,word): print(f'{name}的职业是{word}') #元组形式 @pytest.mark.parametrize("name,word", [("安琪拉", "魔法师"), ("鲁班", "射手"), ("亚瑟", "肉")]) def test_parametrize02(name, word): print(f'{name}的职业是{word}') #字典形式 @pytest.mark.parametrize("hero", [{"name":"魔法师",}, {"name": "射手"}, {"name": "肉"}]) def test_parametrize02(hero): print(hero["name"])

YAML(重要)

YAML是一个对所有编程语言都很友好的数据序列化标准。他是一种直观的能够被电脑识别的数据序列化格式,是一种可读性高且容易被人类阅读、容易和脚本语言(不仅仅是Python)交互,用户表达资料序列的编程语言。本质是一种通用的数据串行化格式

适用场景 在脚步语言中使用,实现简单,解析成本低;  序列化;  编程时写配置文件,比xml快,比ini文档功能更强。  YAML是专门用于写配置文件的语言,非常简洁和强大,远比JSON格式方便。

YAML支持的三种数据结构  对象:即键值对的集合,又称为映射(mapping)/哈希(hashes)/字典(dictionary);  数组:一组按次序排列的值,又称为序列(sequence)/列表(list);  纯量:单个的、不可再分的值

yaml写法

对象 key: child-key: value child-key2: value2

等于 {"key": {"child-key": "value","child-key2": "value2"}}

等于 字典 A: {name:xx,word:xx,Hp:xx}

hero: name: 安琪拉 word:火焰是我最喜欢的玩具 Hp:445.5

数组 key: - A - B - C 更多用在参数化上

等于 {"key": [A,B,C]}

hero: name: 安琪拉 word: 火焰是我最喜欢的玩具 Hp: 445.5

组合 - child-key: value child-key2: value2

等于 {"key": [{"child-key": "value","child-key2": "value2"}]}

heros: - name: 黄忠 word: 周日被我射熄火了 Hp: 440

数组嵌套 key: - - A - B - C

等于 {"key": [[A,B,C]]}

heros_name_list: - - 安琪拉 - 黄忠 - 小乔

Phon读取yaml文件内容

YAML第三方包安装

pip install pyyaml

import yaml f = open("../config/data.yaml", encoding="utf8") data = yaml.safe_load(f) print(data) print(data['hero']) print(data['heros_name']) print(data['heros']) print(data['heros_name_list'])

{'hero': {'name': '安琪拉', 'word': '火焰是我最喜欢的玩具', 'Hp': 445.5}, 'heros_name': ['安琪拉', '黄忠', '小乔'], 'heros': [{'name': '黄忠', 'word': '周日被我射熄火了', 'Hp': 440}], 'heros_name_list': [['安琪拉', '黄忠', '小乔']]} {'name': '安琪拉', 'word': '火焰是我最喜欢的玩具', 'Hp': 445.5} ['安琪拉', '黄忠', '小乔'] [{'name': '黄忠', 'word': '周日被我射熄火了', 'Hp': 440}] [['安琪拉', '黄忠', '小乔']]

yaml复杂数据演示,接口请求

yaml格式 test: name: useradd request: url: http://47.108.153.47:8089/qzcsbj/user/add method: POST headers: Accept: "*/*" json: token: string user: id: 0 username: test password: test realname: test sex: 女 birthday: string phone: 13000000000 utype: string addtime: string adduser: string

import yaml f = open("../config/data.yaml", encoding="utf8") data = yaml.safe_load(f) print(data['test']['name']) print(data['test']['request']['url']) print(data['test']['request']['method']) print(data['test']['request']['headers']) print(data['test']['request']['json']) print(data['test']['request']['json']['token']) print(data['test']['request']['json']['user'])

子主题 1

yaml在线格式校验

https://www.bejson.com/validators/yaml_editor/

yaml+parametrize使用(重点)

heros: - name: 黄忠 word: 周日被我射熄火了 Hp: 440

import yaml,os path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),"config","data.yaml") def read_data(): # f = open("../config/data.yaml",encoding="utf-8") with open(path,encoding="utf") as f: data = yaml.safe_load(f) return data getdata = read_data()

#单参数 @pytest.mark.parametrize("name",getdata['heros']) def test_parametrize_yaml(name): print(name)

heros_namewords: - - 黄忠 - 周日被我射熄火l - - 安琪拉 - 火焰是我最喜欢的玩具

import yaml,os path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),"config","data.yaml") def read_data(): # f = open("../config/data.yaml",encoding="utf-8") with open(path,encoding="utf") as f: data = yaml.safe_load(f) return data getdata = read_data()

#多参数 @pytest.mark.parametrize("name,word",getdata['heros_namewords']) def test_parametrize_yaml1(name,word): print(f'{name}的台词是{word}')

使用yaml进行接口实战

paramspost: #用户名,密码 - [ qzcsbj,123456 ] - [ test01,123456 ]

2条用例通过

import yaml,os path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),"config","data.yaml") def read_data(): # f = open("../config/data.yaml",encoding="utf-8") with open(path,encoding="utf") as f: data = yaml.safe_load(f) return data getdata = read_data()

@pytest.mark.parametrize("username,password",getdata['paramspost']) def test_postmobile(username,password): print("测试post请求") headers = {"Content-Type": "application/json","Accept":"application/json"} r = requests.post('http://47.108.153.47:8089/qzcsbj/user/login', data={"username":username,"password":password},headers=headers) print(r.status_code) assert r.status_code == 200

yaml中使用自定义函数,应用场景(值随机)

随机数据,faker ,pip install faker

faker的详细用法:https://zhuanlan.zhihu.com/p/422182497

示例

person: #名字格式 北京-xx参考代码 带固定不固定都可 - name: 北京-${random_name()}-测试 age: ${age()} sex: ${sex()} - name: 伤害-${random_name()}-测试 age: ${age()} sex: ${sex()}

import random from faker import Faker #要生成中文的随机数据,可以在实例化时给locale参数传入‘zh_CN’这个值 #详细用法:https://zhuanlan.zhihu.com/p/422182497 fake = Faker(locale='zh_CN') def func_yaml(data): #判断是否字典 if isinstance(data,dict): for key,value in data.items(): if '${' and '}' in str(value): start = str(value).index('${') end = str(value).index('}') func_name = str(value)[start+2:end] #名字格式 北京-xxx data[key] = str(value)[0:start] + str(eval(func_name)) +str(value)[end+1:len(value)] # data[key] = eval(func_name) #名字随机 return data def age(): return random.randint(1,100) def sex(): sex = random.choice(['男','女']) return sex def random_name(): return fake.name() if __name__ == '__main__': data = {'name': '北京-${random_name()}-测试', 'age': '${age()}', 'sex': '${sex()}'} print(func_yaml(data))

test_person.py import pytest from utils.read_data import getdata from utils.yaml_util import func_yaml @pytest.mark.parametrize("data",getdata["person"]) def test_person(data): print(func_yaml(data))

路径拼接

import os print(os.path.realpath(__file__)) print(os.path.dirname(os.path.realpath(__file__))) print(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) print(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),"config","data.yaml"))

框架代码分层优化

测试用例代码分层思想 思考一个问题:如果你的域名变了,该怎么办?

uitls目录,公共部分封装

读取ini做配置文件

pip install configparser

示例

config目录下settings.ini [host] api_url = http://47.108.153.47:8089/qzcsbj/user/login

read_ini.py import configparser,os path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),"config","settings.ini") def read_ini(): config = configparser.ConfigParser() config.read(path,encoding='utf8') return config print(read_ini()['host']['api_url'])

import pytest import requests from utils.read_data import getdata from utils.read_ini import read_ini url = read_ini()['host']['api_url'] @pytest.mark.parametrize("username,password",getdata['paramspost']) def test_postmobile(username,password): print("测试post请求") headers = {"Content-Type": "application/json","Accept":"application/json"} r = requests.post(url=url, data={"username":username,"password":password},headers=headers) print(r.status_code) assert r.status_code == 200

读取yaml和ini代码封装

params: {key1: value1 , key2: value2 }

read.py import configparser, yaml, os ini_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "config", "settings.ini") data_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "data", "data.yaml") class FileRead: def __init__(self): self.data_path = data_path self.ini_path = ini_path def read_data(self): # f = open("../config/data.yaml",encoding="utf-8") with open(self.data_path, encoding="utf") as f: data = yaml.safe_load(f) return data def read_ini(self): config = configparser.ConfigParser() config.read(self.ini_path, encoding='utf8') return config getdata = FileRead().read_data() getini = FileRead().read_ini() if __name__ == '__main__': print(FileRead().read_ini()['host']['api_url']) print(getini['host']['api_url'])

test_read.py import requests from utils.read import getdata def test_getmobile(): print("测试get请求") param = getdata["params"] #直接调用方法 # print(param) r=requests.get('https://httpbin.org/get',params=param) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2'

接口请求部分代码封装

测试用例代码分层思想

测试用例中尽可能少写代码

新增一些文件和方法,作为方法传递使用

统一处理接口请求和返回,实现代码复用

示例 --步骤

core目录

rest_client.py文件封装请求方法、主路劲、接口日志参数打印

rest_client.py import json import requests from utils.log_util import logger from utils.read import basedata urlget = basedata.read_ini()['host']['url_get'] urlpost = basedata.read_ini()['host']['api_url'] class RestClient: def __init__(self): self.urlget = urlget self.urlpost = urlpost def get(self, url, **kwargs): return self.request(url, "GET", **kwargs) def post(self, url, **kwargs): return self.request(url, "POST", **kwargs) def put(self, url, **kwargs): return self.request(url, "PUT", **kwargs) def delete(self, url, **kwargs): return self.request(url, "DELETE", **kwargs) def request(self, url, method, **kwargs): self.request_log(url, method, **kwargs) if method == 'GET': return requests.get(self.urlget + url, **kwargs) if method == 'POST': return requests.post(self.urlpost + url, **kwargs) if method == 'PUT': return requests.put(self.urlpost + url, **kwargs) if method == 'DELETE': return requests.delete(self.urlpost + url, **kwargs) def request_log(self, url, method, **kwargs): data = dict(**kwargs).get("data") json_data = dict(**kwargs).get("json") params = dict(**kwargs).get("params") headers = dict(**kwargs).get("headers") logger.info("接口请求的地址:{}".format(self.urlget + url)) logger.info("接口请求的地址:{}".format(self.urlpost + url)) logger.info("接口请求方法是:{}".format(method)) if data is not None: logger.info("接口请求data参数是:\n{}".format(json.dumps(data, indent=2))) if json_data is not None: logger.info("接口请求json参数是:\n{}".format(json.dumps(json_data, indent=2))) if params is not None: logger.info("接口请求params参数是:\n{}".format(json.dumps(params, indent=2))) if headers is not None: logger.info("接口请求headers参数是:\n{}".format(json.dumps(headers, indent=2)))

api_util.py封装后缀路劲,后面模块可将后缀路径放在ymal里封装,继承自RestClient

api_util.py #存放url路径 from core.rest_client import RestClient #用户中心接口地址 class Api(RestClient): def __init__(self): super().__init__() def get_mobile_belong(self,**kwargs): return self.get('/get',**kwargs) def post_data(self,**kwargs): return self.post('/posts',**kwargs) api_util=Api() #商品中心接口地址

创建resultBase.py文件,用来#用来组装response

#用来组装response class ResultResponse: pass

api目录

api.py封装查询用到的参数

api.py import json from core.api_util import api_util #A方法 from theaders import params from utils.log_util import logger from utils.response_util import process_response def mobile_query(params): response = api_util.get_mobile_belong(params=params) result = process_response(response) return result def test_json(json_data): """ 这个方法测试json传参 :param json_data: :return: """ response = api_util.post_data(json=json_data) return response.json()

utils目录,公共模块,log_util.py等封装日志方法

log_util.py

log_util.py import logging import os import time rootpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) logpath = os.path.join(rootpath,"log") class Logger: def __init__(self): #定义日志位置和文件名 self.logname = os.path.join(logpath, "{}.log".format(time.strftime("%Y-%m-%d"))) # 定义一个日志容器 self.logger = logging.getLogger("log") # 设置日志打印的级别 self.logger.setLevel(logging.DEBUG) # 创建日志输入的格式 self.formater = logging.Formatter( '[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s') # 创建日志处理器,用来存放日志文件 self.filelogger = logging.FileHandler(self.logname, mode='a', encoding="UTF-8") # 创建日志处理器,在控制台打印 self.console = logging.StreamHandler() # 设置控制台打印日志界别 self.console.setLevel(logging.DEBUG) # 文件存放日志级别 self.filelogger.setLevel(logging.DEBUG) # 文件存放日志格式 self.filelogger.setFormatter(self.formater) # 控制台打印日志格式 self.console.setFormatter(self.formater) # 将日志输出渠道添加到日志收集器中 self.logger.addHandler(self.filelogger) self.logger.addHandler(self.console) logger = Logger().logger if __name__ == '__main__': logger.info("---测试开始---") logger.debug("---测试结束---")

创建response_util.py文件,封装200、201状态码返回

import json from core.resultBase import ResultResponse from utils.log_util import logger def process_response(response): if response.status_code == 200 or response.status_code == 201: ResultResponse.success = True ResultResponse.body = response.json() logger.info("接口返回内容:\n"+json.dumps(response.json(),ensure_ascii=False)) else: ResultResponse.success = False logger.info("接口状态码不是2开头,请检查") return ResultResponse

读取yaml和ini代码封装

params: {key1: value1 , key2: value2 }

read.py import configparser import yaml, os ini_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "config", "settings.ini") data_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "data", "data.yaml") class FileRead: def __init__(self): self.data_path = data_path self.ini_path = ini_path def read_data(self): # f = open("../config/data.yaml",encoding="utf-8") with open(self.data_path, encoding="utf") as f: data = yaml.safe_load(f) return data def read_ini(self): config = configparser.ConfigParser() config.read(self.ini_path, encoding='utf8') return config getdata = FileRead().read_data() if __name__ == '__main__': print(FileRead().read_ini()['host']['api_url'])

test_read.py import requests from utils.read import getdata def test_getmobile(): print("测试get请求") param = getdata["params"] #直接调用方法 # print(param) r=requests.get('https://httpbin.org/get',params=param) print(r.status_code) assert r.status_code == 200 res = r.json() assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2'

随机姓名、年龄、性别等封装方法,yaml_util.py

import random from faker import Faker #要生成中文的随机数据,可以在实例化时给locale参数传入‘zh_CN’这个值 #详细用法:https://zhuanlan.zhihu.com/p/422182497 fake = Faker(locale='zh_CN') def func_yaml(data): #判断是否字典 if isinstance(data,dict): for key,value in data.items(): if '${' and '}' in str(value): start = str(value).index('${') end = str(value).index('}') func_name = str(value)[start+2:end] #名字格式 北京-xxx data[key] = str(value)[0:start] + str(eval(func_name)) +str(value)[end+1:len(value)] # data[key] = eval(func_name) #名字随机 return data def age(): return random.randint(1,100) def sex(): sex = random.choice(['男','女']) return sex def random_name(): return fake.name() if __name__ == '__main__': data = {'name': '北京-${random_name()}-测试', 'age': '${age()}', 'sex': '${sex()}'} print(func_yaml(data))

person: #名字格式 北京-xx参考代码 - name: 北京-${random_name()}-测试 age: ${age()} sex: ${sex()} - name: 伤害-${random_name()}-测试 age: ${age()} sex: ${sex()}

封装连接mysql数据库,及查询结果、执行等方法

setting.ini

[host] api_url = http://admin.5istudy.online [mysql] MYSQL_HOST = 47.110.151.136 MYSQL_PORT = 3306 MYSQL_USER = vue_shop MYSQL_PASSWORD = 5istudy_shop MYSQL_DB = vue_shop

mysql_util.py

import pymysql from utils.log_util import logger from utils.read import getini data = getini['mysql'] DB_CONF = { "host": data['MYSQL_HOST'], "port": int(data['MYSQL_PORT']), "user": data['MYSQL_USER'], "password": data['MYSQL_PASSWORD'], "db": data['MYSQL_DB'] } class MysqlDb: def __init__(self): #mysql连接,autocommit自动提交 self.conn = pymysql.connect(**DB_CONF,autocommit=True) #游标 self.cur = self.conn.cursor(cursor=pymysql.cursors.DictCursor) #释放资源 def __del__(self): self.cur.close() self.conn.close() #查询sql并获取d多条结果 def select_dball(self,sql): self.cur.execute(sql) #获取数据 return self.cur.fetchall() # 查询sql并获取一条结果 def select_dbone(self,sql): self.cur.execute(sql) #获取数据 return self.cur.fetchone() #执行sql def execute_db(self,sql): try: logger.info(f"执行sql:{sql}") self.cur.execute(sql) self.conn.commit() except Exception as e: logger.info("执行sql出错{}".format(e)) #实例化 db = MysqlDb() if __name__ == '__main__': db = MysqlDb() res = db.select_dbone("select code from users_verifycode where mobile = 17665388050 order by id desc limit 1") print(res['code'])

testcase目录存放用例

创建conftest.py文件,测试前置后置步骤

from utils.log_util import logger from utils.mysql_util import db #获取验证码#从数据库里获取短信验证码 def get_code(mobile): sql = "select code from users_verifycode where mobile = '%s' order by id desc limit 1;" % mobile res = db.select_dbone(sql) logger.info(f'查询验证码sql:{res}') return res['code'] #删除用户 def delete_use(mobile): sql = "delete from users_verifycode where mobile = '%s';" % mobile res = db.execute_db(sql) logger.info(f'删除用户sql:{res}') #删除验证码 def delete_code(mobile): sql = "delete from users_userprofile where mobile = '%s';" % mobile res = db.execute_db(sql) logger.info(f'删除验证码sql:{res}')

最后用例调用api目录里的参数

test_optimize.py from api.api import mobile_query from utils.read import basedata def test_getmobile(): params = basedata.read_data()['params'] res = mobile_query(params) assert res['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res['origin'] == '163.125.202.248' assert res['args']['key1'] == 'value1' assert res['args']['key2'] == 'value2'

testpost.py from api.api import test_json from utils.read import basedata def test_post(): json_data = basedata.read_data()["json_data"] res = test_json(json_data) assert res == {}

json_data: { title: foo,body: bar,userId: 1 }

case_optimize.py from api.api import mobile_query from utils.read import basedata def test_getmobile(): params = basedata.read_data()['params'] res = mobile_query(params) assert res.success is True assert res.body['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res.body['origin'] == '163.125.202.248' assert res.body['args']['key1'] == 'value1' assert res.body['args']['key2'] == 'value2'

子主题 5

子主题 1

框架日志模块logging

优点 记录程序运行信息 方便定位问题

python日志模块logging DEBUG INFO WARNING ERROR

utils公共模块目录,创建log_util.py文件,主目录下创建log目录用来存放日志

import logging import os import time rootpath = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) logpath = os.path.join(rootpath,"log") class Logger: def __init__(self): #定义日志位置和文件名 self.logname = os.path.join(logpath, "{}.log".format(time.strftime("%Y-%m-%d"))) # 定义一个日志容器 self.logger = logging.getLogger("log") # 设置日志打印的级别 self.logger.setLevel(logging.DEBUG) # 创建日志输入的格式 self.formater = logging.Formatter( '[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s') # 创建日志处理器,用来存放日志文件 self.filelogger = logging.FileHandler(self.logname, mode='a', encoding="UTF-8") # 创建日志处理器,在控制台打印 self.console = logging.StreamHandler() # 设置控制台打印日志界别 self.console.setLevel(logging.DEBUG) # 文件存放日志级别 self.filelogger.setLevel(logging.DEBUG) # 文件存放日志格式 self.filelogger.setFormatter(self.formater) # 控制台打印日志格式 self.console.setFormatter(self.formater) # 将日志输出渠道添加到日志收集器中 self.logger.addHandler(self.filelogger) self.logger.addHandler(self.console) logger = Logger().logger if __name__ == '__main__': logger.info("---测试开始---") logger.debug("---测试结束---")

方法里加日志打印

from api.api import test_json from utils.log_util import logger from utils.read import basedata def test_post(): logger.info("开始执行testpost方法") json_data = basedata.read_data()["json_data"] res = test_json(json_data) assert res == {} logger.info("用例执行完毕")

pytest之allure测试报告

子主题 1

安装: pip install allure-pytest java安装

allure使用方法

import allure from api.api import mobile_query from utils.read import basedata @allure.epic("手机号项目") @allure.feature("手机号模块") @allure.story("get方法的story") class Testmobile: # @allure.story("get方法的story") @allure.title("测试get带参数方法") @allure.testcase("https://www.baidu.cm",name="接口地址testcase") @allure.issue("https://www.baidu.cm",name="缺陷地址issure") @allure.link("https://www.baidu.cm",name="链接地址link") @allure.description("当前链接是xxx") @allure.step("测试步骤") @allure.severity(severity_level="blocker") def test_getmobile(self): params = basedata.read_data()['params'] res = mobile_query(params) assert res.success is True assert res.body['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res.body['origin'] == '163.125.202.248' assert res.body['args']['key1'] == 'value1' assert res.body['args']['key2'] == 'value2' # @allure.story("get方法的story2") @allure.title("测试get带参数方法") @allure.testcase("http://www.baidu.cm", name="接口地址testcase") @allure.issue("http://www.baidu.cm", name="缺陷地址issure") @allure.description("当前链接是xxx") @allure.step("测试步骤") @allure.severity(severity_level="blocker") def test_getmobile2(self): params = basedata.read_data()['params'] res = mobile_query(params) assert res.success is True assert res.body['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res.body['origin'] == '163.125.202.248' assert res.body['args']['key1'] == 'value1' assert res.body['args']['key2'] == 'value2' # @allure.story("get方法的story3") @allure.title("测试get带参数方法") @allure.testcase("http://www.baidu.cm", name="接口地址testcase") @allure.issue("http://www.baidu.cm", name="缺陷地址issure") @allure.description("当前链接是xxx") @allure.step("测试步骤") @allure.severity(severity_level="blocker") def test_getmobile3(self): params = basedata.read_data()['params'] res = mobile_query(params) assert res.success is True assert res.body['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res.body['origin'] == '163.125.202.248' assert res.body['args']['key1'] == 'value11' assert res.body['args']['key2'] == 'value2'

终端下执行:allure serve 报告的绝对路径

子主题 1

allure使用

配置pytest.ini

[pytest] testpaths=./testcase markers= p0=高优先级 test=测试环境 pro=生产环境 #配置allure报告地址 addopts:-vs --alluredir ./report

根目录里生成report文件夹,要在Terminal里执行用例: pytest .\testcase\testcase_optimize\case_optimize.py

在非根目录下,就直接run,会在同级目录生成report文件夹

打开报告

Terminal里执行命令:allure serve ./report,自动打开html报告

allure generate report,根目录下生成文件夹allure-report

打开用 allure open .\allure-report\

allure generate report -o allure_report,文件夹名带下划线

allure动态自定义报告

params_dynamic: params: {key1: value1 , key2: value2 } story: get方法演示-动态 title: 测试get带参数方法-动态

import allure from api.api import mobile_query from utils.read import basedata @allure.epic("手机号项目") @allure.feature("手机号模块") @allure.story("get方法的story") class Testmobile: # @allure.story("get方法的story") @allure.title("测试get带参数方法") @allure.testcase("https://www.baidu.cm",name="接口地址testcase") @allure.issue("https://www.baidu.cm",name="缺陷地址issure") @allure.link("https://www.baidu.cm",name="链接地址link") @allure.description("当前链接是xxx") @allure.step("测试步骤") @allure.severity(severity_level="blocker") def test_getmobile(self): params = basedata.read_data()['params_dynamic']['params'] story = basedata.read_data()['params_dynamic']['story'] title = basedata.read_data()['params_dynamic']['title'] allure.dynamic.story(story) allure.dynamic.title(title) res = mobile_query(params) assert res.success is True assert res.body['url'] == 'https://httpbin.org/get?key1=value1&key2=value2' assert res.body['origin'] == '163.125.202.248' assert res.body['args']['key1'] == 'value1' assert res.body['args']['key2'] == 'value2'

allure添加环境信息

report目录下建文件,environment.properties,文件名固定

author=sasa version=5.0 addr=htts://www.baidu.com environment=pro

不能用中文,不支持

之后重新生成报告

接口项目实战环节

yaml格式

#用例多参数,复制一样的参数 login_new: - url: /login/ method: POST data: {username: 17665388050, password: 123456} validate: #正则提取符号,eq等于,ne不等于,在validate。py文件里封装里方法 - eq: [ $.success, true] - ne: [ $.body.token, null] - url: /login/ method: POST data: { username: 17665388, password: 123456 } validate: #正则提取符号,eq等于,ne不等于,在validate。py文件里封装里方法 - eq: [ $.success, true ] - nq: [ $.body.token, null ]

api目录,userapi_new.py

from core.restclient_new import apiutil_new from utils.responseutil_new import process_response def login_new(data): ''' #用户登录接口 :param username: :param password: :return: ''' response = apiutil_new.do_request(url=data['url'],method=data['method'],json=data['data']) return process_response(response)

core目录,封装request方法,restclient_new.py

import json import requests from utils.log_util import logger from utils.read import getini # from utils.read import basedata urlroot = getini['host']['api_url'] class RestClient: def __init__(self): self.urlroot = urlroot def do_request(self, url, method, **kwargs): return self.request(url, method, **kwargs) def request(self, url, method, **kwargs): self.request_log(url, method, **kwargs) if method == 'GET': return requests.get(self.urlroot + url, **kwargs) if method == 'POST': return requests.post(self.urlroot + url, **kwargs) if method == 'PUT': return requests.put(self.urlroot + url, **kwargs) if method == 'DELETE': return requests.delete(self.urlroot + url, **kwargs) def request_log(self, url, method, **kwargs): data = dict(**kwargs).get("data") json_data = dict(**kwargs).get("json") params = dict(**kwargs).get("params") headers = dict(**kwargs).get("headers") logger.info("接口请求的地址:{}".format(self.urlroot + url)) logger.info("接口请求方法是:{}".format(method)) if data is not None: logger.info("接口请求data参数是:\n{}".format(json.dumps(data, indent=2))) if json_data is not None: logger.info("接口请求json参数是:\n{}".format(json.dumps(json_data, indent=2))) if params is not None: logger.info("接口请求params参数是:\n{}".format(json.dumps(params, indent=2))) if headers is not None: logger.info("接口请求headers参数是:\n{}".format(json.dumps(headers, indent=2))) apiutil_new = RestClient()

用例目录testuser_new.py

import allure import pytest from api.user_api import send_code, register, login from api.userapi_new import login_new from testcases.usercenter.conftest import get_code, delete_use, delete_code from utils.read import getdata from utils.validate import validate_response @allure.epic("美客项目") @allure.feature("用户模块") class TestUser: @pytest.mark.parametrize("data",getdata['login_new']) @allure.story("用户登录") @allure.title("登录测试用例") def test_login(self,data): result = login_new(data) validate_response(result,data['validate'])

util目录,

封装jasonpath提取关字段方法,validate.py

""" Built-in validate comparators. """ import re import jsonpath def equals(check_value, expect_value): assert check_value == expect_value, f'{check_value} == {expect_value}' def less_than(check_value, expect_value): assert check_value < expect_value, f'{check_value} < {expect_value})' def less_than_or_equals(check_value, expect_value): assert check_value <= expect_value, f'{check_value} <= {expect_value})' def greater_than(check_value, expect_value): assert check_value > expect_value, f'{check_value} > {expect_value})' def greater_than_or_equals(check_value, expect_value): assert check_value >= expect_value, f'{check_value} >= {expect_value})' def not_equals(check_value, expect_value): assert check_value != expect_value, f'{check_value} != {expect_value})' def string_equals(check_value, expect_value): assert str(check_value) == str(expect_value), f'{check_value} == {expect_value})' def length_equals(check_value, expect_value): expect_len = _cast_to_int(expect_value) assert len(check_value) == expect_len, f'{len(check_value)} == {expect_value})' def length_greater_than(check_value, expect_value): expect_len = _cast_to_int(expect_value) assert len(check_value) > expect_len, f'{len(check_value)} > {expect_value})' def length_greater_than_or_equals(check_value, expect_value): expect_len = _cast_to_int(expect_value) assert len(check_value) >= expect_len, f'{len(check_value)} >= {expect_value})' def length_less_than(check_value, expect_value): expect_len = _cast_to_int(expect_value) assert len(check_value) < expect_len, f'{len(check_value)} < {expect_value})' def length_less_than_or_equals(check_value, expect_value): expect_len = _cast_to_int(expect_value) assert len(check_value) <= expect_len, f'{len(check_value)} <= {expect_value})' def contains(check_value, expect_value): assert isinstance(check_value, (list, tuple, dict, str)) assert expect_value in check_value, f'{len(expect_value)} in {check_value})' def contained_by(check_value, expect_value): assert isinstance(expect_value, (list, tuple, dict, str)) assert check_value in expect_value, f'{len(check_value)} in {expect_value})' def regex_match(check_value, expect_value): assert isinstance(expect_value, str) assert isinstance(check_value, str) assert re.match(expect_value, check_value) def startswith(check_value, expect_value): assert str(check_value).startswith(str(expect_value)), f'{str(check_value)} startswith {str(expect_value)})' def endswith(check_value, expect_value): assert str(check_value).endswith(str(expect_value)), f'{str(check_value)} endswith {str(expect_value)})' def _cast_to_int(expect_value): try: return int(expect_value) except Exception: raise AssertionError(f"%{expect_value} can't cast to int") def extract_by_jsonpath(extract_value: dict, extract_expression: str): # noqa """ json path 取值 :param extract_value: response.json() :param extract_expression: eg: '$.code' :return: None或 提取的第一个值 或全部 """ if not isinstance(extract_expression, str): return extract_expression extract_value = jsonpath.jsonpath(extract_value, extract_expression) if not extract_value: return elif len(extract_value) == 1: return extract_value[0] else: return extract_value def extract_by_object(response, extract_expression): """ 从response 对象属性取值 [status_code, url, ok, headers, cookies, text, json, encoding] :param response: Response Obj :param extract_expression: 取值表达式 :return: 返回取值后的结果 """ if extract_expression.startswith('$.'): response_parse_dict = response.json() return extract_by_jsonpath(response_parse_dict, extract_expression) else: # 其它非取值表达式,直接返回 return extract_expression def validate_response(response, validate_check) : """校验结果""" for check in validate_check: for check_type, check_value in check.items(): actual_value = extract_by_jsonpath(response, check_value[0]) # 实际结果 expect_value = check_value[1] # 期望结果 if check_type in ["eq", "equals", "equal"]: equals(actual_value, expect_value) elif check_type in ["lt", "less_than"]: less_than(actual_value, expect_value) elif check_type in ["le", "less_or_equals"]: less_than_or_equals(actual_value, expect_value) elif check_type in ["gt", "greater_than"]: greater_than(actual_value, expect_value) elif check_type in ["ne", "not_equal"]: not_equals(actual_value, expect_value) elif check_type in ["str_eq", "string_equals"]: string_equals(actual_value, expect_value) elif check_type in ["len_eq", "length_equal"]: length_equals(actual_value, expect_value) elif check_type in ["len_gt", "length_greater_than"]: length_greater_than(actual_value, expect_value) elif check_type in ["len_ge", "length_greater_or_equals"]: length_greater_than_or_equals(actual_value, expect_value) elif check_type in ["len_lt", "length_less_than"]: length_less_than(actual_value, expect_value) elif check_type in ["len_le", "length_less_or_equals"]: length_less_than_or_equals(actual_value, expect_value) elif check_type in ["contains"]: contains(actual_value, expect_value) else: print(f'{check_type} not valid check type')

responseutil_new.py

import json from core.resultBase import ResultResponse from utils.log_util import logger def process_response(response): ResultResponse = {} if response.status_code == 200 or response.status_code == 201: ResultResponse['success'] = True ResultResponse['body'] = response.json() else: ResultResponse['success'] = False logger.info("接口状态码不是2开头,请检查") logger.info("检查接口返回内容:\n" + json.dumps(response.json(), ensure_ascii=False)) return ResultResponse

其余公共封装方法相同

项目结合Jenkins

代码准备

1.生成当前环境第三方包文件 pip freeze > requirements.txt

2.将代码上传到git仓库

我用gitee

失败用例重跑

pip3 install pytest-rerunfailures

[pytest] testpaths=./testcases markers= p0=高优先级 test=测试环境 pro=生产环境 #配置allure报告地址、失败重跑 addopts:-vs -reruns 1 --reruns-delay 3 --alluredir ./report

进阶提升

进程和线程的简单理解

子主题 1

进程,是指在系统中正在运行的一个应用程序,程序一旦运行就是进程。比如打开一个QQ音乐就是启动了一个QQ音乐进程,打开一个 Word 就启动了一个 Word 进程。进程是操作系统分配资源的最小单位。如下图,每一个进程都拥有独立的内存单元。

在一个进程内部,可以同时运行多个“子任务”,也就是多个线程。如上所示,在一个进程中,而多个线程共享内存。线程是进程之内程序执行的最小单位。

进程和线程之间的关系有以下几个特点:

线程必须存在于进程中,一个进程至少有一个线程 ---- 工人需要在流水线上处理工作,完成检验至少需要一个工人。

进程中的任意一线程执行出错,都会导致整个进程的崩溃 ---- 流水线中任何一处电路短路,整个流水线就会停电。

线程之间共享进程中的数据 ---- 工人共享流水线上的工具、资源等。

当一个进程关闭之后,操作系统会回收进程所占用的内存 ---- 一次检验完成,工厂可以关闭这条流水线,将流水线的资源收回分配给其他流水线。

进程之间的内容相互隔离 ---- 两条流水线的资源和任务是相互独立的

并发和并行

并发和并行是两个概念,并行指在同一时刻有多条指令在多个处理器上同时执行;并发是指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果

分布式运行测试用例

pip install pytest-xdist

命令行参数

pytest -n num(num是进程数)

pytest -n 2 -vs testcase/test1.py

pytest -n auto(auto是默认CPU核数)

pytest -n auto testcase/test1.py

pytest.ini配置

[pytest] testpaths=./testcase markers= p0=高优先级 test=测试环境 pro=生产环境 #配置allure报告地址、失败重跑、加分布式运行用例 addopts=-q -n auto --reruns 1 --reruns-delay 3 --alluredir ./report

用例很多的时候,且没有相互独立,会打乱运行有影响

相关思维导图模板

I/O设备基本概念思维导图

树图思维导图提供 I/O设备基本概念 在线思维导图免费制作,点击“编辑”按钮,可对 I/O设备基本概念  进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:d13c3cd0b4c94c9a8b49223229aee4c5

水电站自动化系统设计逻辑结构图思维导图

树图思维导图提供 水电站自动化系统设计逻辑结构图 在线思维导图免费制作,点击“编辑”按钮,可对 水电站自动化系统设计逻辑结构图  进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:ac4f1039b10ee5f4241922f5bc9e61ad