一、接口测试的概念
1、接口:指系统或组件之间的交互点,通过这些交互点可以实现数据之间的交互。(数据交互的通道)
2、接口测试:对系统或组件之间的接口进行测试,主要用于检测外部系统与系统之间以及系统内部之间的数据交换、传递和控制管理过程,以及系统间的相互逻辑依赖关系。
3、接口自动化测试:使用程序或者工具自动的完成对进口进行测试。
二、接口测试的实现方式
- 工具:Postman、Jmeter
- 代码:python + requests
三、接口测试的流程
- 需求分析
- 主要依据是需求文档
- 挑选需要做接口测试的功能
- 接口文档解析
- 设计测试用例
- 执行测试
- 使用接口测试工具实现
- 通过编写代码实现
- 接口缺陷管理与跟踪
- 接口自动化持续集成
四、接口自动化测试的流程
- 将功能用例抽取转化成自动化用例
- 搭建测试环境 (工具环境或者代码环境)
- 使用代码搭建自动化结构目录
- 编写用例脚本
- 执行用例脚本
- 查看测试报告
- 持续集成
五、接口测试用例
ID | 系统 | 模块 | 用例名称 | 接口名称 | 请求URL | 请求类型 | 请求头 | 请求参数类型 | 请求参数 | 预期结果 | 测试结果 |
001 | 前台系统 | 登录 | 登录成功 | 登录 |
| POST |
| json |
| | |
002 | 文章 | 发布文章 | 发布文章 |
| POST |
| json |
| | | |
003 | 后台管理系统 | 登录 | 登录成功 | 登录 |
| POST |
| json |
| | |
004 | 文章 | 查询文章 | 查询文章 |
| GET |
| QueryString |
| | | |
005 | 审核文章 | 审核文章 |
| PUT |
| json |
| | |
六、项目搭建
(1) 初始化项目
- 目录名称
(2)创建目录结构
- 目录结构包括:api、scripts、data、log、report、tool
【关于项目目录结构的说明】
- api:资源对象接口封装(相当于web自动化测试中的page)
- scripts:资源接口测试脚本
- data:数据驱动
- log:日志
- report:测试报告
- tool:工具类
(3)公共变量的抽取
通过分析接口测试用例,我们可以提取如下公共变量,分别为:
- host域名及端口:http://ttapi.research.itcast.cn
- 请求消息头headers:{"Content-Type": "application/json"}
为了方便api包下的模块调用提取的公共变量,我们可以将公共变量存放在api包下的__init__.py模块中:
【以下为本接口自动化测试项目抽取的所有公共变量】
1 """公共变量"""
2 from tool.read_yaml import read_yaml
3
4 # 1. 请求域名
5 host = "http://ttapi.research.itcast.cn"
6
7 # 2. 请求信息头
8 headers = {"Content-Type": "application/json"}
9
10 # 3. 用于接收服务器返回的文章id,用于文章审核是调用
11 article_id = None
12
13 # 接收发布文章读取的数据
14 data_article = read_yaml("mt_article.yaml")
15 print(data_article)
16
17 # 文章title
18 title = data_article[0][0]
19 # 文章内容
20 content = data_article[0][1]
21 # 文章所属频道id
22 channel_id = data_article[0][2]
23 # 文章所属频道
24 channel = data_article[0][3]
(4)资源Api结构搭建及实现
【前端媒体资源】
1、前端媒体资源api结构搭建 -- 主要包括初始化以及待测试的各个接口 -- api_mt.py
- 初始化
- 定义登录接口url
- 定义发布文章接口url
- 登录接口封装
- 定义请求数据
- 发送post请求返回响应对象
- 发布文章接口封装
- 定义请求数据
- 发送post请求返回响应对象
2、前端媒体资源api结构的实现
【代码具体实现如下】
1 import api
2 import requests
3 from tool.get_log import GetLog
4
5 log = GetLog.get_logger()
6
7 class ApiMt:
8
9 # 1. 初始化
10 def __init__(self):
11 # 1. 登录接口url
12 self.url_login = api.host + "/mp/v1_0/authorizations"
13 log.info("正在初始化自媒体登录url:{}".format(self.url_login))
14 # 2. 发布文章接口url
15 self.url_article = api.host + "/mp/v1_0/articles"
16 log.info("正在初始化自媒体发布文章url:{}".format(self.url_article))
17 pass
18
19 # 2. 登录接口
20 def api_mt_login(self,mobile,code):
21 """
22
23 :param mobile: 手机号
24 :param code: 验证码
25 :return: 响应对象
26 """
27 # 1. 定义请求数据
28 data = {"mobile":mobile,"code":code}
29 # 2. 调用post方法
30 log.info("正在调用自媒体登录接口,请求数据:{}".format(data))
31 return requests.post(url=self.url_login, json=data, headers=api.headers)
32
33 # 3. 发布文章接口
34 def api_mt_article(self,title,content,channel_id):
35 """
36
37 :param title: 文章标题
38 :param content: 文章内容
39 :param channel_id: 频道id
40 :param cover: 封面 0: 为自动
41 :return: 响应对象
42 """
43 # 1. 定义请求数据
44 data = {"title":title,"content":content,"channel_id":channel_id,"cover":{"type":0,"images":[]}}
45 # 2,调用post方法
46 log.info("正在调用自媒体发布文章接口,请求数据:{}".format(data))
47 return requests.post(url=self.url_article, json=data, headers=api.headers)
【注意】
在发送请求出传递请求头参数时,注意headres的拼写
【后台管理资源】
1、后台管理资源api结构的搭建 -- 主要包括初始化以及待测试的各个接口 -- api_ht.py
- 初始化
- 定义登录url
- 定义查询文章url
- 定义审核文章url
- 登录
- 定义请求数据
- 发送post请求返回响应对象
- 查询文章
- 定义请求数据
- 发送get请求返回响应对象
- 审核文章
- 定义请求数据
- 发送put请求返回响应对象
2、后台管理资源api结构的实现
【代码具体实现如下】
1 import api
2 import requests
3 from tool.get_log import GetLog
4
5 log = GetLog.get_logger()
6
7 class ApiHt:
8
9 # 初始化: 用于获取各个接口的url
10 def __init__(self):
11 # 1. 登录url
12 self.url_login = api.host + "/mis/v1_0/authorizations"
13 log.info("正在初始化后台管理系统 登录url:{}".format(self.url_login))
14 # 2. 查询url
15 self.url_search = api.host + "/mis/v1_0/articles"
16 log.info("正在初始化后台管理系统 查询url:{}".format(self.url_search))
17 # 3. 审核url
18 self.url_audit = api.host + "/mis/v1_0/articles"
19 log.info("正在输出化后台管理系统 审核url:{}".format(self.url_audit))
20
21
22 # 登录接口
23 def api_ht_login(self,account,password):
24 """
25
26 :param account: 账号
27 :param password: 密码
28 :return: 响应信息
29 """
30 # 1. 参数数据
31 data = {"account":account,"password":password}
32 log.info("正在调用后台管理系统登录接口,请求的数据为:{} 请求的信息头为:{}".format(data,api.headers))
33 # 2. 调用post方法
34 return requests.post(url=self.url_login,json=data,headers=api.headers)
35
36 # 查询文章接口
37 def api_ht_search(self):
38 """
39 :param title: 文章标题 数据来源于api.__init__.py模块
40 :param channel: 文章频道 数据来源于api.__init__.py模块
41 :return:
42 """
43 # 1. 参数数据
44 data = {"title":api.title,"channel":api.channel}
45 log.info("正在调用后台管理系统查询文章接口,请求的数据为:{} 请求的信息头为:{}".format(data,api.headers))
46 # 2. 调用get方法
47 return requests.get(url=self.url_search,params=data,headers=api.headers)
48
49 # 审核文章接口
50 def api_ht_audit(self):
51 """
52 :param article_ids: 文章id,数据来源发布文章后服务器生成的
53 :param status: 2 为文章审核通过
54 :return: 响应对象
55 """
56 # 1. 参数数据
57 data = {"article_ids":[api.article_id],"status":2} # 2 为文章审核通过
58 log.info("正在调用后台管理系统文章审核接口,请求的数据为:{} 请求的信息头为:{}".format(data,api.headers))
59 # 2. 调用put方法
60 return requests.put(url=self.url_audit,json=data,headers=api.headers)
【说明】
- 在实现搭建的api结构时,查询文章和审核文章的参数数据都是直接从api包下的__init__.py模块中直接读取的,因为参数数据已通过read_yaml工具读取并存储在__init__.py模块中;
- 在发送请求时,注意headers单词的拼写。
(5)测试业务层结构搭建及实现
【前端媒体资源测试业务】
1、前端媒体测试业务层搭建
- 初始化
- 获取ApiMt对象
- 登录接口测试方法
- 调用登录接口
- 提取响应数据中的token信息
- 断言(参数化)
- 发布文章接口测试方法
- 调用发布文章接口
- 获取响应中的数据中文章id
- 断言(参数化)
2、公共方法的提取
由于在断言时,前后台接口所涉及的5条测试用例,据需要进行断言操作,且断言操作的内容基本上都是比较响应状态码以及返回数据中的message;
同时在前后台登录接口中均涉及token数据的提取,因此我们可以将断言和token数据的提取封装成成相应的工具方法,在测试脚本中需要时直接调用工具类中的方法即可。
代码实现如下:
1 """
2 用于提取测试脚本中的公共方法:
3 - 获取token
4 - 断言
5 """
6 import api
7 from tool.get_log import GetLog
8
9 log = GetLog.get_logger()
10
11 class Tool:
12
13 @classmethod
14 def common_token(cls,resp):
15 # 获取token
16 token = resp.json()["data"]['token']
17 # 将token添加到headers中
18 api.headers["Authorization"] = "Bearer " + token
19 log.info("正在提取token,提取后的headers为:{}".format(api.headers))
20 print("添加token后的headers为:", api.headers)
21
22 @classmethod
23 def common_assert(cls,resp,status_code=201):
24 log.info("正在调用公共断言方法!")
25 # 断言响应状态码
26 assert status_code == resp.status_code
27 # 断言响应内容
28 assert "OK" == resp.json()["message"]
-- 断言操作中默认的响应状态为201,在在实际的测试过程中,如果存在其他的响应状态码,可以在调用该断言方法时进行修改
3、登录、发布文章测试用例实现
1 from api.api_mt import ApiMt
2 from tool.tool import Tool
3 from tool.get_log import GetLog
4 from tool.read_yaml import read_yaml
5 import pytest
6 import api
7
8 log = GetLog.get_logger()
9
10 import api
11 class TestMt:
12
13 # 1. 初始化
14 def setup_class(self):
15 # 获取ApiMt对象
16 self.mt = ApiMt()
17 pass
18
19 # 2. 登录接口测试方法
20 @pytest.mark.parametrize("mobile,code",read_yaml("mt_login.yaml"))
21 def test_01_mt_login(self,mobile,code):
22 # 调用登录接口
23 resp = self.mt.api_mt_login(mobile=mobile,code=code)
24 # 打印数据结果
25 print("登录结果为:", type(resp.json()))
26 try:
27 # 提取token
28 Tool.common_token(resp)
29 # 断言
30 Tool.common_assert(resp)
31 except Exception as e:
32 # 写日志
33 log.error("断言错误:{}".format(e))
34 # 抛异常
35 raise
36
37 # 以下方法封装成tool
38 # # 提取token
39 # token = resp.json()["data"]['token']
40 # # 将token追加到请求头 -- 保存到api包的__init__.py中
41 # api.headers["Authorization"] = "Bearer " + token
42 # print("添加token后的headers为:", api.headers)
43 # # 断言状态码
44 # assert 201 == resp.status_code
45 # # 断言响应信息
46 # assert "OK" == resp.json()["message"]
47
48 # 3. 发布文章接口测试方法
49 def test_02_mt_article(self,title=api.title,content=api.content,channel_id=api.channel_id):
50 # 1. 调用发布文章接口
51 resp = self.mt.api_mt_article(title,content,channel_id)
52 # 2. 获取id
53 api.article_id = resp.json()["data"]['id']
54 print("发布文章成功后的id:{}".format(api.article_id))
55 try:
56 # 3. 断言
57 Tool.common_assert(resp)
58 except Exception as e:
59 # 1. 日志
60 log.error("断言失败:{}".format(e))
61 # 2. 异常抛出
62 raise
【说明】
- 在实现发布文章接口测试方法时,我们需要提取响应数据中文章id的信息,并将其赋值给api包下的__init__.py模块中的article_id变量,因为后台管理系统在查询文章使需要使用到该article_id的值;
- 在给测试脚本命名时,除了以test开头外,名称中最好不要出现数字。
【后台管理资源测试业务】
1、后台管理测试业务层搭建
- 初始化
- 获取ApiHt对象
- 登录测试方法
- 调用登录接口
- 提取响应信息中的token值
- 断言
- 查询文章测试方法
- 调用查询文章接口
- 断言
- 审核文章测试方法
- 调用审核文章接口
- 断言
2、登录、查询文章以及审核文章测试用例实现
【测试用例代码实现】
1 from api.api_ht import ApiHt
2 from tool.tool import Tool
3 from tool.get_log import GetLog
4 from tool.read_yaml import read_yaml
5 import api
6 import pytest
7
8 log = GetLog.get_logger() # 用于记录日志信息
9
10 class TestHt:
11
12 # 1. 初始化
13 def setup_class(self):
14 # 获取ApiHt对象
15 self.ht = ApiHt()
16 pass
17
18 # 2. 登录
19 @pytest.mark.parametrize("account,password",read_yaml("ht_login.yaml"))
20 def test_01_ht_login(self,account,password):
21 # 调用登录接口
22 resp = self.ht.api_ht_login(account=account,password=password)
23 # 提取token
24 Tool.common_token(resp)
25 print("后台管理系统登录后,请求的headers为:{}".format(api.headers))
26 try:
27 # 断言
28 Tool.common_assert(resp)
29 except Exception as e:
30 # 1. 日志
31 log.error("断言错误:{}".format(e))
32 # 2. 抛异常
33 raise
34
35 # 3. 查询文章
36 # -- 说明:由于后台无法查看的审核文章的信息,因此在断言时出错,因此导致查询文章和审核文章的用例无法继续向下运行
37 def test_02_ht_search(self):
38 # 1. 调用查询文章接口
39 resp = self.ht.api_ht_search()
40 print("查询文章的信息为:",resp.json())
41 try:
42 # 2. 断言
43 Tool.common_assert(resp,status_code=200) # 注意:状态码为200并且为int类型
44 except Exception as e:
45 # 日志记录
46 log.error("断言出错:{}".format(e))
47 # 抛异常
48 raise
49
50 # 4. 审核文章
51 def test_03_ht_audit(self):
52 # 1. 调用审核文章接口
53 resp = self.ht.api_ht_audit()
54 try:
55 # 2. 断言
56 Tool.common_assert(resp)
57 except Exception as e:
58 # 日志记录
59 log.error("断言错误:{}".format(e))
60 # 抛异常
61 raise
【补充说明】
在实现业务的测试用例时,我们还使用到了参数化以及日志记录。
与Web自动化测试,我们仍然使用data包下的.yaml文件来存储测试数据,使用tool包下的read_yaml.py模块来读取测试数据(直接复制web自动化测试项目中的read_yaml.py模块)。同时为了便于分析测试结果,我们还可以在api、scripts包下的模块中添加相应的日志记录,具体实现如下:
- 前端登录测试数据
- 前端发布文章测试数据
- 后台管理登录测试数据
- 测试数据读取
1 # 用于读取yaml中的数据信息
2
3 import os
4 from config import BASE_PATH
5 import yaml
6
7 # 用于读取yaml中的数据
8 def read_yaml(filename):
9 file_path = BASE_PATH + os.sep + 'data' + os.sep + filename # os.sep表示文件的路径分隔符,在windows系统中表示'\',在linux系统中表示'/'
10 # 定义空列表,组装测试数据
11 arr = []
12 # 获取文件流
13 with open(file_path,'r',encoding="utf-8") as f: # 读取文件注意指定编码方式
14 # 遍历 调用yaml.safe_load(f).values()
15 for datas in yaml.safe_load(f).values():
16 #print("hello") # 此时循环只遍历一次
17 arr.append(tuple(datas.values()))
18 # 返回结果
19 return arr
20
21 if __name__ == '__main__':
22 print(read_yaml("mt_login.yaml"))
- 日志记录
1 import logging
2 import logging.handlers
3 import os
4 from config import BASE_PATH
5
6 """
7 确保生成的日志为同一个对象
8 """
9 class GetLog:
10
11 # 定义类属性,保存日志
12 __logger = None
13
14 @classmethod
15 def get_logger(cls):
16 # 判断日志是否为空
17 if cls.__logger is None:
18 # 为空,则创建日志器
19 cls.__logger = logging.getLogger('myLog_mt')
20 # 修改默认级别 -- INFO
21 cls.__logger.setLevel(logging.INFO)
22 # 创建处理器 -- 将日志信息输出到指定的文件中
23 log_path = BASE_PATH + os.sep + "log" + os.sep + "mt.log"
24 th = logging.handlers.TimedRotatingFileHandler(filename=log_path,
25 when="midnight",
26 interval=1,
27 backupCount=3,
28 encoding="utf-8")
29 # 创建格式器
30 fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
31 fm = logging.Formatter(fmt)
32 # 将格式器设置到处理器中
33 th.setFormatter(fm)
34 # 将处理器添加到格式器中
35 cls.__logger.addHandler(th)
36 # 返回日志器
37 return cls.__logger
38
39 if __name__ == '__main__':
40 log = GetLog.get_logger()
41 log.info("测试信息日志级别")
42 log.error("测试错误级别")
【Tips】
- 在读取或存储相关数据到本项目下的文件夹中时(如读取测试数据/日志存储到指定目录下),由于由于路径中存在相同的前缀,因此我们可以将相同的前缀使用常量BASE_PATH进行统一管理,可以简化路径的书写(在项目根目录下的config.py模块中实现)
1 import os
2
3 BASE_PATH = os.path.dirname(__file__)
- 为防止使用pytest指令执行测试脚本时出现路径报错的问题,我们可以在__init__.py文件中添加如下配置信息:
1 """
2 用于解决路径报问题: 用于自动加载__init__.py文件所在目录下的模块
3 """
4 import sys
5 import os
6
7 sys.path.append(os.getcwd())
【报错信息如下】
七、测试脚本的执行及在线生成测试报告
在执行测试脚本之前,我们需要通过调试确保每个测试脚本都能独立的正常运行。
- 配置文件的编写
- 位置:放置在项目的根目录下
- 名称:pytest.ini
- 内容如下:
1 [pytest]
2 addopts = -s --alluredir report
3 testpaths = ./scripts
4 python_files = test*
5 python_classes = Test*
6 python_functions = test*
- 测试脚本的执行
- 由于pytest在执行测试脚本时,是按照.py模块名称的ACSII顺序进行执行的,因此在给测试脚本命名时,处理以test开头外,可以人为的添加_a_、_b_等字母,使测试脚本按预期的顺序执行;
- 在Terminal中输入:pytest
- 在线生成测试报告
- allure serve report
至此,我们完成了Api自动化测试框架的搭建,当我们在搭建接口自动化测试框架时,只需要遵循如下原则即可:
- 项目目录结构搭建
- api资源结构的搭建
- 公共变量的抽取 -- 存储在api下的__init__.py模块中
- 初始化 -- 定义接口url
- 接口封装 -- 暂时使用pass进行占位
- api资源结构的实现
- 定义请求数据
- 发送请求获取服务器返回的响应对象
- 测试结构搭建
- 初始化 -- 获取apiXX对象
- 接口测试方法 -- 暂时使用pass进行占位
- 测试结构实现
- 调用对应api资源中的接口封装方法
- 参数化
- 断言