自动化测试如何做?搭建接口自动化框架从0到1实战(超细)

news2024/11/16 11:40:34

目录:导读

    • 前言
    • 一、Python编程入门到精通
    • 二、接口自动化项目实战
    • 三、Web自动化项目实战
    • 四、App自动化项目实战
    • 五、一线大厂简历
    • 六、测试开发DevOps体系
    • 七、常用自动化测试工具
    • 八、JMeter性能测试
    • 九、总结(尾部小惊喜)


前言

传统软件测试行业是以手工测试为主,也就是所谓的点点点,加上国内软件公司不注重测试,受制于大环境影响等也就给了大众一种测试人员虽然身处互联网行业,却是毫无技术可言的工种。

话锋一转,到了如今,不得不说一声:大人,时代变了,最直观的表现莫过于招聘要求的提高,越来越要求测试人员拥有七十二变的能力。
而在这其中,自动化测试能力是现在手工测试迈向更高技术岗位的必经之路。

搭建接口测试框架

构建接口测试思维:
当前互联网产品最大的特点就是快,上线周期通常是以"天"甚至是以"小时"为单位,而传统软件产品的周期多以"月",甚至以"年"为单位。因此,如何在保证产品质量下,有效缩短测试回归时间成了重中之重。

两个突破口:
引入测试的并发执行,即从以往的串行执行测试用例,采用分布式的方法并行执行。

从测试策略上找到突破口,从传统软件产品的金字塔测试策略往菱形测试策略转变。以接口测试为主,GUI测试为辅,单元测试则根据公司实际情况进行。

四点建议:
以中间层的API测试为重点做全面的测试;
轻量级的GUI测试,只覆盖最核心直接影响主营业务流程的E2E场景;
最上层的GUI测试通常利用探索式测试思维,以人工测试的方式发现尽可能多的潜在问题;
单元测试只对那些相对稳定并且核心的服务和模块开展全面的单元测试,而应用层或者上层业务只会做少量的;

为何要搭建测试框架?

开发自己的框架更能结合自身工作中的痛点,难点来做一个针对性的解决,使其扩展性更高,后期也能接入CI/CD。

利用现有工具来进行接口测试,随着项目的规模变大,维护成本将会增大,不利于管控。

工具本身具有一定的局限性,如支持的协议比较单一。
不用纠结技术选型,根据自身的技术实力和技术功底来选择,而不要以开发工程师的技术栈来选择。

定义专属框架目录结构
test_case:存放测试用例
test_data:存放测试数据
report:存放测试报告
common:存放公共方法
lib:存放第三方库
config:存放环境配置信息
main:框架主入口
fixture:类似unittest中的setUp/tearDown的存在,但功能远比他们强大

构建框架流程

在框架构建过程中,由于篇符有限,本文只涉及其中部分环节。

1、在common公共模块、封装定义框架专属的http请求能力

# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: pan-li
import requests


class HttpRequests(object):

    def __init__(self, url):
        self.url = url
        self.req = requests.session()
        # 自定义请求头,根据自身所在公司项目需求
        self.headers = {'Content-Type': 'application/json', 'User-Agent': 'Node midway-v2x Version/1.28.1'}
	
    # 封装get请求
    def get(self, url='', params='', data='', headers=None, cookies=None):
        response = self.req.get(url=url, params=params, data=data, headers=headers, cookies=cookies)
        return response
	
    # post请求
    def post(self, url='', params='', data='', headers=None, cookies=None):
        response = self.req.post(url=url, params=params, data=data, headers=headers, cookies=cookies)
        return response
	
    # put请求
    def put(self, url='', params='', data='', headers=None, cookies=None):
        response = self.req.put(url=url, params=params, data=data, headers=headers, cookies=cookies)
        return response

    # delete请求
    def delete(self, url='', params='', data='', headers=None, cookies=None):
        response = self.req.delete(url=url, params=params, data=data, headers=headers, cookies=cookies)
        return response

2、抽离URL生成url_conf.py在config文件中

import enum


class URLConf(enum.Enum):
    TEST_URL = 'http://10.12.7.20:8443/v2x-omp/api/'

3、编写接口测试用例在test_case文件中,第一版测试用例,安装pytest,pip install -U pytest

import os
import sys
import pytest
import json
from common.http_requests import *
from config.url_conf import URLConf
project_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(project_root)

class TestV2x:

    @classmethod
    def setup_class(cls) -> None:
        cls.url = URLConf.TEST_URL.value
        cls.http = HttpRequests(cls.url)

    def setup(self) -> None:
        self.headers = {'Content-Type': 'application/json', 'User-Agent': 'Node midway-v2x Version/1.28.1'}
        self.http = HttpRequests(self.url)

    def tearDown(self):
        pass

    @staticmethod
    def get_token():
        headers = {'Content-Type': 'application/json', 'User-Agent': 'Node midway-v2x Version/1.28.1'}
        response = TestV2x.http.post(url=URLConf.TEST_URL.value, data='{"cmd":"signin","params":{"userName":"smarttest","password":"72be4b7f62832c516b85fb26de59df53"}}', headers=headers)
        token = response.json()['detail']['token']
        return token

    def test_001_queryArea(self):
        """查询区域"""
        playload = {"cmd": "queryArea", "csrfToken": TestV2x.get_token(), "params": {"cityId": "320200"}}
        response = TestV2x.http.post(self.url, data=json.dumps(playload), headers=self.headers)
        resultNote = response.json().get('resultNote')
        assert resultNote, 'Success'

    def test_002_queryYearlyCheckCount(self):
        """查询年检总数"""
        playload = {"cmd": "queryYearlyCheckCount", "Token": TestV2x.get_token(), "params": {}}
        response = TestV2x.http.post(self.url, data=json.dumps(playload), headers=self.headers)
        resultNote = response.json().get('resultNote')
        assert resultNote, 'SUCCESS'

    def test_003_queryTrafficEvent(self):
        """查询交通事件"""
        playload = {"cmd": "queryTrafficEvent", "Token": TestV2x.get_token(), "params": {}}
        response = TestV2x.http.post(self.url, data=json.dumps(playload), headers=self.headers)
        resultNote = response.json().get('resultNote')
        assert resultNote, 'Success'

    def test_004_queryRsuCount(self):
        """查询rsu总数"""
        playload = {"cmd": "queryRsuCount", "Token": TestV2x.get_token(), "params": {}}
        response = TestV2x.http.post(self.url, data=json.dumps(playload), headers=self.headers)
        resultNote = response.json().get('resultNote')
        assert resultNote, '查询路测设备数量成功!'

    def test_005_queryDeviceDetail(self):
        """查询设备详情"""
        playload = {"cmd": "queryDeviceDetail", "params": {"deviceId": '0086860703231572'}, "Token": TestV2x.get_token()}
        response = TestV2x.http.post(self.url, data=json.dumps(playload), headers=self.headers)
        resultNote = response.json().get('resultNote')
        assert resultNote, '查询终端信息成功!'


if __name__ == '__main__':
    pytest.main()

4、显然前面的测试用例也是流水账似的,还有很大的优化空间,现在就来一步一步进行。

5、优化一:利用feature特性优化前置和后置条件,fixture目录下的v2x_fixture.py文件

import pytest
from common.http_requests import HttpRequests
from config.url_conf import URLConf


@pytest.fixture(scope='function', autouse=True)
def http():
    url = URLConf.TEST_URL.value
    http = HttpRequests(url)
    return http


@pytest.fixture(scope='function', autouse=True)
def get_token(http):
    headers = {'Content-Type': 'application/json', 'User-Agent': 'Node midway-v2x Version/1.28.1'}
    response = http.post(url=URLConf.TEST_URL.value,
                         data='{"cmd":"signin","params":{"userName":"smarttest","password":"72be4b7f62832c516b85fb26de59df53"}}',
                         headers=headers)
    token = response.json()['detail']['token']
    return token

上述在引入feature之后,简化了http请求的调用,重新定义http()来进行调用。之前每次接口的调用都要附带token参数,现在把获取token的方法提取出来,单独封装,加上feature的装饰,他会作用与每一个方法,用起来更加方便。此处的token是依赖登陆接口之后返回的值,可根据自身项目的需求封装。

6、优化二: 为测试用例添加数据驱动模式

# 以第五个测试用例单独为例
@pytest.mark.parametrize('deviceid', ['0086860703231572', '0086337601270714', '0086822412608154'])
    def test_005_queryDeviceDetail(self, http, get_token, deviceid):
        """查询设备详情"""
        playload = {"cmd": "queryDeviceDetail", "params": {"deviceId": deviceid}, "Token": get_token}
        response = http.post(url=URLConf.TEST_URL.value, data=json.dumps(playload), headers=URLConf.HEADERS.value)
        resultNote = response.json()
        assert resultNote.get('resultNote'), '查询终端信息成功!'
        logger.info('查询终端信息成功!')
"""直接利用pytest.mark.parametrize()装饰器,第一个参数为参数名,后边数组为测试数据,用例当中同样添加形参deviceid"""

在 pytest 中,数据驱动是经由 pytest 自带的 pytest.mark.parametrize() 来实现的。 pytest.mark.parametrize 是 pytest 的内置装饰器,它允许你在 function 或者 class 上定义多组参 数和 fixture 来实现数据驱动。

@pytest.mark.parametrize() 装饰器接收两个参数:

第一个参数以字符串的形式存在,它代表能被被测试函数所能接受的参数,如果被测试函数有多个参数, 则以逗号分

第二个参数用于保存测试数据。如果只有一组数据,以列表的形式存在,如果有多组数据,以列表嵌套元 组的形式存在

7、优化三: 为测试用例添加标签,此时用到pytest.ini配置文件,放在项目任意位置都能生效,有以下作用:

为你的测试框架定制用例查找规则;
为你的测试框架注册标签名称;
指定查找用例起始目录;

[pytest]
python_files = test_*  *_test test*
python_classes = Test* test*
python_functions = test_* test*

markers =
    smoke: marks tests as smoke
    test : marks tests as test
    log : marks tests as log
# 使用时只需要在测试用例上使用@pytest.mark.smoke即可
# 执行时pytest -m [标记名]

8、优化四: 配置pytest.ini文件集成日志收集和实时控制台打印功能

[pytest]
log_cli = 1
log_cli_level = DEBUG
log_cli_date_format = %Y-%m-%d-%H-%M-%S
log_cli_format = %(asctime)s - %(filename)s - %(name)s - %(module)s - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s
log_file = ..\\report\\run.log
log_file_level = DEBUG
log_file_date_format = %Y-%m-%d-%H-%M-%S
log_file_format = %(asctime)s - %(filename)s -%(name)s - %(module)s - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s

关于字段的详解可以在终端输入pytest --help 查看

9、优化五: 定制测试框架测试报告,属于第三方应用放在lib目录中

这里我们使用目前市面上使用人数较多的一款开源测试报告框架Allure,它支持绝大多数测试框架

安装方法:

pip install -U allure-pytest

github上下载最新版本放到lib目录,并配置成系统环境变量(: https://github.com/allure-framework/allure2/releases)
使用方法:

执行pytest命令,并指定allure报告目录: pytest -v -s test_v2x_api_02.py --alluredir=./allure_reports

在线生成allure报告:allure serve allure_reports
生成本地allure报告:allure generate allure_reports

当然这只是在控制台直接命令执行,还不够方便,如果我们想在其他环境运行就又得配置环境变量,那么我们如何把它集成到我们的框架中呢

在共同方法中生成allure工具类,以便分辨运行环境是windows还是mac

import os
import sys
import platform


path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'lib')
allure_path = os.path.join(path, 'allure', 'bin')
sys.path.append(allure_path)


class Report():
    @property
    def allure(self):
        if platform.system() == 'Windows':
            cmd = os.path.join(allure_path, 'allure.bat')
        else:
            cmd = os.path.join(allure_path, 'allure')
        return cmd

10、在main模块中,添加执行调度策略

import os
import threading
import pytest

from common.report import Report

project_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
report_dir = os.path.join(project_root, 'report')
result_dir = os.path.join(report_dir, 'allure_result')
allure_report = os.path.join(report_dir, 'allure_report')
report = Report()


def run_pytest():
    pytest.main(['-v', '-s', f'--alluredir={result_dir}'])


def general_report():
    cmd = "{} generate {} -o {} --clean".format(report.allure, result_dir, allure_report)
    print(os.popen(cmd).read())


if __name__ == '__main__':
    run = threading.Thread(target=run_pytest)
    gen = threading.Thread(target=general_report)
    run.start()  # 多线程先执行pytest命令生成测试报告
    run.join()
    gen.start()	# 报告生成后调用allure工具类生成本地报告

11、最后一版测试用例,整合前面的优化

import os
import sys
import json
from fixture.v2x_fixture import *
from config.url_conf import URLConf
project_root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(project_root)


class TestV2x:
    @pytest.mark.smoke  # 标签的使用
    def test_001_queryArea(self, http, get_token):
        """查询区域"""
        playload = {"cmd": "queryArea", "csrfToken": get_token, "params": {"cityId": "320200"}}
        response = http.post(url=URLConf.TEST_URL.value, data=json.dumps(playload), headers=URLConf.HEADERS.value)
        resultNote = response.json()
        assert resultNote.get('resultNote'), 'success'
        logger.info('查询区域成功')

    def test_002_queryYearlyCheckCount(self, http, get_token):
        """查询年检总数"""
        playload = {"cmd": "queryYearlyCheckCount", "Token": get_token, "params": {}}
        response = http.post(url=URLConf.TEST_URL.value, data=json.dumps(playload), headers=URLConf.HEADERS.value)
        resultNote = response.json()
        assert resultNote.get('resultNote'), 'SUCCESS'
        logger.info('查询年检成功')

    def test_003_queryTrafficEvent(self, http,get_token):
        """查询交通事件"""
        playload = {"cmd": "queryTrafficEvent", "Token": get_token, "params": {}}
        response = http.post(url=URLConf.TEST_URL.value, data=json.dumps(playload), headers=URLConf.HEADERS.value)
        resultNote = response.json()
        assert resultNote.get('resultNote'), 'Success'
        logger.info('查询交通事件成功')

    def test_004_queryRsuCount(self, http, get_token):
        """查询rsu总数"""
        playload = {"cmd": "queryRsuCount", "Token": get_token, "params": {}}
        response = http.post(url=URLConf.TEST_URL.value, data=json.dumps(playload), headers=URLConf.HEADERS.value)
        resultNote = response.json()
        assert resultNote.get('resultNote'), '查询路测设备数量成功!'
        # text = response.text
        # print(text)
        logger.info('查询路侧设备成功')
	
    # 简单的数据驱动
    @pytest.mark.parametrize('deviceid', ['0086860703231572', '0086337601270714', '0086822412608154'])
    def test_005_queryDeviceDetail(self, http, get_token, deviceid):
        """查询设备详情"""
        playload = {"cmd": "queryDeviceDetail", "params": {"deviceId": deviceid}, "Token": get_token}
        response = http.post(url=URLConf.TEST_URL.value, data=json.dumps(playload), headers=URLConf.HEADERS.value)
        resultNote = response.json()
        assert resultNote.get('resultNote'), '查询终端信息成功!'
        logger.info('查询终端信息成功!')


if __name__ == '__main__':
    # 打印更详细的信息
    pytest.main(['-s', '-v', ])
下面是我整理的2023年最全的软件测试工程师学习知识架构体系图

一、Python编程入门到精通

请添加图片描述

二、接口自动化项目实战

请添加图片描述

三、Web自动化项目实战

请添加图片描述

四、App自动化项目实战

请添加图片描述

五、一线大厂简历

请添加图片描述

六、测试开发DevOps体系

请添加图片描述

七、常用自动化测试工具

请添加图片描述

八、JMeter性能测试

请添加图片描述

九、总结(尾部小惊喜)

只要心中燃烧着不灭的激情,勇敢面对困难与挑战,坚持不懈地追求目标,就能书写属于自己的辉煌篇章。相信自己的能力与潜力,努力奋斗,每一次努力都是一次进步,每一次拼搏都是一次成长。奋斗不息,成功必将绽放!

不论前路多么曲折艰难,只要心怀信念与勇气,坚持不懈地奋斗,才能创造属于自己的辉煌。相信自己的才华与潜力,努力拼搏,每一次奋斗都是一次进步,每一次挑战都是一次成长。

勇往直前,不畏艰难险阻,只有坚持不懈的奋斗,才能追逐梦想的光芒。相信自己的能力与潜力,不断超越自我,每一次努力都是一次成长,每一次拼搏都是一次进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/804888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#时间轴曲线图形编辑器开发1-基本功能

目录 一、前言 1、简介 2、开发过程 3、工程下载链接 二、基本功能实现 1、绘图面板创建 (1)界面布置 (2)显示面板代码 (3) 面板水平方向、竖直方向移动功能实现 (4)面板放…

kotlin 编写一个简单的天气预报app(三)

使用eventbus替换broadcast 将从Broadcast切换到EventBus有以下几个好处: 解耦性:通过使用EventBus,您可以实现组件之间的解耦。传统的Broadcast机制需要发送方和接收方明确知道对方的存在,并且需要在代码中设置Intent过滤器和广…

Mnist分类与气温预测任务

目录 传统机器学习与深度学习的特征工程特征向量pytorch实现minist代码解析归一化损失函数计算图Mnist分类获取Mnist数据集,预处理,输出一张图像面向工具包编程使用TensorDataset和DataLoader来简化数据预处理计算验证集准确率 气温预测回归构建神经网络…

网页版五子棋项目演示

项目源码:五子棋游戏 演示使用的用户名:zyz 密码:123 注册页面: 登录页面: 游戏大厅页面: 未匹配: 匹配中: 游戏房间页面: 对方落子: 己方落子: 对…

5.6 Java递归讲解

5.6 Java递归讲解 A方法调用B方法,我们很容易理解递归就是:A方法调用A方法!就是自己调用自己利用递归可以实现通过简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解&#xff0…

Redis的五大数据类型介绍

、简介 Redis的五大数据类型也称五大数据对象;前面介绍过6大数据结构,Redis并没有直接使用这些结构来实现键值对数据库,而是使用这些结构构建了一个对象系统redisObject;这个对象系统包含了五大数据对象,字符串对象&am…

MFC第二十四天 使用GDI对象画笔和画刷来开发控件(分页控件选择态的算法分析、使用CToolTipCtrl开发动静态提示)

文章目录 GDI对象画笔和画刷来开发控件梯形边框的按钮控件CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp 矩形边框的三态按钮控件 CToolTipCtrl开发动静态提示CMainDlg.hCMainDlg.cppCLadderCtrl.hCLadderCtrl.cpp: 实现文件 矩形边框的三态按钮控件 CToolTipCtrl开发动…

linux服务器安装redis

一、安装下载 下载安装参考文章 下载安装包地址:https://download.redis.io/releases/ 亲测有效,但是启动的步骤有一些问题 安装完成!!! 二、启动 有三种启动方式 默认启动指定配置启动开机自启 说明&#xff1a…

CentOS下 Docker、Docker Compose 的安装教程

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。 Docker Compose是用于定义…

【Lua学习笔记】Lua进阶——Table(4)继承,封装,多态

文章目录 封装继承多态 封装 // 定义基类 Object {}//由于表的特性,该句就相当于定义基类变量 Object.id 1//该句相当于定义方法,Object可以视为定义的对象,Test可以视为方法名 //我们知道Object是一个表,但是抽象地看&#xff…

为什么要有虚拟内存?

操作系统是通过内存分段和内存分页的方式管理虚拟内存地址和物理内存地址之间的关系 内存分段 程序是由若干个逻辑分段组成的,代码分段、数据分段、栈段、堆段组成,不同的段有不同的属性,所以就用分段的形式分离开。 分段机制下的虚拟内存…

【业务功能篇58】Springboot + Spring Security 权限管理 【下篇】

4.2.2.3 SpringSecurity工作流程分析 SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KjoRRost-1690534711077)(http…

使用Django自带的后台管理系统进行数据库管理的实例

Django自带的后台管理系统主要用来对数据库进行操作和管理。它是Django框架的一个强大功能,可以让你快速创建一个管理界面,用于管理你的应用程序的数据模型。 使用Django后台管理系统,你可以轻松地进行以下操作: 数据库管理&…

详解机器学习中的熵、条件熵、相对熵和交叉熵

这个是讲的不错的链接 https://www.cnblogs.com/kyrieng/p/8694705.html 这个是交叉熵 https://blog.csdn.net/m0_57236802/article/details/129554878

《焊接点云处理》-角焊焊缝处理

角焊缝点云处理 前言一、代码二、实现步骤3、验证前言 针对T型板,识别效果如下所示 一、代码 主函数 #include "CGALRECONSTRUCT.h" #include "CGALREGIONPLANE.h" #include

设计利器,掌握CAD辅助命令的必备指南

CAD设计中的辅助命令是提高效率和确度的关键工具。掌握并正确运用CAD中的各种辅助命令对于设计师们来说至关重要。本文将为你详细介绍如何使用CAD中的辅助命令,从而帮助你在设计过程中更加高效地实现你的创意。、 大家有没有发现,当我们的直线命令移动到…

Rethinking the Image Fusion(PMGI)

1.摘要 本文提出了一种基于梯度和强度比例维护(PMGI)的快速统一图像融合网络,可以端到端实现各种图像融合任务,包括红外和可见图像融合、多曝光图像融合、医学图像融合、多焦点图像融合和全色增强。我们将图像融合问题统一为源图…

C++信号量与共享内存实现进程间通信

关于信号量和共享内存的相关知识可参考下面链接: 进程间通信方式介绍_夜雨听萧瑟的博客-CSDN博客 C 创建共享内存_c共享内存_夜雨听萧瑟的博客-CSDN博客 信号量SytemV与Posix信号量的介绍与用法_夜雨听萧瑟的博客-CSDN博客 直接上代码,代码如下&#…

蓝桥杯单片机第十二届国赛 真题+代码

iic.c /* # I2C代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础&#xff0c;根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求&#xff0c;进行代码调试和修改。 */ #include <STC1…