一个项目带你走进接口自动化测试

文章目录

  • 前言
  • 一、项目环境搭建
  • 二、项目分析
  • 三、框架搭建
      • 1、解决登录问题、获取token
      • 2、熟悉项目的接口请求方式、二次封装requests请求
      • 3、缓解业务请求接口参数臃肿
      • 4、重新封装logging日志
      • 5、通用方法编写
  • 四、编写自动化脚本
      • 场景业务需求
      • 单接口业务需求
  • 五、生成测试报告
  • 六、写在最后


前言

  • 该项目有助于进一步了解自动化测试

  • 适用于普遍中型企业接口自动化框架

    • Python+Request+Pytest+Yaml+Log+allure+git+邮箱/钉钉+集成Jenkins(本文暂时不写集成Jenkins)
  • 适合有一定Python编程语言基础,了解函数式编程;会使用Pycharm开发工具。


一、项目环境搭建

~~为了方便同学们进一步了解接口自动化测试,该项目基于考试星在线考试系统进行接口自动化测试。

一个项目带你走进接口自动化测试_第1张图片

  • 提前注册账号

  • 熟悉里面的问卷模块业务功能(玩玩!)

    • 发布问卷
    • 查询问卷
    • 删除问卷

二、项目分析

  1. 解决项目登录问题(Cookie持久化)、部分请求操作包含token(获取token)
  2. 知道项目主要接口请求类型,便于requests请求二次封装
  3. 如何处理数据(YAML)
  4. 如何记录日志(Logging)
  5. 通用方法剥离
  6. 生成测试报告(Allure)-该博客省略
  7. 如何发生测试报告(集成邮箱/钉钉)-该博客省略
  8. 如何持续集成(Jenkins)-该博客省略

三、框架搭建

一个项目带你走进接口自动化测试_第2张图片

1、解决登录问题、获取token

	打开F12抓取登录等接口,分析请求方式、请求体内容、返回值内容
	login 接口可以获取Cooke值,获取存储即可cookie持久化操作
	login_check 接口返回值中有token值,即发起请求(两个参数sessionid和companyId都是login接口返回中有)即可获取

一个项目带你走进接口自动化测试_第3张图片

# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"


import requests
import urllib3
import json

HTTP_HEADERS = {
            'authority': "www.kaoshixing.com",
            'pragma': "no-cache",
            'Cache-Control': "no-cache",
            'origin': "https://www.kaoshixing.com",
            'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36",
            'x-tingyun-id': "YfNlX9ebdkc;r=778649428",
            'Content-Type': "application/x-www-form-urlencoded;charset=UTF-8",
            'accept': "application/json, text/javascript, */*; q=0.01",
            'sec-fetch-dest': "empty",
            'x-requested-with': "XMLHttpRequest",
            'sec-fetch-site': "same-origin",
            'sec-fetch-mode': "cors",
            'referer': "https://www.kaoshixing.com/login/account/login/262319",
            'accept-language': "zh-CN,zh;q=0.9",

}

class Exam_login(object):
    """考试星登录方法
    这里只考虑单账号登录,只把user_name参数化
    @param user_name 用户名
    companyId
    newCompanyId
    password
    passwordMD5 这些参数通过抓取Login接口即可获得
    """
    def __init__(self,user_name):
        self.user_name = user_name
        # 处理user_name_input 参数
        user_name_input = user_name.split('@')[0]
        login_url = "https://www.kaoshixing.com/login/account/login"
        data = {
            "userName": user_name,
            "userNameInput": user_name_input,
            "phoneAccount": "",
            "authCode": "",
            "captchaText": "",
            "companyId": "xxxx",
            "newCompanyId": "xxxx",
            "password": "xxxx",
            "passwordMD5": "xxxx",
            "nextUrl": "",
            "remember": "false"
        }
        # 警用urllib3 警告
        urllib3.disable_warnings()
        response = requests.post(login_url, data=data, headers=HTTP_HEADERS, verify=False)
        # 获取登录后的cookie
        self.cookies = requests.utils.dict_from_cookiejar(response.cookies)
        # session_id 用于登录login_check
        self.session_id = self.cookies['sessionId']
        # 把获取的cookie值进行拼接
        cookie = ""
        for i in self.cookies:
            cookie = cookie + i + '=' + self.cookies[i] + ';'
        # 把获取的cookie与本地存储的cookie进行拼接
        self.cookies = cookie
        cookies = cookie + config.LOCAL_COOKIE
        self.post_cookies = cookie


    def login_check(self):
        """考试星登录后验证方法
		companyId Login接口抓取
		"""
        login_check_url = "https://exam.kaoshixing.com/login/public/login_check"
        data ={
            "sessionId": self.session_id,
            "companyId": "xxxx"
        }
        # 转为字符串进行传参数
        data = json.dumps(data)
        response = requests.request("POST", login_check_url, headers=HTTP_HEADERS, data=data, verify=False)
        content = json.loads(str(response.content,encoding='utf-8'))
        # 获取登录token的值
        token = content['data']['bizContent']['token']
        return token


if __name__ == '__main__':
    a  = Exam_login(user_name="xxxx")
    print(a.post_cookies)
    token = a.login_check()
    print(token)

2、熟悉项目的接口请求方式、二次封装requests请求

  • 项目中主要是post和get请求,post请求居多
import requests


def exam_request(method, param, headers, url):
    """
    考试星接口二次封装,考试星大部分是POST请求
    :param method: 请求方法
    :param param: 参数
    :param headers: 请求头
    :param url: 请求地址
    :return: 数据
    """
    if len(param)<= 0:
        raise AssertionError("参数不能为空")
     # 如果是get请求,拼接url
    if method == "GET" or  method == "get":
        get_param = ""
        for key in param:
            get_param = get_param + "{}={}&".format(key, param[key])
        url = url + "?" + get_param
        payload = {}
        response = requests.request("GET", url, headers=headers, data=payload)
    else:
        response = requests.request("POST", url, headers=headers, data=param)
    return response

3、缓解业务请求接口参数臃肿

# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"


"""用来读取数据"""

import yaml 
import json
from configparser import  ConfigParser


class MyConfigParser(ConfigParser):
    # 重写configparser 中的optionxform 函数 解决.ini 文件自动转小写的问题
    def __init__(self, defaults=None):
        ConfigParser.__init__(self, defaults=defaults)

    def optionxform(self, optionstr: str) -> str:
        return optionstr

class ReadFileData():
    def __init__(self):
        pass
        
	# 读取Yaml数据
    def load_yaml(self, file_path):
        with open(file_path, encoding="utf-8") as f:
            data = yaml.safe_load(f)
        return data
	
	# 读取json数据
    def load_json(self, file_path):
        with open(file_path, encoding="utf-8") as f:
            data = json.load(f)
        return data
        
    # 读取init数据
    def load_init(self, file_path):
        config = MyConfigParser()
        config.read(file_path, encoding="utf-8")
        data = dict(config._sections)
        return  data

data = ReadFileData()
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"


import pytest
import  os
from method.read_data import data # 上一个封装读取数据的py文件路径


BASE_PATH = os.path.dirname(__file__)


def get_data(yaml_file_name):
    try:
        data_file_path = os.path.join(BASE_PATH, "data", yaml_file_name)
        yaml_data = data.load_yaml(data_file_path)
    except Exception as e:
        pytest.skip(str(e))
    else:
        return yaml_data

api_data_get = get_data("api_test_get.yaml") # 读取当前目录data下文件名为api_test_get.yaml
api_data_post = get_data("api_test_post.yaml")# 读取当前目录data下文件名为api_test_post.yaml
scenes_data = get_data("scenes_test.yaml")#读取当前目录data下文件名为scenes_test.yaml
单接口参数通过@pytest.mark.parametrize()传递参数即可,场景测试参数需要写个模块级别的请求fixtures
"""
针对于单接口  
yaml数据为
test_right:
  # 考试口令,返回码
  - ['EDBHET', 0]
"""


from Test_exam.conftest import api_data_get

class Test_get_demo():
    """获取考试列表"""
    @allure.testcase(".....")
    @allure.description("test_get_demo")
    @pytest.mark.parametrize("name, result",api_data_get["test_get_demo"])
    def test_get_demo(self,name, result):
"""
对于场景,Yaml参数为
# 新建我的考试文件夹-重命名-移动文件夹-删除
test_create_dir_rename_move_delete:
  # 创建考试名称
  new_name: '期中考试'
  # 修改考试名称
  change_name: '期末考试'
  # 移动文件夹id
  target_id: '15688454'
  # 删除返回断言
  errorcode: 0
"""

def test_create_dir_rename_move_delete(testcase_data):
	pass

4、重新封装logging日志

# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"

"""
封装log日志控制台打印输出
"""


import datetime
import logging
import os




def init_log(log_level=logging.INFO, log_dir =''):
    if log_dir =='':
        logging.basicConfig(level=log_level,
                            format="%(asctime)s-%(name)s-%(levelname)s-%(message)s",
                            datefmt= '%m-%d %H:%M:%S')
	# 此处省略文件记录日志方法方法

def debug(tag, msg):
    _logger = logging.getLogger(tag)
    _logger.debug(msg)

def info(tag, msg):
    _logger = logging.getLogger(tag)
    _logger.info(msg)

def warning(tag, msg):
    _logger = logging.getLogger(tag)
    _logger.warning(msg)

def error(tag, msg):
    _logger = logging.getLogger(tag)
    _logger.error(msg)

def critical(tag, msg):
    _logger = logging.getLogger(tag)
    _logger.error(msg)


init_log()

5、通用方法编写

  • 创建13时间戳
  • 生成Allure测试报告

四、编写自动化脚本

场景业务需求

  • 管理员创建问卷->列表查询问卷->用户答卷提交->获取用户问卷信息->删除问卷
  • 问卷分类新增->修改->删除
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"


from method.exam_request import Exam_request
from method import config
import allure
import pytest


@allure.description("创建分类_修改分类_删除分类")
@allure.title("test_create_category_change_and_delete")
def test_create_category_change_and_delete(testcase_data):
    # 实例化
    request = Exam_request()
    # 登录账号
    request.account_login(user_name=config.MASTER_ACCOUNT)
    # 获取分类id
    request.get_classify_id()
    assert request.pid is not None
    # 新增分类
    name = testcase_data["name"]
    request.add_classify(name)
    assert request.question_classify_id is not None
    # 修改分类
    rename = testcase_data["rename"]
    update_info = request.update_classify(rename=rename)
    assert update_info["code"] == 200
    # 删除分类
    delete_info = request.delete_classify()
    assert delete_info["code"] == 200


if __name__ == '__main__':
    pytest.main([__file__,'-s'])
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"


from method.exam_request import Exam_request
from method import config
import allure
import pytest


@allure.description("创建问卷_查询问卷_用户答卷_获取数据_删除问卷")
@allure.title("test_create_questionnaire_query_user_post_get_data_delete_questionnaire")
def test_create_questionnaire_query_user_post_get_data_delete_questionnaire(testcase_data):
    # 实例化
    request = Exam_request()
    # 登录账号
    request.account_login(user_name=config.MASTER_ACCOUNT)
    # 编辑问卷
    name = testcase_data["name"]
    introduce = testcase_data["introduce"]
    question = testcase_data["question"]
    content = testcase_data["content"]
    request.edit_the_questionnaire(name=name,
                                   introduction=introduce,
                                   question=question,
                                   content=content)
    assert request.questioninfotab is not None
    assert request.questioninfo is not None
    # 发布问卷
    days = testcase_data["during_days"]
    request.post_questionnaire(title=name,during_time=days)
    # 查询问卷
    question_info = request.query_questionnaire(name=name)
    assert question_info["code"] == testcase_data["code"]
    assert question_info["data"]["bizContent"]["rows"][0]["title"] == name
    # 获取问卷
    request.get_questionnaire()
    assert request.questionid is not None
    # 提交问卷
    request.submit_questionnaire()
    # 统计数据
    request.analysis_questionnaire()
    # 删除问卷---去了回收站,并未删除
    request.delete_questionnaire()
    assert request.code == 200


if __name__ == '__main__':
    pytest.main([__file__,'-s'])

单接口业务需求

  • 存在问卷删除
  • 不存在问卷删除
# -*- encoding:utf-8 -*-
__author__ = "Nick"
__created_date__ = "2022/09/24"


"""
这里的两个删除只是例子,虽然不影响功能
但任意id值都可以删除,接口并未做校验
"""


import pytest
import allure
import os
from method.exam_request import Exam_request
from method import config
from Test_exam.conftest import api_data_get




class Test_delete_questionnaire():
    """删除问卷接口"""
    @allure.testcase("删除问卷接口-存在")
    @allure.description("Test_delete_questionnaire")
    @pytest.mark.parametrize("id, code",api_data_get["test_delete_one"])
    def test_delete_one(self,id, code):
        # 初始化
        request = Exam_request()
        # 登录账号
        request.account_login(user_name=config.MASTER_ACCOUNT)
        # 删除问卷
        request.delete_questionnaire(id,single=True)
        assert request.code == code


    @allure.testcase("删除问卷接口-不存在")
    @allure.description("Test_delete_questionnaire")
    @pytest.mark.parametrize("id, code",api_data_get["test_delete_two"])
    def test_delete_two(self,id, code):
        # 初始化
        request = Exam_request()
        # 登录账号
        request.account_login(user_name=config.MASTER_ACCOUNT)
        # 删除问卷
        request.delete_questionnaire(id, single=True)
        assert request.code == code



if __name__ == '__main__':
    pytest.main([__file__ ,'-s'])
    # 单脚本测试生成allure报告
    # pytest.main([__file__, '-s', '-q', '--alluredir', './result'])
    # os.system('allure generate ./result -o ./report --clean')

五、生成测试报告

  • 自行配置allure环境(windows/mac)
# 定义生成测试报告的方法
def create_allure_report(xml_report_path, html_report_path):
    """生成Allure测试报告"""
    # 自定义shell
    cmd = "allure generate %s -o %s --clean"%(xml_report_path, html_report_path)
    try:
        os.system(cmd)
    except Exception:
        raise Exception("执行用例失败,请检查环境配置")

一个项目带你走进接口自动化测试_第4张图片

六、写在最后

  • 文档主要是带入了解接口自动化的学习(基于Python语言),有些细化的知识点需要自己去索引学习
  • 有部分过程文档没有写,需要自己去研究,最后会附上整体项目源码(知识星球内可下载)
  • 集成Jenkins也不是特别难,搭建好Jenkins自己捣鼓捣鼓即可集成,下次有时间把Jenkins集成补充完成

一个项目带你走进接口自动化测试_第5张图片

一个项目带你走进接口自动化测试_第6张图片

你可能感兴趣的:(测试实战,接口自动化,python,开发语言)