Python接口自动化测试
接口测试
接口测试是一种测试方法,主要用于验证不同软件系统组件或服务之间的交互是否正确。接口测试通常是在开发阶段或集成阶段进行的,旨在确保各个系统或模块通过API(应用程序接口)以预期的方式交换数据和信息。
接口自动化测试
接口自动化测试是通过编程实现接口的自动化验证,旨在确保各个模块或服务之间的数据交换符合预期。它通常适用于系统的后台接口,例如API或微服务间的接口,用来验证输入输出数据、逻辑流程、数据格式和错误处理等。与手动测试相比,接口自动化测试能提高测试效率、减少人为错误,并且便于在持续集成中实现自动化。
什么时候进行接口自动化测试
开发提测前,优先开展接口自动化测试
在开发提测之前优先进行接口自动化测试,主要是为了确保接口的基本功能、稳定性和可靠性,使后续的系统测试更有效。具体原因如下:
- 提前发现问题:接口自动化测试能够快速检测接口的核心功能是否符合预期,确保在正式提交给测试团队前发现并解决问题。这样,开发团队可以在测试初期就消除接口的明显缺陷,减轻后续测试阶段的问题积压。
- 避免系统级的传染问题:如果接口在提测前已经通过自动化测试验证,那么在系统测试阶段出现的问题更可能是集成或业务逻辑问题,而非单个接口的问题,避免系统测试受接口错误影响。
- 提高测试效率:接口自动化测试可以重复执行并快速反馈。通过在提测前开展,可以为开发团队提供即时反馈,减少返工,提高整体测试效率。
开发提测后,优先开展系统测试,后开展接口自动化测试
在开发提测后优先进行系统测试,而将接口自动化测试放在系统测试之后进行的安排,主要是考虑整个系统的集成性和端到端的业务流程。具体原因包括:
- 系统测试的全面性:系统测试关注的是系统整体的功能、性能和用户体验,是端到端的测试流程。它可以通过UI或集成测试发现接口之间、模块之间的集成问题,帮助快速验证整体功能是否符合需求。
- 接口自动化测试的补充作用:接口自动化测试更适合用于验证接口的逻辑是否正确,但对于全系统级别的流程验证可能存在一定的局限性。在系统测试后开展接口自动化测试,可以补充测试用例覆盖不到的细节和边缘情况,尤其适合复杂的业务逻辑和边界条件的测试。
- 测试资源分配:在开发提测后,系统测试优先进行可以充分利用有限的测试资源和时间,确保主要功能流程在系统层面上被验证。接口自动化测试随后进行可以更有针对性地进行补充测试,避免测试资源的浪费。
如何开展接口自动化测试
- 选取自动化测试用例
- 搭建自动化测试环境
- 搭建自动化测试框架
- 代码实现自动化
- 输出测试报告
- 实现持续集成
选取自动化测试用例
1. 适合自动化的用例特点
-
重复性强的用例:例如登录、注册等基本功能,这些用例在每次回归测试中都会被执行,适合自动化。
-
业务逻辑相对简单:自动化测试较适合测试独立性强、无复杂依赖关系的功能。例如,接口功能测试、边界值验证等。
-
易于预期和检查的用例:测试结果容易验证的用例,如简单的输入-输出匹配的接口,适合自动化。
-
回归测试频率高的用例:频繁进行回归测试的模块,比如核心业务流程和关键功能模块。
-
多种组合的用例:参数化测试,例如不同用户角色访问权限或不同商品种类,适合自动化并进行数据驱动测试。
2. 不适合自动化的用例特点
-
依赖人类判断的用例:涉及用户界面、视觉效果和用户体验的测试,这类测试依赖人的主观判断。
-
业务变化频繁的用例:需求变化较快或不稳定的模块,自动化测试的维护成本较高,不适合优先自动化。
-
复杂依赖关系的用例:需要与外部系统频繁交互或依赖外部数据的测试。此类用例容易因外部因素导致结果不稳定。
搭建自动化测试环境
核心技术:
编程语言:Python
测试框架:pytest
接口请求:requests
在终端或命令行输入以下命令来安装
pytest
:pip install pytest
安装完成后,可以用以下命令检查是否安装成功:
pytest --version
在终端或命令行输入以下命令来安装
requests
:pip3 install requests
安装完成后,可以在Python中使用以下代码验证安装成功:
pip3 show requests
搭建自动化测试框架
搭建基础框架
定义项目目录结构
发起 HTTP 请求
步骤包括:
导包
Request发送请求
Response查看响应
下面两个示例:
GET
请求
GET
请求用于从服务器获取数据:
import requestsresponse = requests.get("https://jsonplaceholder.typicode.com/posts/1")
print(response.status_code) # 输出状态码
print(response.json()) # 以 JSON 格式解析并输出响应内容
2. POST
请求
POST
请求用于向服务器发送数据,通常是表单数据或 JSON 数据:
import requests# 请求数据
data = {
"title": "foo",
"body": "bar",
"userId": 1
}# 自定义请求头
headers = {"Content-Type": "application/json", # 设置请求体内容为 JSON"Authorization": "Bearer YOUR_ACCESS_TOKEN" # 示例添加 Authorization 头部
}# 发送 POST 请求,包含 JSON 数据和自定义头部
response = requests.post("https://jsonplaceholder.typicode.com/posts", json=data, headers=headers)# 打印响应状态码
print(response.status_code)# 打印响应内容(假设返回的内容是 JSON 格式)
print(response.json())
以下代码实现的测试流程图:
通用功能类封装
比如我们现在将验证码和登陆功能进行封装
api目录下的login.py:
import requestsclass LoginAPI:def __init__(self):self.url_verify="验证码的url"self.url_login="登陆的url"# 验证码def get_verify_code(selfs):return requests.get(url=selfs.url_verify)# 登陆def login(self,test_data):return requests.post(url=self.url_login,json=test_data)
接口对象封装和调用
举例:
script目录下的business.py:
from api.login import LoginAPI
#创建测试类
class TestContractBusiness:#前置处理def setup_method(self):#实例化接口对象self.login_api=LoginAPI()# 后置处理def teardown_method(self):pass#登陆成功def test01_login_success(self):#获取验证码res_v=self.login_api.get_verify_code()print(res_v.status_code)print(res_v.json())print(res_v.json().get("uuid"))#登陆login_data={"username":"admin","password":"HM_2023_test","code":"2","uuid":res_v.json().get("uuid")}res_l=self.login_api.login(test_data=login_data)print(res_l.status_code)print(res_l.json())
比如登陆之后我们要实现添加课程的功能,那么代码示例:
api目录下的course.py:
import requests#创建接口类
class CourseAPI:#初始化def __init__(self):self.url_add_course="添加课程的url"#课程添加def add_course(self,test_data,token):return requests.post(url=self.url_add_course,json=test_data,headers={"Authorization":token})
CourseAPI
类初始化时,会设定好课程添加接口的 URL。- 调用
add_course
方法时,可以传入课程数据和身份令牌。- 该方法会构造一个 POST 请求,将数据和令牌发送至指定的课程接口,完成课程的添加操作。
script目录下的business.py:
# from api.login import LoginAPI
from api.login import CourseAPI
#创建测试类
class TestContractBusiness:#前置处理def setup(self):#实例化接口对象# self.login_api=LoginAPI()self.course_api=CourseAPI()def teardown(self):pass#登陆成功# def test01_login_success(self):# #获取验证码# res_v=self.login_api.get_verify_code# print(res_v.status_code)# print(res_v.json())# print(res_v.json.get("uuid"))## #登陆# login_data={# "username":"admin",# "password":"admin123",# "code":"2",# "uuid":res_v.json.get("uuid")# }## res_l=self.login_api.login(test_data=login_data)# print(res_l.status_code)# print(res_l.json())#提取登陆成功之后的token数据并保存在类的属性中TestContractBusiness.token=res_l.json.get("token")print(TestContractBusiness.token)def test02_add_course(self):add_data={"新增的课程信息"}response=self.course_api.add_course(test_data=add_data,token=TestContractBusiness.token)print(response.json())
以上代码为将登陆功能的代码注释掉了,方便大家看新增的代码
假如添加课程后还有一个上传合同的功能:
api目录下的contract.py:
import requests#创建接口类
class ContractAPI:#初始化def __init__(self):self.url_upload="上传合同的url"#合同上传接口def upload_contract(self,test_data,token):return requests.post(url=self.url_upload,files={"file":test_data},headers={"Authorization":token})
multipart/form-data
类型:
- 如果要上传文件或以
multipart/form-data
形式发送数据,需要使用files
参数。files
参数适用于发送文件和表单数据。
application/json
类型:
- 如果要以 JSON 格式发送数据,用
json
参数,这会自动将数据序列化为 JSON 格式。
script目录下的business.py:
# 导包
from api.contract import ContractAPI# 创建测试类
class TestContractBusiness:#前置处理def setup(self):#实例化接口对象# self.login_api=LoginAPI# self.course_api=CourseAPIself.contract_api=ContractAPIdef teardown(self):pass# 上传合同成功def test03_upload_contract(self):
# 在data目录下放一个合同的PDF文件
# 读取PDF文件数据f=open("../data/test.pdf","rb")
# "rb" 是一种文件打开模式,表示以“二进制读模式”打开文件,因此可以处理 PDF 文件的二进制内容response=self.contract_api.upload_contract(test_data=f,token=TestContractBusiness.token)print(response.json())
登陆功能和新增课程的代码这里我就没复制过来啦
api目录下的contract.py:
import requests
#
# #创建接口类
class ContractAPI:#初始化def __init__(self):self.url_upload="上传合同的url"self.add_contract="新增合同的url"
#
# #合同上传接口
# def upload_contract(self,test_data,token):
# 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_contract,json=test_data,headers={"Authorization":token})
script目录下的business.py:
# 合同新增成功def test04_add_contract(self):add_data={"新增的合同的相关信息"}responses=self.contract_api.add_contract(test_data=add_data,token=TestContractBusiness.token)print(responses.json())
断言:
Python 中常见的断言(assertion)方式主要用于测试代码中的条件是否为真,通常在单元测试和调试过程中非常有用。
assert
语句
这是 Python 自带的断言语句,用于快速检测条件是否满足。如果条件为假,它会抛出 AssertionError
异常并终止程序。
判断值相等
用于检测两个值是否相等。常用于验证计算结果是否符合预期。
x = 10
y = 10
assert x == y
判断字符串包含
用于检测某个字符串是否包含在另一个字符串中。
message = "Hello, world!"
assert "Hello" in message
断言的应用
登陆:
script目录下的test08_login.py
from api.login import LoginAPIclass TestLoginAPI:
# 初始化uuid=None# 前置处理def setup(self):
# 实例化接口类self.login_api=LoginAPI()
# 获取验证码responses=self.login_api.get_verify_code()print(responses.json())
# 提取验证码接口返回的uuid参数值TestLoginAPI.uuid=responses.json.get("uuid")print(TestLoginAPI.uuid)# 后置处理def teardown(self):pass# 登陆成功def test01_success(self):login_data={"username":"manager","password":"123456","code":"2","uuid":TestLoginAPI.uuid}responses=self.login_api.login(test_data=login_data)# 断言响应状态码为200assert 200==responses.status_code# 断言响应数据中包含"成功"assert '成功'in responses.txt# 断言响应数据中的code值 assert 200==responses.json.get("code")# 登陆失败(用户名为空)def test02_without_username(self):login_data = {"username": "","password": "123456","code": "2","uuid": TestLoginAPI.uuid}responses = self.login_api.login(test_data=login_data)assert 200 == responses.status_codeassert '错误' in responses.txtassert 500 == responses.json.get("code")# 登陆失败(为注册用户)def test03_username_not_exist(self):login_data = {"username": "111","password": "123456","code": "2","uuid": TestLoginAPI.uuid}responses = self.login_api.login(test_data=login_data)assert 200 == responses.status_codeassert '错误' in responses.txtassert 500 == responses.json.get("code")
我们看到上面的代码是不是有很多重复的,下面我们学习数据驱动来通过一个测试脚本可以覆盖多个不同的数据场景,不用为每种测试数据单独编写测试用例,而是通过迭代测试数据来驱动测试。
Python 数据驱动
Python 数据驱动(Data-Driven)是一种通过外部数据源驱动测试执行的测试方法。在数据驱动测试中,测试用例的数据(输入、期望结果等)被分离到一个外部文件或数据源中,而测试逻辑和数据是独立的。这种方法使得测试更灵活,可以通过简单地添加或修改数据来扩展测试用例。
测试数据参数化
参数化是实现数据驱动的一种具体方式。pytest
的参数化功能可以显著提高测试效率、减少重复代码,并且让测试更加易读易维护。
在 pytest
中,可以使用参数化(parameterization)来方便地测试函数在不同输入下的输出。参数化让我们可以一次性编写多个测试用例,而不用为每组数据单独写测试函数。
基本的参数化用法
pytest
提供了 @pytest.mark.parametrize
装饰器来实现参数化。它的基本语法如下:
import pytest@pytest.mark.parametrize("参数列表", [(参数1值1, 参数2值1, ...), (参数1值2, 参数2值2, ...)])
def test_function(参数1, 参数2, ...):# 测试逻辑
比如刚刚的登陆功能我们可以写成这样:
from api.login import LoginAPI
import pytest# 测试数据
test_data=[("manager","123456",200,'成功',200),("","123456",200,'错误',500),("111","123456",200,'错误',500)
]
class TestLoginAPI:
# 初始化uuid=None# 前置处理def setup(self):
# 实例化接口类self.login_api=LoginAPI()
# 获取验证码responses=self.login_api.get_verify_code()print(responses.json())
# 提取验证码接口返回的uuid参数值TestLoginAPI.uuid=responses.json.get("uuid")print(TestLoginAPI.uuid)# 后置处理def teardown(self):pass# 登陆成功@pytest.mark.parametrize("useername,password,status,message,code",test_data)def test01_success(self,useername,password,status,message,code):login_data={"username":useername,"password":password,"code":"2","uuid":TestLoginAPI.uuid}responses=self.login_api.login(test_data=login_data)# 断言响应状态码为200assert status==responses.status_code# 断言响应数据中包含"成功"assert message in responses.txt# 断言响应数据中的code值assert code==responses.json.get("code")
但是在实际工作中,测试数据和逻辑是需要分开写的,所以接下来我们用json文件实现数据驱动
data包下的login.json:
[{"username":"manager","password":"123456","status":200,"message":"成功","code":200},{"username":"","password":"123456","status":200,"message":"错误","code":500},{"username":"111","password":"123456","status":200,"message":"错误","code":500}
]
script目录下的test09_login.py
from api.login import LoginAPI
import pytest
import json# 读取json文件
def build_data(json_file):
# 定义空列表test_data=[]
# 打开json文件with open(json_file,"r") as f:
# 加载json文件数据json_data=json.load(f)
# 循环遍历测试数据for case_data in json_data:
# 转换数据格式[{}] ==> [(),()]username=case_data.get("username")password=case_data.get("password")status=case_data.get("status")message=case_data.get("message")code=case_data.get("code")test_data.append((username,password,status,message,code))
# 返回处理之后的测试数据return test_dataclass TestLoginAPI:
# 初始化uuid=None# 前置处理def setup_method(self):
# 实例化接口类self.login_api=LoginAPI()
# 获取验证码responses=self.login_api.get_verify_code()print(responses.json())
# 提取验证码接口返回的uuid参数值TestLoginAPI.uuid=responses.json().get("uuid")print(TestLoginAPI.uuid)# 后置处理def teardown_method(self):pass# 登陆成功@pytest.mark.parametrize("username,password,status,message,code",build_data(json_file="../data/login.json"))def test01_success(self,username,password,status,message,code):login_data={"username":username,"password":password,"code":"2","uuid":TestLoginAPI.uuid}responses=self.login_api.login(test_data=login_data)# 断言响应状态码为200assert status==responses.status_code# 断言响应数据中包含"成功"assert message in responses.text# 断言响应数据中的code值assert code==responses.json().get("code")
用例组织执行
1.登陆单接口自动化测试:如上
2.添加课程单接口自动化测试:
from api.login import LoginAPI
from api.course import CourseAPI
#创建测试类
class TestContractBusiness:#前置处理def setup_method(self):#实例化接口对象self.login_api=LoginAPI()self.course_api= CourseAPI()# 登陆成功def test01_login_success(self):#获取验证码res_v=self.login_api.get_verify_code()print(res_v.status_code)print(res_v.json())print(res_v.json().get("uuid"))#登陆login_data={"username":"admin","password":"HM_2023_test","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")print(TestContractBusiness.token)def teardown_method(self):pass# 课程添加成功def test01_add_course(self):add_data={"name":"测试开发提升课1","subject":"6","price":55,"applicablePerson":"2","info":"测试开发提升课102"}responses=self.course_api.add_course(test_data=add_data,token=TestContractBusiness.token)print(responses.json())# 断言响应状态码assert 200==responses.status_code# 断言返回数据中包含指定的文字assert "成功" in responses.text# 断言json返回数据code值assert 200==responses.json().get("code")# 课程添加失败(未登录)def test02_add_course(self):add_data = {"name": "测试开发提升课1","subject": "6","price": 55,"applicablePerson": "2","info": "测试开发提升课102"}responses = self.course_api.add_course(test_data=add_data, token="xxx")print(responses.json())# 断言响应状态码assert 200 == responses.status_code# 断言返回数据中包含指定的文字assert "认证失败" in responses.text# 断言json返回数据code值assert 401 == responses.json().get("code")
3.课程查询列表单接口自动化测试:
需求:
之前我们没有学过课程查询功能,所以我们需要先对接口对象进行封装
import requests
import config#创建接口类
class CourseAPI:#初始化def __init__(self):self.url_add_course="添加课程的URL"self.url_select_course = "查询课程的URL"#课程添加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):return requests.get(url=self.url_select_course + f"/{test_data}", headers={"Authorization": token})
from api.login import LoginAPI
from api.course import CourseAPI# 创建测试类
class TestContractBusiness1:# 前置处理def setup_method(self):# 实例化接口对象self.login_api = LoginAPI()self.course_api = CourseAPI()# 登陆成功def test01_login_success(self):# 获取验证码res_v = self.login_api.get_verify_code()print(res_v.status_code)print(res_v.json())print(res_v.json().get("uuid"))# 登陆login_data = {"username": "admin","password": "HM_2023_test","code": "2","uuid": res_v.json().get("uuid")}res_l = self.login_api.login(test_data=login_data)# 提取登陆成功之后的token数据并保存在类的属性中TestContractBusiness1.token = res_l.json().get("token")print(TestContractBusiness1.token)def teardown_method(self):pass# 查询存在的课程def test01_select_course(self):responses = self.course_api.select_course(test_data="?name=测试开发提升课1",token=TestContractBusiness1.token)print(responses.json())# 断言响应状态码assert 200 == responses.status_code# 断言返回数据中包含指定的文字assert "成功" in responses.text# 断言json返回数据code值assert 200 == responses.json().get("code")print(responses.json())# 查询不存在的课程def test02_select_course(self):responses = self.course_api.select_course(test_data="?name=测试开发提升课1", token="xxx")print(responses.json())# 断言响应状态码assert 200 == responses.status_code# 断言返回数据中包含指定的文字assert "认证失败" in responses.text# 断言json返回数据code值assert 401 == responses.json().get("code")
当在线上环境测试的时候,这么多的测试url修改起来是不是太麻烦了,而且之前的上传合同功能的路径都要一个个修改。。。
这时我们就可以利用配置文件来解决~
在 Python 接口自动化框架中,配置文件的作用是集中管理框架运行所需的各种配置信息,从而提高代码的可维护性和可扩展性。它允许开发者在不修改核心代码的情况下,灵活调整框架的运行参数或环境配置。
将运行所需的配置信息(如接口地址、数据库连接信息、测试用户信息等)集中存储,避免在代码中硬编码,提高代码的可读性和易维护性。
方便实现测试环境、预发布环境、生产环境等多环境的切换。例如,接口自动化测试框架可能需要在开发环境和生产环境下运行不同的接口地址。
现在我们就来修改一下前面的代码吧
针对url和项目路径
config.py:
# 存放被测试项目的基本信息,比如URL等import os
# 设置项目环境域名
BASE_URL="http://kdtx-test.net"# 获取项目根路径
BASE_PATH=os.path.dirname(__file__)
print(BASE_PATH)
import requests#导包
import configclass LoginAPI:def __init__(self):self.url_verify=config.BASE_URL+"/api/yzm"self.url_login=config.BASE_URL+"/api/login"# 验证码def get_verify_code(selfs):return requests.get(url=selfs.url_verify)# 登陆def login(self,test_data):return requests.post(url=self.url_login,json=test_data)
import requests
import config#创建接口类
class CourseAPI:#初始化def __init__(self):self.url_add_course=config.BASE_URL+"/api/clues/course"self.url_select_course = config.BASE_URL+"/api/clues/course/list"#课程添加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):return requests.get(url=self.url_select_course + f"/{test_data}", headers={"Authorization": token})
上传合同的路径修改为:
f = open(config.BASE_PATH+"/data/test.pdf", "rb")
当这些学习完了之后呢,我们就会基本的接口自动化测试啦~
最后我们就学习一下怎么生成测试报告
Allure 是一个轻量级、灵活且强大的 测试报告生成工具,主要用于整合和展示自动化测试结果。它支持多种测试框架(如 Pytest、JUnit、TestNG 等),帮助开发人员和测试人员以可视化的方式查看测试用例执行情况、分类信息、测试步骤和数据。
首先我们要安装allure
pip install allure-pytest
修改pytest.ini配置文件:
[pytest]
addopts = -s --alluredir=report
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*
运行pytest:(注意在Terminal运行)
运行allure serve report生成测试报告即可