一篇文章带你了解接口自动化
在之前的文章里我们已经学习了Python,unitTest,pyTest,Allure等自动化框架
在这篇文章中我们将借助这些自动化框架和实际案例来讲解如何针对接口进行自动化处理
我们这篇文章将从以下角度展开讲解:
- 常用类Requests
- 接口自动化基础
- 接口自动化实例
常用类Requests
首先我们需要了解一个在接口自动化中经常使用的工具类
Requests库基本信息
我们在使用接口自动化时一定会调用到不同类型的接口,而Requests库可以帮助我们快速调用接口:
- Requests 是⽤Python语⾔编写,基于urllib,采⽤Apache2 Licensed开源协议的 HTTP 库
- 它⽐ urllib 更加⽅便,可以节约我们⼤量的⼯作,完全满⾜HTTP测试需求
我们需要了解到Requests库是第三方库,所以我们需要下载后使用:
# 我们直接在Terminal里采用pip指令下载Requests库即可
pip install requests
# 我们可以采用show命令查看是否安装成功
pip show requests
我们通常会使用到的关于Requests的方法主要包含以下五种:
# 首先我们注意我们需要导入Requests库才可以使用
# Get
Requests.get()
# Post
Requests.post()
# Put
Requests.put()
# Delete
Requests.delete()
Requests库基本使用
下面我们针对Requests库的使用来做基本的讲解:
- Requests库可以调用不同的类型方法来针对不同请求类型发送接口请求
- Requests库在发送接口请求后,后端会返回一个Response,我们可以接受并查看信息
下面我们首先针对Requests库的发送请求进行讲解:
# 首先我们需要导包使用
import requests
# 在正式开始讲解之前我们需要了解Requests所使用的方法中的一些参数
# url:请求路径
# params:get特有的参数赋值方法,意思是在url的?之后的参数赋值
# json:通用的参数赋值方法,可以用于复杂的格式也可以用于字典格式
# data:请求体为form表单参数,通常用于字典类型的比较简单的格式
# header:请求头参数,通常是Authorization验证登录信息或者参数传输格式信息等
# files:文件格式,使用字典传输,且字典中含有'file'键名称
# 首先我们先来看一下常用的Get请求
# Requests.get(url,params/json)
r = requests.get('http://httpbin.org/get')
# 如果我们想要添加参数进行传输,我们可以直接在url后面添加?+参数信息等
r = requests.get('http://httpbin.org/get?name=germey&age=20')
# 我们也可以采用params参数或者json参数进行传输
data = {
'name':'germey',
'age':22
}
r = requests.get('http://httpbin.org/get',params=data)
r = requests.get('http://httpbin.org/get',json=data)
# 下面我们了解一下Post请求,Put和Delete操作和Post操作完全相同,这里不做介绍
# Requests.get(url,json)
r = requests.post('http://httpbin.org/post')
# 和Get方法不同的是,Post不能采用params也不能采用url拼接,只能采用json进行传输
data = {
"name":"germey",
"age":"22"
}
r = requests.post('http://httpbin.org/post',data=data)
# 除了基本的url和json之外,我们在请求过程中还需要很多参数类型
# 例如header头,我们的大部分操作都需要做登陆验证,而登录一般会含有一个Authorization属性,后面value值为Token值,这里我们讲述
# 但是除了Authorization属性外,我们还可能会传输参数类型Content-Type等其他信息
# 我们通常将header写为字典格式进行传输
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0',
'my-test':'Hello'
}
r = requests.get('http://httpbin.org/get',headers=headers)
# 我们在传输过程中也经常传输文件类型,我们通常需要读取文件并将其存储为Python中的文件才可以使用
# 我们需要注意以二进制方式读取文件
# 我们在post方法中需要传输字典,字典key值为'file'是默认名词
files = {'file':open('favicon.ico','rb')}
# 我们调用post方法并使用files参数进行文件上传
r = requests.post('http://httpbin.org/post',files=files)
# 我们还需要注意一些特殊的网站,如果该网站的证书没有被CA机构信任,程序将会出错,提示SSL证书验证错误
# 那么我们在访问时,只需要将verify参数设置为False即可
r = requests.get('https://www.12306.cn',verify=False)
# 针对特别防范的网站,当我们大量进行数据爬取或者重复使用时,可能会触发该网站的自卫系统将我们当前IP进行屏蔽
# 我们可以通过使用代理来防止这种情况(IP可以在网上购买)
proxies = {
'http': 'http://161.35.4.201:80',
'https': 'https://161.35.4.201:80'
}
r = requests.get('http://httpbin.org/get', proxies=proxies)
在讲述完Requests库的发送请求后,我们可以针对Requests请求后的Response响应进行讲述:
# 我们在发送请求后服务器会对我们的请求进行响应,这就是所获得的Response,所有请求都会获取到该响应
# 我们下面来介绍一些我们常用的response类型的方法
# 获取状态码
response.status_code
# JSON形式的响应内容
response.json()
# 文本形式的响应内容
response.text
# 请求url
response.url
# 查看响应头部字符编码
response.encoding
# 头信息
response.headers
# cookie信息
response.cookies
# 我们通常获得这些信息之后可以进行查看,我们可以通过print直接打印查看
print(response.status_code)
# 但更多情况下,我们通过这些信息来做断言判断是否符合我们预期
assert 200 == response.status_code
assert "成功" in response.text
# 当然我们在必要情况下我们还需要获取到对应的值去使用
# 例如我们通常需要一个Token来表示我们处于登陆状态,那么我们就需要在执行登陆操作时将token值获取并保存下来
self.token = r.json().get("token")
# 如果我们需要cookie,我们也可以直接获取cookies并将其保存在一个列表中
response = requests.get("百度一下,你就知道")
for key, value in response.cookies.items():
print(key + '=' + value)
接口自动化基础
下面我们来讲解接口自动化的一些基础信息
接口测试
既然我们要学习接口自动化,那么我们肯定首先需要了解接口测试:
- 使用工具或代码代替人对接口进行测试的技术
- 其根本目的是为了防止开发修改代码时引入新的问题
那么我们的接口测试主要针对哪些方面:
- 我们的项目分为前端和后端
- 前端仅仅是调用接口的一种方式,其根本还是后端的处理
- 所以我们的接口测试就是针对后端所书写的方法进行测试,测试其是否符合我们的需求
我们测试人员针对接口测试的主要时间段是在哪里:
-
开发进行系统测试提测前,可以先进行接口自动化脚本的编写
-
开发进行系统测试提测后,优先进行系统测试用例的执行,再进行接口自动化脚本的编写
-
所以我们接口测试的主要时间段针对于前后端联调的时间进行接口测试
接口测试基本操作
下面我们来讲解接口测试的基本操作顺序:
# 1.选取自动化测试用例
# 我们一般会在前后端开发时进行测试用例的书写,我们仅需要选取我们所测试模块的测试用例即可
# 2.搭建自动化测试环境
# 这个主要根据公司本身,一般由主管进行搭建,员工拉取书写更新上传即可
# 3.搭建自动化测试框架
# 这个主要也是公司进行选取,不过一般都是python+pytest+allure基本框架加上一些辅助框架
# 4.代码实现自动化
# 这个就是我们接口测试自动化主要书写的部分
# 5.输出测试报告
# 我们一般会采用allure进行美观测试报告生成
# 6.实现持续集成
# 我们接口测试的目的其实是为了在本次开发时使当前开发不影响之前功能的使用,所以我们的接口自动化需要保证效率重复使用等多方面
我们在进行接口测试时可以通过三种方式获得接口:
-
在测试页面使用F12查看调用接口信息
-
采用Charles等抓包工具查看接口信息
-
向项目经理/后端人员索要接口文档信息
最后我们稍微提一下如果我们使用手动接口测试,我们一般会使用Postman工具进行测试,我们这里不做详解
接口自动化实例
接下来我们通过一个接口自动化实例来讲解接口自动化
接口自动化框架信息
我们在接触接口自动化代码之前,我们首先需要了解这个框架主要需要什么:
# 首先假设我们创建了一个Pro_viv文件来当作主目录
# 首先我们需要一个api文件夹
# 我们会将我们所需要调用的Api接口进行封装(Requests库相关的信息)
# 然后我们需要一个script文件夹
# 我们需要将我们所使用的case进行存储,其实就是我们pytest中所使用的测试用例,我们的数据调用也是在这里进行调用
# 然后我们需要一个data文件夹
# 我们会将我们所需要使用的jpg或json等信息文件存储进该文件夹
# 最后我们还需要一个report文件夹
# 我们会将所生成的allure报告文档存放在该文件夹下
# 除了文件夹外,我们还需要两个文件来进行一些信息整理
# 首先我们需要一个config.py
# 我们会使用该文件来存放一些通用的路径或其他信息
# 我们还需要一个pytest.ini
# 这是pytest的配置文件,我们可以在里面修改pytest相关的信息
接口自动化实例展示
那么下面我们将通过多个需求案例来逐步了解接口自动化的使用
登录接口
我们首先给出需求信息:
# 地址:http://kdtx-test.itheima.net/api/login
# 方法:Post
# 请求数据:
# 请求头:Content-Type: application/json
# 请求体:{"username":”admin", "password": " admin123","code":"2", "uuid":"验证码接口返回数据"}
我们首先需要书写接口,所以我们需要在api文件下创建login.py进行接口书写:
# api 文件夹 - login.py
# 接口封装时,重点是依据接口文档封装接口信息
# 需要使用的测试数据是从测试用例传递的、接口方法被调用时需要返回对应的响应结果
# 所以我们在书写完接口方法后,我们还需要到script文件夹下创建test文件来创建case来执行pytest
# 导包
import requests
# 创建接口类
class LoginAPI:
# 初始化
def __init__(self):
# 指定url基本信息
self.url_verify = "http://kdtx-test.itheima.net/api/captchaImage"
self.url_login = "http://kdtx-test.itheima.net/api/login"
# 验证码
def get_verify_code(self):
return requests.get(url=self.url_verify)
# 登录
# 我们在登录时需要请求信息,我们这里添加一个参数,当我们使用case时传参并调用
def login(self, test_data):
return requests.post(url=self.url_login, json=test_data)
当我们书写完接口信息之后,我们就可以到script文件夹下创建test文件进行测试用例书写:
# script 文件夹 - verify.py
Class TestLogin:
uuidValue = None
# 验证码测试
def test01_get_verify_code1(self):
# 获取验证码
res_v = self.login_api.get_verify_code()
# 登录验证
def test02_login1(self):
# 这里我们直接将信息写为列表进行赋值处置
login_data = {
"username": "admin",
"password": "admin123",
"code": "2",
"uuid": "f3334fd726bf4155b787198c701786b6"
}
res_l = self.login_api.login(test_data=login_data)
# 验证码测试2
def test03_get_verify_code1(self):
# 获取验证码
res_v = self.login_api.get_verify服务器托管网_code()
# 一些信息的打印
print(res_v.status_code)
print(res_v.json())
# 打印uuid数据,这是我们在登录时所需要使用的数据
print(res_v.json().get("uuid"))
# 我们可以采用一个类变量来将uuid存储起来进行使用
self.uuidValue = res_v.json().get("uuid")
# 登陆验证2
def test04_login(self):
# 由于我们的uuid是通过get_verify_code获得的验证码,我们可以通过pytest连续执行两条case并获取uuid进行使用
login_data = {
"username": "admin",
"password": "admin123",
"code": "2",
"uuid": self.uuidValue
}
res_l = self.login_api.login(test_data=login_data)
课程接口
我们首先给出需求信息:
# 课程模块接口封装:核心在于依据接口文档实现接口信息封装、重点关注接口如何被调用
# 接口信息:
# URL:http://kdtx-test.itheima.net/api/clues/course
# 方法:Post
# 请求数据:
# 请求头:{ "Content-Type ": "application/json ", "Authorization": "xxx " }
# 请求体:{ "name": "测试开发提升课01", "subject": "6","price": 899,"applicablePerson": "2", "info": "测试开发提升课01"}
我们首先需要书写接口,所以我们需要在api文件下创建course.py进行接口书写:
# api 文件夹 - course.py
# 我们将在该接口类中实现多个接口,我们将书写除了上述需求信息外的其他接口
# 导包
import requests
# 创建接口类
class CourseAPI:
# 初始化
def __init__(self):
self.url_add_course = "http://kdtx-test.itheima.net/api/clues/course"
self.url_select_course = "http://kdtx-test.itheima.net/api/clues/course/list"
# 我们需要注意:
# 我们合同的操作均需要在登录前提下进行操作
# 而我们判断是否登录的条件就是在headers请求头中是否存在符合要求的token
# 所以我们下述的接口方法中,除了我们所需要的数据信息外,我们还需要添加headers,并提供Authorization信息
# 课程添加
def add_course(self, test_data, token):
return requests.post(url=self.url_add_course, json=test_data, headers={"Authorization": token})
# 查询课程列表
def select_course(self, test_data, token):
# 这里我们可以采用url拼接,也可以采用params来传递参数,
# return requests.get(url=self.url_select_course,params=params,headers={"Authorization": token})
return requests.get(url=self.url_select_course + f"/{test_data}", headers={"Authorization": token})
# 修改课程
def update_course(self, test_data, token):
return requests.put(url=self.url_add_course, json=test_data, headers={"Authorization": token})
# 删除课程
def delete_course(self, course_id, token):
return requests.delete(url=self.url_add_course + f"/{course_id}", headers={"Authorization": token})
当我们书写完接口信息之后,我们就可以到script文件夹下创建test文件进行测试用例书写:
# 导包
import config
from api.login import LoginAPI
from api.course import CourseAPI
from api.contract import ContractAPI
# 创建测试类
class TestContractBusiness:
# 初始化(由于我们需要使用登录时的Token,所以我们进行存储)
token = None
# 前置处理
def setup(self):
# 实例化接口对象
self.login_api = LoginAPI()
self.course_api = CourseAPI()
self.contract_api = ContractAPI()
# 后置处理
def teardown(self):
pass
# 1、登录成功
def test01_login_success(self):
# 获取验证码
res_v = self.login_api.get_verify_code()
# 登录
login_data = {
"username": "admin",
"password": "admin123",
"code": "2",
"uuid": res_v.json().get("uuid")
}
res_l = self.login_api.login(test_data=login_data)
# 提取登录成功之后的token数据并保存在类的属性中
TestContractBusiness.token = res_l.json().get("token")
# 2、课程新增成功
def test02_add_course(self):
add_data = {
"name": "测试开发提升课01",
"subject": "6",
"price": 899,
"applicablePerson": "2",
"info": "测试开发提升课01"
}
response = self.course_api.add_course(test_data=add_data, token=TestContractBusiness.token)
print(response.json())
合同接口
我们首先给出需求信息:
# 请求头:{ "Content-Type ": " multipart/form-data ", "Authorization": "xxx " }
# 请求体:{" file " : 合同文件"}
# 接口信息:
# 新增合同:
# 地址:http://kdtx-test.itheima.net/api/contract
# 方法:Post
# 请求数据:
# 请求头:{ "Content-Type ": "application/json ", "Authorization": "xxx " }
# 请求体:{ "name": "测试888", "phone": "13612345678", "contractNo": "HT10012003", "subject": "6", "courseId": " 99", "channel": "0", "activityId": 77, "fileName": "xxx"}
我们首先需要书写接口,所以我们需要在api文件下创建contract.py进行接口书写:
# 导包
import requests
# 创建接口类
class ContractAPI:
# 初始化
def __init__(self):
self.url_upload = "http://kdtx-test.itheima.net/api/common/upload"
self.add_contrat = "http://kdtx-test.itheima.net/api/contract"
# 合同上传接口
def upload_contract(self, test_data, token):
# 我们的合同需要一份文件信息上传,所以我们参数这里使用到了file
# 其实我们的headers头部的Content-Type信息也被修改,但由于该信息是根据传输内容更新的,所以我们不需要设置
return requests.post(url=self.url_upload, files={"file": test_data}, headers={"Authorization": token})
# 合同新增
def add_contract(self, test_data, token):
# 这里就是很正常的新增
return requests.post(url=self.add_contrat, json=test_data, headers={"Authorization": token})
当我们书写完接口信息之后,我们就可以到script文件夹下创建test文件进行测试用例书写:
# 导包
import config
from api.login import LoginAPI
from api.course import CourseAPI
from api.contract import ContractAPI
# 创建测试类
class TestContractBusiness:
# 初始化
token = None
# 前置处理
def setup(self):
# 实例化接口对象
self.login_api = LoginAPI()
self.course_api = CourseAPI()
self.contract_api = ContractAPI()
# 后置处理
def teardown(self):
pass
# 1、登录成功
def test01_login_success(self):
# 获取验证码
res_v = self.login_api.get_verify_code()
# 登录
login_data = {
"username": "admin",
"password": "admin123",
"code": "2",
"uuid": res_v.json().get("uuid")
}
res_l = self.login_api.login(test_data=login_data)
# 提取登录成功之后的token数据并保存在类的属性中
TestContractBusiness.token = res_l.json().get("token")
# 2、上传合同成功
def test02_upload_contract(self):
# 这里我们需要将对应的文件采用二进制读取并且转化为file类型将其作为参数传参
f = open(config.BASE_PATH + "/data/test.pdf", "rb")
# 这里的test_data在Api中作为files的file被传递
response = self.contract_api.upload_contract(test_data=f, token=TestContractBusiness.token)
print(response.json())
# 3、合同新增成功
def test03_add_contract(self):
# contractNo: 数据唯一
add_data = { "name": "测试888", "phone": "13612345678", "contractNo": "HT20230007", "subject": "6", "courseId": " 99", "channel": "0", "activityId": 77, "fileName": "xxx"}
response = self.contract_api.add_contract(test_data=add_data, token=TestContractBusiness.token)
print(response.json())
单接口测试
下面我们来讲解针对于单个接口进行多测试用例测试的方法:
# 我们这里以Login的API为基准
# 我们需要采用不同的测试用例来进行测试并判断是否满足需求
我们首先给出测试用例图表:
然后我们采用代码进行接口自动化测试用例书写:
# 导包
from api.login import LoginAPI
# 创建测试类
class TestLoginAPI:
# 初始化
uuid = None
# 前置处理
# 我们将uuid验证码作为所有case方法执行前的前置操作进行执行并存储使用
def setup(self):
# 实例化接口类
self.login_api = LoginAPI()
# 获取验证码
response = self.login_api.get_verify_code()
# 提取验证码接口返回的uuid参数值
TestLoginAPI.uuid = response.json().get("uuid")
# 后置处理
def teardown(self):
pass
# 下面我们通过多个Case的书写来模拟多场景情况
# 我们下面Case的基本逻辑没有发生修改,仅仅是针对参数进行了处理
# 登录成功
def test01_success(self):
login_data = {
"username": "manager",
"password": "123456",
"code": "2",
"uuid": TestLoginAPI.uuid
}
response = self.login_api.login(test_data=login_data)
# 我们使用断言来判断是否符合需求,若符合需求pytest会通过案例,若不符合会进行报错
assert 200 == response.status_code # 断言响应状态码为200
assert '成功' in response.text # 断言响应数据包含'成功'
assert 200 == response.json().get("code") # 断言响应json数据中code值
# 登录失败(用户名为空)
def test02_without_username(self):
login_data = {
"username": "",
"password": "123456",
"code": "2",
"uuid": TestLoginAPI.uuid
}
response = self.login_api.login(test_data=login_data)
# 我们使用断言来判断是否符合需求,若符合需求pytest会通过案例,若不符合会进行报错
assert 200 == response.status_code # 断言响应状态码为200
assert '成功' in response.text # 断言响应数据包含'成功'
assert 200 == response.json().get("code") # 断言响应json数据中code值
# 登录失败(未注册用户)
def test03_username_not_exist(self):
login_data = {
"username": "jack666",
"password": "123456",
"code": "2",
"uuid": TestLoginAPI.uuid
}
response = self.login_api.login(test_data=login_data)
# 我们使用断言来判断是否符合需求,若符合需求pytest会通过案例,若不符合会进行报错
assert 200 == response.status_code # 断言响应状态码为200
assert '成功' in response.text # 断言响应数据包含'成功'
assert 200 == response.json().get("code") # 断言响应json数据中code值
参数化测试
我们在前面通过书写多个Case来进行同一接口的自动化测试,但这种方法会导致代码冗余
所以我们通常会通过pytest的参数化设置来统一数据将其放入Case用例中按顺序依次执行来减少代码冗余:
# 我们可以将数据单独存放在文件顶层
# 我们所传入的数据需要采用列表作为外层,采用字典作为数据参数进行存储
# 导包
from api.login import LoginAPI
import pytest
import json
# 测试数据
test_data = [
("manager", "123456", 200, '成功', 200),
("", "123456", 200, '错误', 500),
("jack666", "123456", 200, '错误', 500),
]
class TestLoginAPI:
# 初始化
uuid = None
# 前置处理
def setup(self):
# 实例化接口类
self.login_api = LoginAPI()
# 获取验证码
response = self.login_api.get_verify_code()
# 提取验证码接口返回的uuid参数值
TestLoginAPI.uuid = response.json().get("uuid")
# 后置处理
def teardown(self):
pass
# 多Case登录验证
# 这里我们使用了我们上面所定义的test_data作为数据源来进行参数化处理
# 我们可以将我们所需要更替的数值全部采用参数化处理,使处理数据变得多样化
# 这里所使用的parametrize是pytest的内容,我们在上篇文章已经讲过,如果不了解可以去查看
@pytest.mark.parametrize("username, password, status, message, code",test_data)
def test01_success(self, username, password, status, message, code):
# 这里的username,password使用参数化
login_data = {
"username": username,
"password": password,
"code": "2",
"uuid": TestLoginAPI.uuid
}
response = self.login_api.login(test_data=login_data)
# 这里的断言判断均使用参数化,因为不同条件下所产生的response数据是不同的
assert status == response.status_code
assert message in response.text
assert code == response.json().get("code")
接口自动化配置解释
最后我们需要解释我们在最开始所提及到的两个接口配置信息
全局信息配置文件
我们在最开始提及到了在我们的文件目录下创建了一个Config.py文件
下面我们来解释一下该文件的主要作用:
# 我们通常在该文件下定义我们全局都需要使用的数据信息,来减少我们代码的冗余
# 例如我们在之前的接口或测试用例中都需要使用到的url,我们可以在这里定义其项目url前缀,然后我们只需要书写对应的接口路径即可
# 又或者说由于我们之前的文件上传没有固定的文件目录,我们需要从当前文件下去推断data文件夹在哪个位置
# 但是如果我们项目发生改动,或者我们借鉴该代码去书写其他部分的文件,我们就需要去重新推断data文件夹在服务器托管网哪个位置
# 所以我们可以通过config.py直接获得绝对路径下的data目录的位置,然后我们只需要进行拼接就可以获得到我们所需要的数据信息
所以我们的config.py文件可以这样去定义:
# config.py文件
# 导包
import os
# 设置项目环境域名
BASE_URL = "http://kdtx-test.itheima.net"
# 获取项目根路径
BASE_PATH = os.path.dirname(__file__)
当然我们对应的接口代码和测试用例代码也需要进行修改:
# 首先针对API文件,我们需要去修改我们的url路径信息
def __init__(self):
# 指定url基本信息
# self.url_verify = "http://kdtx-test.itheima.net/api/captchaImage"
self.url_verify = config.BASE_URL + "/api/captchaImage"
# self.url_login = "http://kdtx-test.itheima.net/api/login"
self.url_login = config.BASE_URL + "/api/login"
# 针对文件上传时的路径我们也可以去修改
def test03_upload_contract(self):
# f = open("../data/test.pdf", "rb")
f = open(config.BASE_PATH + "/data/test.pdf", "rb")
response = self.contract_api.upload_contract(test_data=f, token=TestContractBusiness.token)
print(response.json())
框架设置配置文件
我们之前还提到了一个pytest.ini文件,该文件其实就是针对pytest执行用例的初始化进行设置:
# 该文件其实属于pytest的学习范畴,我们在这里简单介绍一下
[pytest]
# 表示我们在Terminal执行pytest时的默认执行配置
# 这里我们采用-s表示详情信息展示,采用--alluredir表示采用allure生成测试报告并生成在report文件夹下
addopts=-s --alluredir report
# 这里表示我们所需要执行的测试用例的文件范围
testpaths=./script
# 这里规定了配置测试搜索的模块文件名称
python_files=test*
# 这里规定了我们所执行的测试类的类名规范
python_classes=Test*
# 这里规定了我们所执行的测试方法的方法名规范
python_functions=test*
结束语
这篇文章中详细介绍了以pytest为框架的接口自动化实现的基本信息,希望能为你带来帮助
下面给出我学习和书写该篇文章的一些参考文章,大家也可以去查阅:
- 黑马课程:001_认识接口测试_哔哩哔哩_bilibili
- 知乎文章:python中requests库使用方法详解 – 知乎 (zhihu.com)
- CSDN:requests库的使用(一篇就够了)_上善若水。。的博客-CSDN博客
- 稀土掘金:软件测试/测试开发丨学习笔记之接口自动化测试 – 掘金 (juejin.cn)
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
一个非传统且富有同情心的成为早起鸟的指南 我曾以为自己注定要永远做夜猫子。 我对早起的好处或保持相同睡眠习惯的文章并不陌生–我们都可能在生活中的某个时刻读过这些。我现在是大学的最后一个学期,所以过去几年我的生活一直是一片混乱。有些日子我上课,有些日子我工作,…