设置共享夹具——软件自动化测试入门攻略(5)

发表于:2024-3-12 09:37

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:杨定佳    来源:51Testing软件测试网原创

  11.6.6  设置共享夹具
  接口操作中,绝大部分接口都需要用户认证,因此保持会话就非常关键。这个时候,可以在共享夹具文件conftest.py中添加一个session函数,用以保持会话。在core\conftest.py文件中添加会话函数,代码如下:
  # chapter11\core\conftest.py
  import pytest
  from core.send_request import set_session
  @pytest.fixture(scope="session")
  def session():
      """通过session维持会话"""
      session = set_session()
      yield session
      session.close()
  在函数session中直接调用core/send_request.py文件中封装好的设置会话函数set_session,在每次执行接口测试项目前设置一次session,接口测试项目执行后关闭会话。
  11.6.7  封装断言函数
  在11.6.1节设计测试用例文件时,对预期结果与实际结果的比对已经做了规划。就是根据预期结果类型字段判断实际结果取什么样的值,并和预期结果以怎样的方式进行比对。本小节只需要将其实现即可,实现思路是需要先实现一个compare函数,无论预期结果类型和预期值是什么,都可以通过该compare函数匹配到对应的断言函数,然后再依次实现所有的断言函数。
  在core\compare.py文件中添加断言函数,代码如下:
  # chapter11\core\compare.py
  import json
  import os
  import allure
  from pathlib import Path
  from json import JSONDecodeError
  from requests import Response
  def compare(response: Response, expect_type, expect_value):
      """通过 expect_type 字符串匹配对应的断言"""
      type_map = {"status": status_compare,
                  "boolean": boolean,
                  "str_contain": str_contain,
                  "str_eql": str_eql,
                  "json_contain": json_contain,
                  "file_info": file_info,
                  }
      compare_map = type_map.get(expect_type.lower(), "预期结果类型不存在")
      return compare_map(response, expect_value)
  def add_attachment(actual_body, expect_body, name="响应体", 
  attachment_type=allure.attachment_type.TEXT):
      """添加附件信息"""
      allure.attach(body=str(actual_body), name=f"实际结果:{name}", 
  attachment_type=attachment_type)
      allure.attach(body=str(expect_body), name=f"预期结果:{name}", 
  attachment_type=attachment_type)
  def status_compare(response: Response, expect_value):
      """断言响应状态码与预期结果相等"""
      status_code = response.status_code
      add_attachment(status_code, expect_value, "响应状态码")
      assert expect_value == status_code
  def boolean(response: Response, expect_value):
      """断言布尔类型的响应体与预期结果相等"""
      text = response.text
      add_attachment(text, expect_value)
      assert text.lower() == str(expect_value).lower()
  def str_contain(response: Response, expect_value):
      """断言预期结果在响应体字符串中"""
      text = response.text
      add_attachment(text, expect_value)
      assert expect_value in text
  def str_eql(response: Response, expect_value):
      """断言预期结果与响应体字符串相等"""
      text = response.text
      add_attachment(text, expect_value)
      assert expect_value == text
  def json_contain(response: Response, expect_value):
      """断言JSON格式的响应体包含或相等预期结果"""
      result_json = response.json()
      try:
          expect_value = json.loads(expect_value)
  add_attachment(result_json, expect_value, 
  attachment_type=allure.attachment_type.JSON)
          assert expect_value.items() <= result_json.items()
      except JSONDecodeError as e:
          add_attachment(e, expect_value, name="ERROR")
          assert False
  def file_info(response: Response, expect_value):
      """下载文件断言,断言文件大小大于0 和接口响应状态码是200"""
      expect_value = json.loads(expect_value)
      name = expect_value.get("name", 'download.txt')
      file_name = f"{create_download_path()}/{name}"
      __download_file(response, file_name)
      file_size = __get_file_size(file_name)
      add_attachment(file_size, "文件大小大于 0", "文件大小")
      status_compare(response, 200)
      assert file_size > 0
  def create_download_path():
      """创建 download 文件夹"""
      current_file = os.path.realpath(__file__)
      download_path = Path(current_file).parent.parent.joinpath("download")
      if not os.path.isdir(download_path):
          os.makedirs(download_path)
      return download_path
  def __download_file(response: Response, file_name):
      """下载文件"""
      f = open(file_name, 'ab')
      for chunk in response.iter_content(chunk_size=512):
          if chunk:
              f.write(chunk)
      f.close()
  def __get_file_size(file):
      """获取文件大小"""
      file_size = Path(file).stat().st_size
      return file_size
  首先,我们定义了一个compare函数,并在此函数下定义了一个字典,字典key对应预期结果类型,字典value对应断言函数。由此通过compare函数即可由预期结果类型找到对应的断言函数,断言函数对实际结果和预期结果进行比较,并给出是否通过的结论;然后定义了一个add_attachment函数,用于将预期结果和实际结果输出到Allure测试报告中,方便查看;之后就是依次实现不同类型的断言,具体如下:
  ·status_compare:断言响应状态码与预期结果相等。
   · boolean:断言布尔类型的响应体与预期结果相等。
   · str_contain:断言预期结果在响应体字符串中。
   · str_eql:断言预期结果与响应体字符串相等。
   · json_contain:断言JSON格式的响应体包含或相等预期结果。
   · file_info:下载文件断言,断言文件大小大于0 和接口响应状态码是200。
  对于文件下载的断言,还封装了两个私有函数__download_file和__get_file_size,__download_file用于下载文件,__get_file_size用于获取下载的文件大小。
  11.6.8  添加测试用例函数
  前面几节已经实现了测试用例文件、获取测试用例数据、发送接口请求和封装断言函数,本节将借助前几节已实现的功能实现测试用例函数。在core\test_case.py文件中添加如下:
  # chapter11\core\test_case.py
  import json
  import pytest
  import pandas as pd
  import requests
  from core.get_case import get_all_case, get_login_case, get_change_pwd_case
  from common.read_config import login as login_conf
  from core.send_request import send, set_session
  from core.compare import compare
  def case_ids_body(all_case):
      """获取所有的测试用例,并按照一定顺序排序,并返回测试用例数据和测试用例名"""
      case_df_list = list(all_case)
      case_df = pd.concat(case_df_list) if len(case_df_list) > 1 else case_df_list[0]  # 
  合并所有的 sheet 用例
      case_ids = case_df["用例编号"] + " " + case_df["用例名称"]
      case_body = case_df[["接口地址", "请求方式", "请求头", "请求参数", "预期结果类型", 
  "预期结果"]]
      return {"params": case_body.values, "ids": case_ids.values}
  @pytest.fixture(**case_ids_body(get_all_case()))
  def param_data(request):
      """参数化测试用例"""
      # 返回 request 对象中 param,即参数化数据
      return request.param
  @pytest.fixture(**case_ids_body(get_login_case()))
  def param_login_data(request):
      """参数化登录测试用例"""
      return request.param
  @pytest.fixture(**case_ids_body(get_change_pwd_case()))
  def param_change_pwd_data(request):
      """参数化修改密码测试用例"""
      return request.param
  def run_case(case_fields, session=requests):
      """运行测试用例"""
      url = case_fields[0]
      method = case_fields[1]
      headers = case_fields[2]
      body = case_fields[3]
      expect_type = case_fields[4]
      expect_value = case_fields[5]
      response = send(method, url, headers, body, session)
      compare(response, expect_type, expect_value)
  def test_case(param_data, session):
      """测试用例函数"""
      run_case(param_data, session)
  def test_login_case(param_login_data):
      """登录测试用例函数"""
      run_case(param_login_data)
  def test_change_pwd_case(param_change_pwd_data):
      """修改密码测试用例函数"""
      pwd = eval(param_change_pwd_data[3]).get("old_password")
      data = eval(login_conf["body"])
      data["password"] = pwd
      session = set_session(json.dumps(data))
      run_case(param_change_pwd_data, session)
      session.close()
  在上面代码中首先定义了一个函数case_ids_body,用于获取所有的测试用例,并按照接口地址、请求方式、请求头、请求参数、预期结果类型、预期结果的顺序返回测试用例数据,以“用例编号 用例名称”的格式返回用例名称;然后通过param_data、param_login_data、param_change_pwd_data函数分别以参数化的形式传入所有测试用例数据、登录测试用例数据和修改密码测试用例数据;接着定义了一个基础函数run_case用于运行测试用例,从参数化数据中依次获取接口地址、请求方式、请求头、请求参数、预期结果类型、预期结果字段,并发送请求和结果断言;最后通过基础函数实现test_case(所有测试用例)、test_login_case(登录测试用例)、test_change_pwd_case(修改密码测试用例)测试用例函数。
  下面对添加的代码进行测试,在core\test_case.py文件中添加pytest执行测试用例的代码,如下所示:
  if __name__ == '__main__':
  pytest.main(['-v', 'test_case.py'])
  执行脚本core\test_case.py,控制台输出的结果如下所示:
  ============================= test session starts =============================
  collecting ... collected 23 items
  ..\core\test_case.py::test_case[test_download_01 成功下载小程序包] 
  ..\core\test_case.py::test_case[test_change_pwd_01 请求体中无旧密码] 
  ..\core\test_case.py::test_case[test_change_pwd_02 请求体中无新密码] 
  ..\core\test_case.py::test_case[test_change_pwd_03   请求体中无确认新密码] 
  ..\core\test_case.py::test_case[test_change_pwd_04   旧密码为空] 
  ..\core\test_case.py::test_case[test_change_pwd_05   新密码为空] 
  ..\core\test_case.py::test_case[test_change_pwd_06   确认新密码为空] 
  ..\core\test_case.py::test_case[test_change_pwd_07   旧密码错误] 
  ..\core\test_case.py::test_case[test_change_pwd_08  新密码与确认新密码不一致] 
  ..\core\test_case.py::test_case[test_cache_01 清理缓存成功] 
  ..\core\test_case.py::test_login_case[test_login_01 成功登录] 
  ..\core\test_case.py::test_login_case[test_login_02 请求体中无手机号] 
  ..\core\test_case.py::test_login_case[test_login_03 请求体中无密码] 
  ..\core\test_case.py::test_login_case[test_login_04 手机号为空] 
  ..\core\test_case.py::test_login_case[test_login_05 密码为空] 
  ..\core\test_case.py::test_login_case[test_login_06 手机号错误] 
  ..\core\test_case.py::test_login_case[test_login_07 密码错误] 
  ..\core\test_case.py::test_login_case[test_login_08 手机号不存在] 
  ..\core\test_case.py::test_change_pwd_case[test_change_pwd_01 修改密码成功] 
  ..\core\test_case.py::test_change_pwd_case[test_change_pwd_02   新密码长度=1] 
  ..\core\test_case.py::test_change_pwd_case[test_change_pwd_03   新密码长度超长] 
  ..\core\test_case.py::test_change_pwd_case[test_change_pwd_04   新密码含有空格] 
  ..\core\test_case.py::test_change_pwd_case[test_change_pwd_05 修改密码成功_恢复旧密码] 
  ============================= 23 passed in 11.77s =============================
  从控制台输出的结果可以看到,一共收集了23条测试用例,并且全部执行通过,用时11.77秒。
  至此,整个接口测试项目的核心内容已全部完成。
  11.7  执行测试项目
  接口测试项目核心内容已完成,接下来配置pytest执行参数和添加项目入口文件。
  首先,在pytest.ini中添加pytest配置参数,改变pytest的默认行为。此文件内容根据需要设置,例如添加如下内容:
  [pytest]
  addopts = -sv
  disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
  addopts = -sv意味着pytest在执行时默认添加-vs参数,以输出更加详细的执行信息,并输出print函数的打印信息;disable_test_id_escaping_and_forfeit_all_rights_to_community_support=True可以使pytest在运行过程中保持中文utf-8格式编码。
  然后在项目入口run.py文件中添加执行测试用例语句,再使用Allure命令生成测试报告,代码如下:
  # chapter11\run.py
  import os
  import pytest
  if __name__ == '__main__':
      # 执行测试用例,并生成 Allure 测试报告 JSON 文件
      pytest.main(['-s', '-v', './core/', '--alluredir', './report/json'])
      # 执行 Allure 命令,将 JSON 文件转换为 XML 测试报告
      os.system("allure generate ./report/json -o ./report/xml --clean")
  执行脚本run.py,在download目录下可以看到下载的文件wxapp.zip,在report目录下可以看到生成的JSON文件和XML报告,如图11-5所示。
图11-5  下载文件和报告文件
  打开report/xml/index.html文件查看测试报告,如图11-6所示。可以看到,一共运行了23条测试用例,通过率为100%。
图11-6  测试报告
  打开的测试报告,在suites下可以看到每条测试用例运行的具体情况,如图11-7所示。
图11-7  查看用例详情
  11.8  思  考  题
  1. 什么是接口测试?
  2. 为什么要实施接口测试?
  3. 你都了解哪些接口协议?
  4. 给你一个接口,你如何设计测试用例?
  5. 测试一个接口的基本步骤有哪些?
  6. 你之前做过的接口测试项目,测试流程是什么?
查看《软件自动化测试入门攻略》全部连载章节
版权声明:51Testing软件测试网获得作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号