一、接口测试理论

1.1接口概念:系统之间数据交互的通道

接口本质:可以当一个函数(方法)理解

接口测试:校验接口响应数据与预期数据是否一致

说明:

(1)功能测试玩前端页面,接口测试玩服务器端

(2)接口测试,可以绕过前端界面。直接对服务器进行测试

  1.2接口测试价值

(1)测试页面测试发现不了的问题

(2)符合质量控制的前移概念

(3)测试成本低,效益高

    1.3接口测试的实现方式

Postman:使用简单、上手难度低。功能较少(比如不能直接操作数据库、不能直接生成测试报告--需要借助插件实现)

JMeter:使用难度较大、上手难度大。功能齐全。学会后使用jmeter代替postman做接口测试。

JMeter工具学习使用见:JMeter 工具实践功能接口测试和性能测试-CSDN博客

二、HTTP协议

2.1HTTP简介和URL格式

2.1.1HTTP协议简介

HTTP:超文本传输协议,基于请求与响应 的应用层协议

2.1.2URL格式

语法格式:协议://IP地址:端口号/资源路径?查询参数

URL组成:协议://hostname[:port]/path/[?查询参数1&查询参数2]

例如:https://www.example.com:8080/blog/search?q=python&page=2#results

说明:端口号可以省略,跟随协议走。HTTP----80 HTTPS----443

IP地址:也就是域名。作用:在网络环境中唯一定位一台主机

端口号:作用:在主机上唯一定义个应用程序

资源路径:作用:应用对应的数据资源

查询参数:作用:给资源传递参数

2.2HTTP请求

2.2.1HTTP请求协议整体语法格式

产生端:一定产生于客户端。当客户端给服务器发送请求时,使用该协议

整体格式:

HTTP请求整体格式
请求行 请求方法 空格 URL  空格 协议版本 回车符 换行符
请求头 头部字段名称 :(冒号) 回车符 换行符
                               ......
头部字段名称 :(冒号) 回车符 换行符
空行  空行(回车符或换行符)
请求体                              请求体

(1)请求行:请求方法( get查询、post添加、delete删除、put修改)、URL 、协议版本

        注册登录用post请求

        get、delete请求没有请求体

(2)请求头:key:value

        Content-Type:作用:指定请求体的数据类型

        (1)text/html:HTML格式

        (2)text/plain:纯文本格式

        (3)image/jpeg:jpg图片格式

        (4)application/json:JSOIN数据格式

        (5)application/x-www-from-urlencoded:表单默认的提交数据格式

        (6)multipart/from-data:在表单中进行文件上传时使用

(3)空行 :代表请求头结束

(4)请求体   :发送给服务器请求时,携带的数据 

  练习:抓包获取登录接口,解析请求数据

测试步骤:

(1)谷歌浏览器访问https://reqres.in/

(2)打开抓包工具(浏览器开发者工具为例)

        浏览器开发者工具(Chrome/Firefox):按 F12 打开,选择 Network 标签。
        专业工具:Charles、Fiddler(支持HTTPS解密)或 Wireshark(底层网络包分析)。

(3) 点击登录

2.3HTTP响应

2.3.1HTTP响应协议整体语法格式

产生端:一定产生于服务端。当服务器接收到HTTP请求协议之后才会产生HTTP响应协议

HTTP响应整体格式
响应行 协议版本 空格 状态码 空格 状态码描述 回车符 换行符
响应头 头部字段名称 :(冒号) 回车符 换行符
                                     ......
头部字段名称 :(冒号) 回车符 换行符
空行  空行(回车符或换行符)
响应体                                      响应体

 状态码由三位数字组成,第一个数字定义响应类别

1xx 指示信息

2xx 成功

3xx 重定向

4xx 客服端错误

5xx 服务端错误

三、接口测试基础知识

3.1接口规范

3.1.1传统风格接口

(1)接口统一采用get/post实现所有操作

(2)URL与资源不是一一对应的,在URL中查看出是何种操作

(3)状态码统一返回200

3.1.2RESTful风格

        接口使用的方法,与http协议的请求方法一一对应

(1)get----查、post----增、put----改、delete----删

(2)URL与资源一一对应,不能从URL中看出是何种操作。需要结合请求方法识别何种操作

(3)响应状态码使用比较全面(200、201、204)

3.2接口测试流程

接口文档:又称API文档,是由后端开发编写,用来描述接口信息的文档。

接口文档解析
基本信息                           请求信息          响应信息
编号 接口名称 请求URL 请求方法 请求头 请求参数类型 请求参数 备注(字段说明) 响应状态码 响应数据 备注(字段说明)
001 登录 https://reqres.in/api/login post

contenttype:

application/json

string {email: "eve.holt@reqres.in", password: "cityslicka"} 200

success

code

message

data

 3.3相关工具安装

3.3.1postman工具安装

(1)下载postman安装包Download Postman | Get Started for Free

(2)双击postman安装包安装

3.3.2安装postman插件newman

前提:要安装newman必须先安装有node.js

3.3.2.1安装node.js

下载node.js:Node.js — Download Node.js®

3.3.2.2安装Newman

前提:安装Newman前必须保证node.js已安装成功

在线安装命令:以管理员打开cmd执行命令

npm install -g newman

说明:Newman 是 Postman 官方提供的 命令行工具,用于在终端或持续集成(CI/CD)环境中运行 Postman 集合(Collections)。它的核心作用是将 Postman 的手动测试流程自动化,适合批量执行 API 测试、生成报告并与开发流程集成。

3.3.2.3安装newman-reporter-htmlextra

在线安装命令:以管理员打开cmd执行命令

npm install -g newman-reporter-htmlextra

说明:newman-reporter-htmlextra 是 Newman 的一个扩展报告插件,用于生成比默认 HTML 报告更详细、更美观且可定制化的测试报告。它是社区开发的第三方插件,专为提升 API 测试结果的可读性和分析效率而设计。

3.2.3安装Git

Git安装及使用教程:Git管控本地项目及远程仓库代码(最详细基础篇)_git关联本地目录和远程仓库的命令-CSDN博客

说明:必须给git配置 用户名 和 邮箱。

git config --global user.email "邮箱名"

git config --global user.name "用户名(英文)"

3.2.4pycharm安装配置gitee

(1)打开pycharm--文件--设置

(2)在插件plugins的marketplace标签下。搜索gitee点击安装

四、postman基础使用

4.1postman测试案例

4.1.1案例1:传递查询参数

查询列表用户,并查看响应数据

(1)新建用例集

(2)用例集中添加request请求

请求URL:Request URL:https://reqres.in/api/users?page=2

4.1.2案例2提交JSON数据

发送登录接口请求并查看响应结果

Request URL:https://reqres.in/api/login

请求头:

content-type:application/json

请求体:

{
    "email": "eve.holt@reqres.in",
    "password": "cityslicka"
}

五、接口测试用例设计(重要)

5.1接口测试的测试点

5.1.1接口测试维度-功能测试

5.2接口用例设计方法

5.2.1接口用例设计思路-单接口

业务场景测试:

一定在单接口测试之后!

(1)尽量模拟用户实际使用场景

(2)尽量用最少的用例,覆盖最多的接口请求

(3)一般情况下,覆盖正向测试即可

5.3单接口测试用例

5.3.1登录接口测试用例设计

分析测试点

登录:

(1)正向

        登录成功

(2)反向

        功能异常:手机号未注册、密码错误

        数据异常:手机号为空、手机号含特殊字符、手机号12位、手机号10位、密码为空、密码超过范围

        参数异常:多参、少参、无参、错误参数

登录接口测试用例
编号 用例名称 模块 优先级 前置条件 接口名称 请求方法 URL 请求头 请求数据 预期结果

实际

结果

login_

001

登录成功 登录 P0 手机号已注册 登录 post https://reqres.in/api/login

contenttype:

application/json

{"email": "eve.holt
@reqres.in",
"password": "cityslicka"
}

响应状态码:

200

{ "token":"Qp

wL5tke4

Pnpja7X4"

}

PASS/

Fail/NA

/Brock

login_

002

密码错误 登录 P1 手机号已注册 登录 post https://reqres.in/api/login

contenttype:

application/json

{
    "email": "peter@klaven"
}

响应状态码:

400

{
    "error": "Missing password"
}

PASS/

Fail/NA

/Brock

5.4业务场景测试用例

5.4.1分析测试点

指导思想:模拟用户实际使用,用较少的测试用例,覆盖更多接口,测试正向即可。(时间充裕可增加反向测试)

例如:员工管理业务

登录-添加员工-查询员工-修改员工-删除员工-查询员工列表

        登录作为作为其余接口请求的令牌

六、postman高级使用

6.1postman断言

 让postman工具代替人工自动判断预期结果和实际结果是否一致

6.1.1断言响应状态码

操作步骤:

(1)新建用例集collections-create new collect

(2)用例集中新建接口用例add request

(3)选择请求方法(post)

(4)填写URL、请求头、请求体

(5)断言-script中添加代码

// 断言响应状态码为200
pm.test("Response status code is 200", function () {
    pm.response.to.have.status(200);
});

/*
    pm:postman的实例。
    test()  postman实例的测试方法。这个方法有2个参数。
       参数1: "Response status code is 200"。这个参数可以任意修改,不影响断言。
            作用:在断言结束后,显示给用户,断言结果的提示文字。
        参数2:function () {pm.response.to.have.status(200);}
            作用:是一个匿名函数调用。
            pm.response.to.have.status(200);的意思是:postman的响应结果中,应改有响应状态码200
这里200是预期结果
*/

6.1.2断言包含某字符串(响应体包含任意字符串)

常用单词
英文 中文

expect

期望

include

包含
pm.test("Body matches string",function(){
    pm.expect(pm.response.text()).to.include("string_you_want_search");
});

/*
    pm.expect(pm.response.text()).to.include("string_you_want_search");意思是:postman期望响应文本中应该包含"你想搜索的字符串"(预期结果)
*/

6.1.3断言JSON数据

// 断言JSON数据
 pm.test("you test name",function(){
    var jsonData = pm.response.json();
    pm.expect(jsonData.value).to.eql(100);
 });

/*
    var jsonData = pm.response.json();将整个json响应体赋值到变量 jsonData上。
    var是js语法定义变量。
    pm.expect(jsonData.value).to.eql(100);意思是postman期望json结果中指定key的值为XXX
    value对应实际json结果做变化时候替换成json响应体中的键key。如success、code、message

*/

// 断言响应状态码为200
pm.test("Response status code is 200", function () {
    pm.response.to.have.status(200);
});

// 断言包含某字符串
pm.test("断言响应结果包含:QpwL5tke4Pnpja7X4",function(){
    pm.expect(pm.response.text()).to.include("QpwL5tke4Pnpja7X4");
});

// 断言JSON数据
 pm.test("断言token的值为:QpwL5tke4Pnpja7X4",function(){
    var jsonData = pm.response.json();
    pm.expect(jsonData.token).to.eql("QpwL5tke4Pnpja7X4");
 }); 

6.2postman关联

6.2.1postman关联-简介和步骤

6.2.1.1简介

当接口和接口之间,有依赖关系时,需要借助postman关联技术,来实现。

例如:登录接口 返回的 令牌数据,被 添加员工接口依赖。添加员工接口返回员工ID被查询员工接口依赖。


6.2.1.2实现步骤

(1)原理:

(2)核心代码

# 1、获取响应数据,转为json格式,保存到变量jsonData中
var jsonData = pm.response.json();

# 2.1、使用全局变量做容器
pm.global.set("全局变量名",全局变量值);
#2.2、使用环境变量做容器
pm.environment.set("环境变量名",环境变量值);

# 3、在postman界面(URL、请求头headers、请求体body)中提取全局、环境变量数据
{{全局变量名}}/{{环境变量名}}


#代码提取数据
 var value = pm.environment.get("var_name");

(3)创建环境:environment-create environment

全局变量:在整个postman中都可以使用的变量。不需要单独创建环境

环境变量:在特定的环境下,才能使用的变量。需要给此变量创建单独的环境。

(4)练习:使用postman关联,实现下面案例

从获取天气接口,http://www.weather.com.cn/data/sk/101010100.html获取返回结果中的城市名称

调用百度搜索接口:http://www.baidu.com/S?wd=北京,把获取到的城市名称如北京作为请求参数

操作步骤:

(1)发送获取天气请求,获取响应结果

(2)从响应结果中,拿到城市名,存入全局变量

// 1、获取响应结果
var jsonData = pm.response.json()
// 2、从响应结果中,提取 城市名
var city = jsonData.weatherinfo.city
// 3、将城市名保存到全局变量
pm.globals.set("glb_city",city)

(3)百度搜索接口从全局变量中,取城市名,发送搜索请求

练习2:使用postman关联技术,实现添加员工接口。

登录成功返回的令牌被添加员工接口依赖

登录接口参数:

请求方法:post

URL:https://reqres.in/api/login

请求头header:content-type:application/json

请求体body:

{

    "email": "eve.holt@reqres.in",

    "password": "cityslicka"

}

添加员工参数:

请求方法:post

URL:https://reqres.in/api/users

请求头header:

        content-type:application/json

        authorization:{{env_auth_token}} // 获取令牌的变量名

请求体body:

{
    "name": "John",
    "job": "Engineer"
}

思路:

(1)发送登录请求(必须登录成功)获取响应结果

(2)从json响应结果中,提取data值。拼接上"Bearer "前缀。

        注:单词不能拼错,首字母大写,只有一个空格

(3)将拼接无误的令牌存入环境变量。

// 验证登录成功
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

/*/ 提取token并保存到环境变量
var jsonData = pm.response.json();
pm.environment.set("auth_token", jsonData.token); */

// 1、获取响应结果
var jsonData = pm.response.json()

// 2、提取响应体中的token的值,拼接"Bearer空格"前缀。形成完整的令牌
var auth_token = "Bearer "+jsonData.token

// 3、将完整的令牌存入环境变量
pm.environment.set("env_auth_token",auth_token)

(4)添加员工接口从环境变量中提取令牌。设置到请求头中作为 authorization(授权)的值

(5)填写添加员工接口其他信息发送请求

6.3postman参数化

6.3.1postman参数化简介

参数化:将测试数据,组织到数据文件中,通过脚本的反复迭代,使用不同的数据,达到不同用例的目标。

应用场景:一般在测试同一个接口的不同测试点时,只有测试数据不同。考虑使用参数化。

测试脚本中仅测试数据不一样,使用参数化提高脚本复用。

实现步骤:

(1)测试数据保存在数据文件单独维护

(2)引用数据文件实现脚本迭代调用

6.3.2postman支持的数据文件

(1)CSV

优点:数据组织格式简单

缺点:

        1.不能测试bool类型。因为postman读取csv文件后,将所有非数值类型数据,自动添加双引号变成了字符串。

        2.不能存储复杂数据类型(元组、列表、字典)

        3.不能实现参数测试

应用场景:数据量较大,数据组织格式简单

(2)JSON

优点:

        1.可以测试bool类型

        2.能使用复杂数据类型

        3.可以实现参数测试

缺点:

相同数据量,json文件要远大于CSV文件

应用场景:数据量较少,数据组织格式复杂,需要进行参数测试

postman导入文件操作:

JSON文件编写及校验JSON在线解析格式化验证 - JSON.cn

6.3.3读取数据文件中数据

根据使用位置不同,有2中方法

(1)第一种:请求参数(请求行、请求头、请求体)中,使用数据文件的数据

        CSV文件:{{字段名}};JSON文件:{{键名}}

(2)第二种:代码test中使用数据文件中的数据

        使用postman内置的关键字data,索引字段名或键名

        CSV文件:data.字段名;JSON文件:data.键名

练习:批量查询手机号所属运营商,校验运营商的数据正确性

接口 地址: https://cx.shouji.360.cn/phonearea.php?number=手机号 

测试数据:

手机号:XXXXXX        运营商:联通

手机号:XXXXXX        运营商:移动

手机号:XXXXXX        运营商:电信

操作步骤:

步骤1:准备CSV测试数据文件
创建 test_data.csv 文件,内容如下:
mobile,expected_sp
13800138000,联通
13900139000,移动
13300133000,电信
步骤2:配置Postman请求及参数化
    GET请求URL:https://cx.shouji.360.cn/phonearea.php?number={{mobile}}
        使用 {{mobile}} 引用CSV中的手机号列。
    Headers:添加 Authorization 头(根据实际接口需求填写)。
步骤3:测试脚本-断言

// 验证状态码
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

/**var	函数作用域	在函数内声明时,整个函数内有效;在函数外声明则成为全局变量。
 const	块级作用域	仅在声明它的代码块(如 {}、if、for 等)内有效。
 **/
// 获取当前迭代的预期运营商名称
const expectedSP = pm.iterationData.get("expected_sp");

// 断言运营商匹配
pm.test("运营商校验通过", function () {
    const jsonData = pm.response.json();
    console.log("完整响应内容:", JSON.stringify(jsonData, null, 2)); // 打印响应数据
    pm.expect(jsonData.data.sp).to.eql(expectedSP); // 使用.eql进行深度比较
});

步骤4:运行参数化测试
导入CSV文件:

    点击集合右上角的 Run → 选择 Run Collection。

    在 Data 栏上传 test_data.csv 文件。

配置迭代次数:

    Postman会自动根据CSV行数执行多次请求(每行一次迭代)。

查看测试结果:

    所有迭代完成后,检查断言结果是否全部通过。

    失败排查:检查CSV中预期值与接口实际返回值是否一致。

JSON文件参数化:

// 验证状态码
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

/**var	函数作用域	在函数内声明时,整个函数内有效;在函数外声明则成为全局变量。
 const	块级作用域	仅在声明它的代码块(如 {}、if、for 等)内有效。
 **/
// 获取当前迭代的预期运营商名称
const expectedSP = pm.iterationData.get("expected_sp");
console.log("expectedSP:",expectedSP); // 打印expectedSP数据

// 断言运营商匹配
pm.test("运营商校验通过", function () {
    const jsonData = pm.response.json();
    console.log("完整响应内容:", JSON.stringify(jsonData, null, 2)); // 打印响应数据
    pm.expect(jsonData.data.sp).to.eql(expectedSP); // 使用.eql进行深度比较
});

6.4postman测试报告

目标:掌握使用newman生成测试报告的命令

6.4.1用例集的导入和导出

(1)用例集的导出:

(2)用例集的导入

6.4.2导出环境文件

        全局变量不需要导出只需要导出环境变量

路径:environment-选中环境(如测试环境)-export

6.4.3postman生成测试报告

6.4.3.1Newman生成测试报告的命令
 

newman run 测试集文件 -e 环境变量文件 -d 测试数据文件 -r htmlextra --reporter-htmlextra-export report.html

命令说明:
run xxx.json 执行测试集文件
-e source 环境变量文件
-d source 测试数据文件
-r htmlextra 生成测试报告类型
--reporter-htmlextra-export source 存放报告路径

(1)生成参数化测试报告:按手机号查询运营商(json数据文件)

        测试集文件.json 

        测试数据文件.json

newman  run 参数化练习-json数据文件.postman_collection.json -d mobile_test.json -r htmlextra --reporter-htmlextra-export 按手机号查询运营测试报告.html

        

(2)生成postman关联测试(环境变量)报告

        测试集文件.json 

        测试数据文件.json

        测试环境.postman_environment.json

newman run "postman关联测试(环境变量).postman_collection.json" -e 测试环境.postman_environment.json -d login.json -r htmlextra --reporter-htmlextra-export 关联测试环境 变量报告.html

(3)生成postman关联测试(全局变量)报告

         测试集文件.json 

newman run postman关联测试(全局变量).postman_collection.json -r htmlextra --reporter-htmlextra-export 天气关联测试全局报告.html

七、项目实战(测试项目-reqres.in

7.1搭建项目环境

7.1.1创建项目用例集

操作步骤:创建用例集代表项目-用例集创建文件代表模块-模块添加接口请求

7.1.2创建环境

备注:请求请求时遇到此报错

{

    "error": "Missing API key.",

    "how_to_get_one": "https://reqres.in/signup"

}

解决办法:请求设置的header中添加免费密钥的key:value

x-api-key: reqres-free-v1

7.1.3生成测试报告

newman run 测试项目-reqres.in.postman_collection.json -r htmlextra --reporter-htmlextra-export reqres项目登录注册用户查询测试报告.html

八、使用requests库实现接口测试

8.1requests库

8.1.1requests库的安装和简介

简介:requests库是python编写的基于urllib的HTTP库使用方便

安装:

(1)pip install requests

(2)pip install requests -i 国内镜像地址(常用国内镜像9源地址汇总-CSDN博客

pip install requests -i http://mirrors.aliyun.com/pypi/simple/

8.1.2requests发送请求的语法

(1)设置http请求语法

resp = requests.请求方法(url='',params={key:value},header={key:value},data={key:value},json={key:value},cookies='cookies的数据(如令牌'))

请求方法:
    get请求:get()
    post请求:post()
  
URL:待请求的URL-string(python中字符串用单引号,sql语句字符串必须用单引号)
params:查询参数--字典
header:请求头--字典
data:表单格式的请求体--字典
json:json格式的请求体
cookies:cookies数据--字符串string类型
resp:响应结果

练习:使用requests库访问百度http://www.baidu.com

"""
使用requests库访问百度http://www.baidu.com
"""
import requests
resp = requests.get(url="http://www.baidu.com")
# 将请求结果resp转成text打印出来
print(f"使用requests库访问百度结果是:{resp.text}")

练习:带json数据的post请求使用requests库完成Reqres成功登录。返回令牌数据

"""
    带json数据的post请求使用requests库完成Reqres成功登录。返回令牌数据
"""
import requests
# 发送post请求,指定url、请求头、请求体、获取响应结果
resp = requests.post(url="https://reqres.in/api/login",
                     headers={"content-typ":"application/json" , "x-api-key":"reqres-free-v1"},
                     data={"email": "eve.holt@reqres.in","password": "cityslicka"})
print(f"reqres的登录请求响应结果是:{resp.json()}")

8.2cookie和session

8.2.1cookie简介

工程师针对HTTP协议是无链接、无状态、设计的一种技术。可以在浏览器端存储用户的信息。

特性:

(1)cookie用于存储用户临时的不敏感信息

(2)cookie位于浏览器端。默认大小是4K(可以调整)

(3)cookie中的数据可以随意被访问,没有安全性可言

(4)cookie中存储的数据类型,受浏览器限制

8.2.2cookie+session认证方式

在计算机中,认证用户身份的方式有多种!

(1)token认证

(2)cookie+session认证
(1)浏览器第一次向服务器发送请求
例如:第一次访问百度(www.baidu.com)请求
服务器针对本次请求会产生session_id(key)和对应的session_对象(value)
    session_id和session_对象关系是key:value关系
    session_对象是有一定空间大小的可以存储东西
(2)服务器针对本次请求给出应答,回发响应时会携带着session_id给浏览器。
    浏览器会把服务器返回的session_id存储到cookie中。
(3)进一步访问服务器,例如第二次访问服务器发送登录请求
    登录请求会携带:用户名和密码,同时会携带cookie,cookie中存在着session_id
服务器收到登录请求后会进行校验:
    校验用户名和密码是否正确
        如果正确:服务器会生成一个代表这个用户身份的id比如user_id
            user_id代表这个用户已成功登录。
            user_id存放到session_对象空间内部中。
        如果没有user_id表示这个用户没有登录
(4)服务器针对登录请求给出回发响应:返回登录结果
    返回登录成功则跳转到对应的登录页面
(5)浏览器端再次访问服务器时只要携带cookie(带着session_id)
    本次携带的session_id对应的对象是user_id
服务器收到请求给出校验,校验请求携带的cookie中的session_id对应的session_对象中的数据有没有user_id
    session_对象中的数据有user_id:说明用户已登录----显示登录后的页

8.2.3session简介

session会话:通常出现在网络通信中,从客户端借助访问终端登录到服务器,直到退出登录所产生的通信数据,保存在会话中。

特性:

(1)session用于存储用户的信息

(2)session位于服务端。大小直接使用服务器存储空间

(3)session中的数据,不能随意被访问,安全性较高

(4)session中存储的数据类型受服务器影响,几乎能支持所有的数据类型。

session自动管理cookie

        因为cookie中的数据,都是session传递的。因此session可以直接自动管理cookie

import requests
# 1、创建一个session实例
session = requests.session()
# 2、使用session实例,调用get方法,发送获取验证码
# 2. 发送获取验证码的 GET 请求(假设验证码接口为 /api/unknown/2,仅作示例演示)
verify_url = "https://reqres.in/api/unknown/2"  # 此接口实际是获取资源列表,仅为演示
verify_response = session.get(verify_url)
print("验证码请求状态码:", verify_response.status_code)  # 预期返回 200
# 3、使用同一个session实例,调用post方法,发送登录请求
resp = session.post(url="https://reqres.in/api/login",
                     headers={"content-typ":"application/json" , "x-api-key":"reqres-free-v1"},
                     data={"email": "eve.holt@reqres.in","password": "cityslicka"})
print(f"reqres的登录请求响应结果是:{resp.json()}")

8.2.4获取响应结果指定内容

常用:

(1)获取URL:resp.url

(2)获取响应状态码:resp.status_code

(3)获取cookie:resp.cookies

(4)获取响应头:resp.headers

(5)获取响应体:

        文本格式:resp.text()

        json格式:resp.json()

8.3Unittest框架集成requests库

8.3.1unittest框架语法

unittest框架是开发人员用来实现单元测试的框架,可以在自动化测试执行时使用

使用unittest框架的好处:

(1)组织管理测试用例,方便管理、维护测试用例

(2)提供丰富的断言方法

(3)生成测试报告(需要插件HTMLTestReport)

8.3.2unittest框架回顾

8.3.2.1TestCase
# 1、导包
import unittest
# 2、定义测试类从TestCase类继承
class TestLogin(unittest.TestCase):
    pass
# 3、测试方法定义必须以test开头
class TestRegister(unittest.TestCase):
    def test01_register_successful(self):
        pass
    
8.3.2.2TestFixture

fixture称夹具:用来规范当前方法的测试用例执行的风格。

(1)方法级别的setUp(self)  tearDown(self)

        形参是self,每个普通方法执行之前/之后自动运行

(2)类级别的setUpClass(cls) tearDownClass(cls)

        形参是cls,在类内所有方法之前/之后运行一次

8.3.2.3TestSuite
# 测试套件testsuite
# 方法1
# 1、实例化测试集对象
import unittest
import testcase

suite = unittest.TestSuite()
# 2、添加指定类的全部测试方法
# suite.addTest(unittest.makeSuite(类名))
suite.addTest(unittest.makeSuite(testcase.TestRegister))
# 若makeSuite()被移除用不了就需要使用suite.addTest(unittest.TestLoader.loadTestsFromTestCase(类名))
#方法2
# TestSuite通过搜索创建测试集
# suite = unittest.TestLoader().discover("搜索目录","搜索文件名")
suite = unittest.TestLoader().discover("./","test*.py")
8.3.2.4TestRunner--HTMLTestReport生成测试报告
from testfixture import suite

runner = HTMLTestReport("./report.html",description="描述信息",title="报告标题")
runner.run(suite)
8.3.2.5unittest框架巩固练习

项目结构
your_project/
├── unittest_demo/
│   ├── __init__.py
│   ├── testcase.py
│   ├── testfixture.py
│   ├── testsuite.py
│   └── html_test_report.py

__init__.py中无代码:

  • 标识 unittest_demo为 Python 包

  • 允许使用 from unittest_demoimport ... 语法

  • 空文件即可,无需任何内容

testfixture.py中代码为

import unittest
"""
testfixture.py(需与测试类绑定)
"""
# 创建 Fixture 基类(供测试类继承)
class BaseTestFixture(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print("==== 所有测试方法之前运行 setUpsuite ====")

    @classmethod
    def tearDownClass(cls):
        print("==== 所有测试方法之后运行 tearDownsuite ====")

    def setUp(self):
        print("---- 每个测试方法前执行 setUp ----")

    def tearDown(self):
        print("---- 每个测试方法后执行 tearDown ----")

testcase.py中代码为

from unittest_demo.testfixture import BaseTestFixture
"""
继承 testfixture.py模块中的Fixture基类BaseTestFixture
"""
# 待测试的方法
def add(x,y):
    return x+y

# 封装测试类,从unittest.TestCase继承
class TestAdd(BaseTestFixture):
    # 自定义测试方法
    def test01_add(self):
        print("测试方法1")
        ret = add(10,20)
        # 断言测试结果
        self.assertEqual(30,ret)

    def test02_add(self):
        print("测试方法2")
        ret = add(100,200)
        # 断言测试结果
        self.assertEqual(300,ret)


testsuite.py中代码为

import unittest
from unittest_demo.testcase import TestAdd
"""
确保正确加载用例
"""
def create_suite():
    # 创建测试套件,suite实列
    suite = unittest.TestSuite()
    # 加载测试类中的所有用例,指定测试类,添加测试方法
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestAdd))
    return suite
if __name__ == "__main__":
    # 本地调试用
    runner = unittest.TextTestRunner()
    runner.run(create_suite())

html_test_report.py中代码为

from htmltestreport import HTMLTestReport
from unittest_demo.testsuite import create_suite

runner = HTMLTestReport("测试报告.html")
runner.run(create_suite())

程序运行结果为:

8.3.3unittest做接口测试

练习:登录成功

断言方法:

assertEqual(参1,参2):

        参1:预期结果。参2:实际结果

        成功:完全相等。断言通过。不报错!

        失败:报错!

assertIn(参1,参2): 

        参1:预期结果。参2:实际结果

        成功:实际结果中包含预期结果。断言通过。不报错!

        失败:报错!

import unittest
import requests

# 定义测试类
class TestReqresLogin(unittest.TestCase):
    # 添加测试方法:登录成功
    def test01_login_success(self):
        # 发送post请求,指定url、请求头、请求体、获取响应结果
        resp = requests.post(url="https://reqres.in/api/login",
                             headers={"content-typ": "application/json", "x-api-key": "reqres-free-v1"},
                             data={"email": "eve.holt@reqres.in", "password": "cityslicka"})
        # 打印响应结果
        print(f"reqres的登录请求响应结果是:{resp.json()}")
        # 断言响应状态码为200
        self.assertEqual(200,resp.status_code)
        # 断言token的值为QpwL5tke4Pnpja7X4
        self.assertEqual('QpwL5tke4Pnpja7X4',resp.json().get('token'))

    # 添加测试方法:查询单用户信息
    def test02_single_user(self):
        # 发送get请求,指定url
        resp = requests.get(url="https://reqres.in//api/users/2",
                            headers={"content-typ": "application/json", "x-api-key": "reqres-free-v1"})
        # 打印响应结果
        print(f"reqres的登录请求响应结果是:{resp.json()}")
        # 断言响应状态码为200
        self.assertEqual(200, resp.status_code)
        # 断言响应结果id为2
        self.assertEqual(2,resp.json()['data']['id'])
        # 断言响应结果email为janet.weaver@reqres.in
        self.assertEqual('janet.weaver@reqres.in',resp.json().get('data',{}).get('email'))
        # 断言响应结果first_name值为Janet
        self.assertIn('Janet',resp.json().get('data',{}).get('first_name'))

九、数据库工具类封装

9.1pymysql操作数据库

9.1.1数据库操作应用场景

校验数据:

        接口发送请求后明确会对数据库中的某个字段进行修改,但,响应结果中无该字段时

        如:删除员工接口

构造测试数据;

        测试数据使用一次就失效的

        如:添加员工使用的手机号

        测试前,无法保证测试数据是否存在

        如:查询员工接口,使用的员工ID

9.1.2安装pymysql

pip install PyMySQL -i https://mirrors.aliyun.com/pypi/simple/

9.1.3操作步骤

代码实现步骤:

(1)导包 import pymysql

(2)创建数据库连接conn = pymysql.connect()

(3)获取游标对象cursor = conn.Cursor()

(4)执行操作cursor.execute("sql语句")

        查询语句(select)

                处理结果集(提取数据fetch*)

        增删改语句(insert、update、delete)

                成功:提交事务conn.commit()

                失败:回滚事务conn.rollback()

(5)关闭游标cursor.close()

(6)关闭连接conn.close()

9.1.3.1事务的概念

(1)事务,是关系型数据库mysql的特有概念

(2)事务,可以看做一个虚拟的容器,在容器中存放一系列的数据库操作,看作一个整体。内部的所有操作,要么都一次性全部成功,只要有一个失败,就全部失败!

(5)关闭游标对象

(6)关闭数据库连接

9.1.4pymysql连接数据库

建立连接方式:

conn = pymysql.connect(host="",port="",user="",password="",database="",charset="")

# host 数据库所在主机IP地址
# port 数据库使用的端口
# user 数据库使用的用户名
# password 连接数据库使用的密码
# database 要连接的那个数据库的名字
# charset 字符集常用utf-8
# coon 连接数据库的对象

"""
conn = pymysql.connect(host="",port="",user="",
            password="",database="",charset="")
# host 数据库所在主机IP地址
# port 数据库使用的端口
# user 数据库使用的用户名
# password 连接数据库使用的密码
# database 要连接的那个数据库的名字
# charset 字符集常用utf-8
# coon 连接数据库的对象
"""
# (1)导包 import pymysql
import pymysql
# (2)创建数据库连接conn = pymysql.connect()
conn = pymysql.connect(host="192.168.10.5",port=3306,user="mysql",
                       password="Yuanft!123",database="mysql_db",charset="utf8")
# (3)获取游标对象cursor = conn.Cursor()
cursor = conn.cursor()
# (4)执行操作cursor.execute("sql语句")
cursor.execute("select version()")
# (5)获取结果
res = cursor.fetchone()
print("res=",res[0])
# (6)关闭游标cursor.close()
cursor.close()
# (7)关闭连接conn.close()
conn.close()

9.1.5游标简介

conn.curso()提取数据时,提取游标所在位置下一行,每提取一行数据,自动向下移动。

游标常用操作:

(1)修改游标位置:回零

例如:cursor.rownumber=0

(2)提取前2条数据

cursor.rownumber=0

res2=cursor.fetchmany(2)

(3)提取全部数据

cursor.rownumber=0

res3=cursor.fetchall()

(4)提取第3条和第4条数据

cursor.rownumber=2

res2=cursor.fetchmany(2)

9.1.6异常捕获

try:

        尝试执行的代码

except Exception as err:

        有错误出现时,执行的代码

finally:

        无论有没有错误,都会执行的代码

"""
conn = pymysql.connect(host="",port="",user="",
            password="",database="",charset="")
# host 数据库所在主机IP地址
# port 数据库使用的端口
# user 数据库使用的用户名
# password 连接数据库使用的密码
# database 要连接的那个数据库的名字
# charset 字符集常用utf-8
# coon 连接数据库的对象
"""
# (1)导包 import pymysql
import pymysql

# 定义全局变量,初值为None
conn = None
cursor = None

try:
    # (2)创建数据库连接conn = pymysql.connect()
    conn = pymysql.connect(host="192.168.151.69", port=3306, user="mysql",
                           password="Yuanft!123", database="mysql_db", charset="utf8")
    # (3)获取游标对象cursor = conn.Cursor()
    cursor = conn.cursor()
    # (4)执行操作cursor.execute("sql语句")
    cursor.execute("SELECT *FROM student WHERE age<22;")
    #cursor.execute("SELECT *FROM students WHERE age<22;")
    # (5)获取结果
    res = cursor.fetchone()
    print("res=", res)
except Exception as err:
    print("查询语句执行错误:",str(err))
finally:
    # (6)关闭游标cursor.close()
    cursor.close()
    # (7)关闭连接conn.close()
    conn.close()

例如insert插入数据:#需求:新增一条学生数据(studentNo:018 name:张三 sex:男 hometown:山西 age:18 class:8班 card:000000000000111111)

"""
conn = pymysql.connect(host="",port="",user="",
            password="",database="",charset="")
# host 数据库所在主机IP地址
# port 数据库使用的端口
# user 数据库使用的用户名
# password 连接数据库使用的密码
# database 要连接的那个数据库的名字
# charset 字符集常用utf-8
# coon 连接数据库的对象
"""
# 需求:新增一条学生数据(studentNo:018 name:张三 sex:男 hometown:山西 age:18 class:8班 card:000000000000111111)

# (1)导包 import pymysql
import pymysql

# 定义全局变量,初值为None
conn = None
cursor = None

try:
    # (2)创建数据库连接conn = pymysql.connect()
    conn = pymysql.connect(host="192.168.151.69", port=3306, user="mysql",
                           password="Yuanft!123", database="mysql_db", charset="utf8")
    # (3)获取游标对象cursor = conn.Cursor()
    cursor = conn.cursor()
    # (4)执行操作cursor.execute("sql语句")
    cursor.execute("insert into students(studentNo,name,sex,hometown,age,class,card) values('018','张三','男','山西',18,'8班','000000000000111111');")
    # 查看sql执行,影响多少行
    print("影响的行数:",conn.affected_rows())
    # (5)提交事务
    conn.commit()
except Exception as err:
    print("查询语句执行错误:",str(err))
    # 回滚事务
    conn.rollback()
finally:
    # (6)关闭游标cursor.close()
    cursor.close()
    # (7)关闭连接conn.close()
    conn.close()

9.2数据库工具类封装

9.2.1数据库工具类封装-架构

封装的目的:将反复用的东西做成一个通用的方法用于后续使用时调用。

将常用的数据库操作,封装到一个方法。后续再操作数据库时通过调用该方法来实现。提高代码的复用性。

# 封装数据库操作类
class DBUtil(object):
    #----继承object类
    # 连接数据库
    @classmethod
    def __get_conn(cls):
        # 方法前面加__变成私有方法,用户看不到但仍会执行
        pass
    # 关闭连接
    @classmethod
    # 类直接调用方法必须给方法添加@classmethod装饰器
    def __close_conn(cls):
        pass
    # 常用方法:查询一条数据
    @classmethod
    def select_one(cls,sql):
        pass
    @classmethod
    # 常用方法:增删改
    def uid_db(cls,sql):
        pass
    
if __name__ == '__main__':
    DBUtil.select_one("SELECT *FROM student WHERE age<22;")

9.2.2获取连接和关闭连接

# 封装数据库操作类
import pymysql

class DBUtil(object):
    # 添加类属性(类全局变量)
    conn = None
    """
    继承object类
    """
    @classmethod
    def __get_conn(cls):
        """
        连接数据库
        注:方法前面加__变成私有方法,用户看不到但仍会执行
        :return:
        """
        if cls.conn is None:
            # cls.conn使用cls索引类属性
            # 判断conn是否为空,如果conn连接为空则建立连接,否则直接返回已有的非空连接
            cls.conn = pymysql.connect(host="192.168.151.69", port=3306, user="mysql",
                                password="Yuanft!123", database="mysql_db", charset="utf8")
        # 这里return conn是因为__get_conn给别的方法调用,return conn后其他方法调用此方法时,就会把conn连接给调用的方法
        return cls.conn
    @classmethod
    # 类直接调用方法必须给方法添加@classmethod装饰器
    def __close_conn(cls):
        """
        关闭连接
        :return:
        """
        # 判断conn是否为空,不为空需要关闭
        if cls.conn is not None:
            cls.conn.close()
            cls.conn = None
    @classmethod
    def select_one(cls,sql):
        """
        常用方法:查询一条数据
        :param sql:
        :return:
        """
        # cursor不是整个类都通用只是查询这个方法时用所以不用设置为类属性变量,只需要放在try结构之外即可,res同理
        cursor = None
        res = None
        try:
            # (1)获取连接
            cls.conn = cls.__get_conn()
            # (2)获取游标,执行sql查询语句
            # 获取游标
            cursor = cls.conn.cursor()
            # 执行sql语句
            cursor.execute(sql)
            # 提取结果
            res = cursor.fetchone()
            print("res=", res)
        except Exception as err:
            print("查询语句执行错误:",str(err))
        finally:
            # 关闭游标
            cursor.close()
            # (3)关闭连接
            cls.__close_conn()
            # 因为这个是封装工具给其他方法用的所以要把结果res返回出去,谁调用return给谁,return放在最后
            return res
    @classmethod
    def uid_db(cls,sql):
        """
        常用方法:增删改
        :param sql:
        :return:
        """
        cursor = None
        try:
            #  (1)获取连接
            cls.conn = cls.__get_conn()
            #   获取游标
            cursor = cls.conn.cursor()
            # 执行sql语句
            cursor.execute(sql)
            print("影响的行数:", cls.conn.affected_rows())
            # 提交事务
            cls.conn.commit()
        except Exception as err:
            print("查询语句执行错误:", str(err))
            # 回滚事务
            cls.conn.rollback()
        finally:
            # 关闭游标
            cursor.close()
            # (3)关闭连接
            cls.__close_conn()

if __name__ == '__main__':
    DBUtil.select_one("SELECT *FROM students WHERE age<22;")
    DBUtil.uid_db("insert into students(studentNo,name,sex,hometown,age,class,card) values('020','张三','男','山西',28,'8班','000000000001111111');")
    DBUtil.uid_db("update students set sex='男' where studentNo='014'")

9.2.3调用封装好的数据库工具DBUtil

在pymysql_call_DBUtil.py中导入封装好的数据库工具DBUtil

"""
调用pymysql_class_encapsulation02.py中封装好的数据库操作工具
"""
from pymysql_test.pymysql_class_encapsulation02 import DBUtil

DBUtil.select_one("SELECT *FROM students WHERE age<22;")
DBUtil.uid_db("insert into students(studentNo,name,sex,hometown,age,class,card) values('021','张三','男','澳门',48,'1班','000000000111111111');")
DBUtil.uid_db("update students set sex='男' where studentNo='018'")

十、接口对象封装思想及实现

10.1接口对象的封装

需要解决的问题:

(1)代码冗余度高

(2)代码耦合度高

(3)代码维护成本高

解决的核心思想:代码分层

(1)分层思想:将普通方法实现的分为测试脚本层和接口对象层

(2)接口对象层

        对接口进行封装。封装好之后,给测试用例层调用!

        面向对象类封装实现

        注:接口请求要用到request

(3)测试用例层

        调用接口对象层封装的方法,拿到响应结果,断言进行接口测试

        借助unit test框架实现

10.1.1登录接口对象层

先写出普通实现方法再从普通方法中找哪些是变化的哪些是不变的

比如:登录注册的普通实现方法是

import unittest
import requests

class ReqresLogin(unittest.TestCase):
    # 测试 登录成功
    def test01_login_success(self):
        # 创建session实例
        session = requests.session()
        # 使用实例,调用post请求发送登录请求
        resp = session.post(url="https://reqres.in/api/login",
                             headers={"content-typ": "application/json", "x-api-key": "reqres-free-v1"},
                             data={"email": "eve.holt@reqres.in", "password": "cityslicka"})
        print(f"reqres的登录请求响应结果是:{resp.json()}")

        # 断言
        # 断言响应状态码为200
        self.assertEqual(200, resp.status_code)
        # 断言token的值为QpwL5tke4Pnpja7X4
        self.assertEqual('QpwL5tke4Pnpja7X4', resp.json().get('token'))
    def test02_register_unsuccessful(self):
        # 创建session实例
        session = requests.session()
        # 使用实例,调用post请求发送登录请求
        resp = session.post(url="https://reqres.in/api/register",
                            headers={"content-typ": "application/json", "x-api-key": "reqres-free-v1"},
                            data={"email": "sydney@fife"})
        print(f"reqres的注册请求响应结果是:{resp.json()}")

        # 断言
        # 断言响应状态码为400
        self.assertEqual(400, resp.status_code)
        # 断言error的值为Missing password
        self.assertEqual('Missing password',resp.json().get('error'))

封装思想:

(1)将动态变化的数据,设计到方法的参数

(2)将固定不变的直接写成方法实现

(3)将响应结果,通过返回值传出

如:登录接口普通方法中,session和data是变化的需要需要参数化,post和url、header是不变的直接写

-------封装后的登录接口方法,谁调用就给这个方法传递session和data参数

class ReqesLoginApi(object):
    # 发送登录请求
    def login(self,session,login_data):
        resp = session.post(url="https://reqres.in/api/login",
                     headers={"content-typ": "application/json", "x-api-key": "reqres-free-v1"},
                     data=login_data)
        return resp

----封装好的登录接口方法为了方便其他方法调用,创建方法的类的实例用类实例去调用。

给登录接口方法加上类装饰器,再把self参数改为cls参数

class ReqesLoginApi(object):
    # 发送登录请求
    @classmethod
    def login(cls,session,login_data):
        resp = session.post(url="https://reqres.in/api/login",
                     headers={"content-typ": "application/json", "x-api-key": "reqres-free-v1"},
                     data=login_data)
        return resp

-----编写登录测试用例去调用登录接口API

import unittest
import requests
from interface_encapsulation.reqes_login_api import ReqesLoginApi

class ReqresLogin(unittest.TestCase):
    # 测试 登录成功
    def test01_login_success(self):
        session = requests.Session()
        data = {"email": "eve.holt@reqres.in", "password": "cityslicka"}
        # 调用自己封装的接口,登录
        resp = ReqesLoginApi.login(session,data)
        print(f"reqres的登录请求响应结果是:{resp.json()}")

        # 断言
        # 断言响应状态码为200
        self.assertEqual(200, resp.status_code)
        # 断言token的值为QpwL5tke4Pnpja7X4
        self.assertEqual('QpwL5tke4Pnpja7X4', resp.json().get('token'))

----将断言部分单独封装成通用的方法

import unittest
class ReqesAssert(unittest.TestCase):
    def common_assert(self,resp,status_code,msg):
        self.assertEqual(status_code,resp.status_code,)
        self.assertIn(msg,resp.json().get('token'))

---------------------------------------------------------------------------------------------------------------------------------登录接口API、断言类、测试类三者调用继承关系是:

测试类的方法去调用登录接口API和断言类

断言类继承unittest.TestCase;测试类继承断言类

import requests
from interface_encapsulation.reqes_assert_common import ReqesAssert
from interface_encapsulation.reqes_login_api import ReqesLoginApi

class ReqresLogin(ReqesAssert):
    # 测试 登录成功
    def test01_login_success(self):
        session = requests.Session()
        data = {"email": "eve.holt@reqres.in", "password": "cityslicka"}
        # 调用自己封装的接口,登录
        resp = ReqesLoginApi.login(session,data)
        print(f"reqres的登录请求响应结果是:{resp.json()}")
        # 断言
        # 断言响应状态码为200,断言token的值为QpwL5tke4Pnpja7X4
        self.common_assert(resp,200,'QpwL5tke4Pnpja7X4')

---------------------------------------------------------------------------------------------------------------------------------

若测试类中有多个方法时,那么每一个方法都会调用session = requests.Session()

如果有N个测试方法就需要执行N次,因此可以使用Unittest框架中夹具fixture实现后,只需要执行一次就可以。

import requests
from interface_encapsulation.reqes_assert_common import ReqesAssert
from interface_encapsulation.reqes_login_api import ReqesLoginApi

class ReqresLogin(ReqesAssert):
    # 添加类属性,类属性在普通方法里可以用self索引,self.session
    session = None
    @classmethod
    def setUp(cls)->None:
        cls.session = requests.Session()
    # 测试 登录成功
    def test01_login_successful(self):
        data = {"email": "eve.holt@reqres.in", "password": "cityslicka"}
        # 调用自己封装的接口,登录
        resp = ReqesLoginApi.login(self.session,data)
        print(f"reqres的登录请求响应结果是:{resp.json()}")
        # 断言
        # 断言响应状态码为200,断言token的值为QpwL5tke4Pnpja7X4
        self.common_assert(resp,200,'QpwL5tke4Pnpja7X4')

    # 测试注册成功
    def test02_register_successful(self):
        data = {"email": "eve.holt@reqres.in","password": "pistol"}
        # 调用自己封装的接口,登录
        resp = ReqesLoginApi.login(self.session, data)
        print(f"reqres的注册请求响应结果是:{resp.json()}")
        # 断言
        # 断言响应状态码为200,断言token的值为QpwL5tke4Pnpja7X4
        self.common_assert(resp, 200, 'QpwL5tke4Pnpja7X4')


10.1.2接口参数化

通过上文写的测试类中的测试方法,只有测试数据不同,可以将数据提取出来用一个测试方法传递不同的数据。来展开接口测试

例如:把data数据参数化

使用parameterized参数模块实现,但此模块接收数据是元组。因此需要将json数据转换为元组存放到列表中。最后列表中数据全是元组。

参数化实现步骤:

(1)导包:parameterized

(2)在通用测试方法上一行添加@parameterized.expand()

(3)给expand()传入[(),()]

(4)修改通用测试方法添加形参,添加形参个数顺序与[{},{}]中{}内的所有key完全一一对应

(5)在通用测试方法内使用形参。

import requests
from interface_encapsulation.reqes_assert_common import ReqesAssert
from interface_encapsulation.reqes_login_api import ReqesLoginApi
from parameterized import parameterized
json_data = [
    {
        "req_body":{"email": "eve.holt@reqres.in", "password": "cityslicka"},
        "status_code":200,
        "msg":"QpwL5tke4Pnpja7X4"
    },
    {
        "req_body":{"email": "eve.holt@reqres.in", "password": "pistol"},
        "status_code":200,
        "msg":"QpwL5tke4Pnpja7X4"
    }
    ]
# 定义函数,读取[{},{}]转换成[(),()]
# 将json数据转换为元组列表
def read_json_data():
    """
    读取[{},{}]转换成[(),()]
    :return:
    """
    list_data = []
    for item in json_data:
        tmp = tuple(item.values())
        print(f"提取到的元素为:{tmp}")
        list_data.append(tmp)
    return list_data
class ReqresLogin(ReqesAssert):
    # 添加类属性,类属性在普通方法里可以用self索引,self.session
    session = None
    @classmethod
    def setUp(cls)->None:
        cls.session = requests.Session()
    # 测试接口
    @parameterized.expand(read_json_data())
    def test_api(self,req_body,status_code,msg):
        # 调用自己封装的接口,登录
        resp = ReqesLoginApi.login(self.session,req_body)
        print(f"reqres的登录请求响应结果是:{resp.json()}")
        # 断言
        # 断言响应状态码为200,断言token的值为QpwL5tke4Pnpja7X4
        self.common_assert(resp,status_code,msg)

十一、接口自动化测试框架

10.1接口测试框架思想

10.2接口自动化框架设计思路

10.2.1搭建基础框架

api/:存储接口对象层(自己封装的接口)

common/:存储通用的工具方法

data/:存储.json数据文件

log/:存储日志文件

report/:存储生成的HTML测试报告

scripts/:存储测试脚本层(unit test框架实现的测试类、测试方法)

config.py:存储项目的配置信息(全局变量)

run_suite.py:组装测试用例、生成测试报告的代码

10.2.1.1登录接口对象层

(1)在api/下创建reqes_login_api.py文件

(2)在文件内,封装ReqesLoginApi类,添加 login方法

class ReqesLoginApi(object):
    # 发送登录请求
    @classmethod
    def login(cls,session,login_data):
        resp = session.post(url="https://reqres.in/api/login",
                     headers={"content-typ": "application/json", "x-api-key": "reqres-free-v1"},
                     data=login_data)
        return resp
10.2.1.2登录接口测试用例层

(1)在scripts/下创建test_reqes_login.py文件

(2)在文件内,创建ReqresLogin类从ReqesAssert继承,ReqesAssert类从unittest.TestCase继承

(3)添加测试方法test01_login_successful,并实现

import requests
from interface_encapsulation.reqes_assert_common import ReqesAssert
from interface_encapsulation.reqes_login_api import ReqesLoginApi

class ReqresLogin(ReqesAssert):
    # 测试 登录成功
    def test01_login_successful(self):
        session = requests.Session()
        data = {"email": "eve.holt@reqres.in", "password": "cityslicka"}
        # 调用自己封装的接口,登录
        resp = ReqesLoginApi.login(session,data)
        print(f"reqres的登录请求响应结果是:{resp.json()}")
        # 断言
        # 断言响应状态码为200,断言token的值为QpwL5tke4Pnpja7X4
        self.common_assert(resp,200,'QpwL5tke4Pnpja7X4')

    # 测试注册成功
    def test02_register_successful(self):
        session = requests.Session()
        data = {"email": "eve.holt@reqres.in","password": "pistol"}
        # 调用自己封装的接口,登录
        resp = ReqesLoginApi.login(session, data)
        print(f"reqres的注册请求响应结果是:{resp.json()}")
        # 断言
        # 断言响应状态码为200,断言token的值为QpwL5tke4Pnpja7X4
        self.common_assert(resp, 200, 'QpwL5tke4Pnpja7X4')


10.2.1.3封装断言方法

(1)在common/下创建reqes_assert_common.py文件

(2)在文件内创建ReqesAssert类从unittest.TestCase继承

(3)添加函数common_assert

(4)在函数内实现通用的断言方法

(5)在测试方法中使用自己封装的断言函数,实现断言

import unittest
class ReqesAssert(unittest.TestCase):
    def common_assert(self,resp,status_code,msg):
        self.assertEqual(status_code,resp.status_code,)
        self.assertIn(msg,resp.json().get('token'))
10.2.1.4登录参数化

(1)组织数据文件

在data/创建.json文件存放数据

[
    {
        "req_body":
          {
            "email": "eve.holt@reqres.in",
            "password": "cityslicka"
          },
        "status_code":200,
        "msg":"QpwL5tke4Pnpja7X4"
    },
    {
        "req_body":
          {
            "email": "eve.holt@reqres.in",
            "password": "pistol"
          },
        "status_code":200,
        "msg":"QpwL5tke4Pnpja7X4"
    }
]

(2)读取数据文件

在common/下创建read_json_data.py文件

在文件内,定义函数,从json文件中读取数据,转换成元组列表,返回

# 定义函数,读取data/xxx.json文件
import json
def read_json_data():
    """
    读取[{},{}]转换成[(),()]
    :return:
    """
    with open("../data/reqes_data.json","r",encoding="utf-8") as f:
        json_data = json.load(f)
        print(json_data)

    list_data = []
    for item in json_data:
        tmp = tuple(item.values())
        print(f"提取到的元素为:{tmp}")
        list_data.append(tmp)
    return list_data

if __name__ == '__main__':
    read_json_data()

(3)使用parameterized实现参数化

在scripts/下创建test_reqes_params.py文件,将test_reqes_login.py文件中的方法参数化

import requests
from interface_encapsulation.reqes_assert_common import ReqesAssert
from interface_encapsulation.reqes_login_api import ReqesLoginApi
from parameterized import parameterized
from api_test_framework.common.read_json_data import read_json_data
class ReqresLogin(ReqesAssert):
    # 添加类属性,类属性在普通方法里可以用self索引,self.session
    session = None
    @classmethod
    def setUp(cls)->None:
        cls.session = requests.Session()
    # 测试接口
    @parameterized.expand(read_json_data())
    def test_api(self,req_body,status_code,msg):
        # 调用自己封装的接口,登录
        resp = ReqesLoginApi.login(self.session,req_body)
        print(f"reqres的登录请求响应结果是:{resp.json()}")
        # 断言
        # 断言响应状态码为200,断言token的值为QpwL5tke4Pnpja7X4
        self.common_assert(resp,status_code,msg)

十二、日志收集及全量字段校验

12.1日志收集

12.1.1日志代码分析

12.1.2日志代码实现步骤

步骤:
0.导包
1.创建日志器对象
2.设置日志打印级别
    logging.DEBUG调试级别
    logging.INFO 信息级别
    logging.WARNING 警告级别
    logging.CRITICAL 严重错误级别
3.创建处理器对象
    创建 输出到控制台 处理器对象
    创建 输出到日志文件 处理器对象
4.创建日志信息格式
5.将日志信息格式设置给处理器
    设置给 控制台处理器
    设置给 日志文件处理器
6.给日志器添加处理器
    给日志对象 添加 控制台处理器
    给日志对象 添加 日志文件处理器
7.打印日志

# 步骤:
# 0.导包
import logging
import time
from logging.handlers import TimedRotatingFileHandler
# 1.创建日志器对象
logger = logging.getLogger()
# 2.设置日志打印级别
logger.setLevel(logging.DEBUG)
#     logging.DEBUG调试级别
#     logging.INFO 信息级别
#     logging.WARNING 警告级别
#     logging.CRITICAL 严重错误级别
# 3.创建处理器对象
#     创建 输出到控制台 处理器对象
console = logging.StreamHandler()
#     创建 输出到日志文件 处理器对象
log_file = logging.handlers.TimedRotatingFileHandler('../log/a.log',when='midnight',interval=1,backupCount=3,encoding='utf-8')
"""
when 字符串,指定日志切分间隔时间的单位。midnight表示凌晨12点
interval 是间隔时间单位的个数,指等待多少个when后继续进行日志记录
backup count 是保留日志文件的个数
"""
# 4.创建日志信息格式
fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
formatter = logging.Formatter(fmt)
# 5.将日志信息格式设置给处理器
#     设置给 控制台处理器
console.setFormatter(formatter)
#     设置给 日志文件处理器
log_file.setFormatter(formatter)
# 6.给日志器添加处理器
#     给日志对象 添加 控制台处理器
logger.addHandler(console)
#     给日志对象 添加 日志文件处理器
logger.addHandler(log_file)
# 7.打印日志
while True:
    # logging.debug('我是一个调试级别日志')
    # logging.info('我是一个信息级别日志')
    # logging.warning('我是一个警告级别日志')
    logging.error('我是一个错误级别日志')
    #logging.critical('我是一个严重错误级别日志')
    time.sleep(1)

12.1.3将日志打印代码封装成函数进行调用

import logging
from logging.handlers import TimedRotatingFileHandler
def init_log_config(filename,when='midnight',interval=1,backup_count=7):
    """
    功能:初始化日志配置函数
    :param filename: 日志文件名
    :param when: 设定日志切分的间隔时间单位
    :param interval: 间隔时间单位的个数,指等待多少个when后继续进行日志记录
    :param backup_count: 保留日志文件的个数
    :return: 
    """
    # 1.创建日志器对象
    logger = logging.getLogger()
    # 2.设置日志打印级别
    logger.setLevel(logging.DEBUG)
    #     logging.DEBUG调试级别
    #     logging.INFO 信息级别
    #     logging.WARNING 警告级别
    #     logging.CRITICAL 严重错误级别
    # 3.创建处理器对象
    #     创建 输出到控制台 处理器对象
    console = logging.StreamHandler()
    #     创建 输出到日志文件 处理器对象
    log_file = logging.handlers.TimedRotatingFileHandler(filename, when, interval, backup_count,
                                                         encoding='utf-8')
    # 4.创建日志信息格式
    fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
    formatter = logging.Formatter(fmt)
    # 5.将日志信息格式设置给处理器
    #     设置给 控制台处理器
    console.setFormatter(formatter)
    #     设置给 日志文件处理器
    log_file.setFormatter(formatter)
    # 6.给日志器添加处理器
    #     给日志对象 添加 控制台处理器
    logger.addHandler(console)
    #     给日志对象 添加 日志文件处理器
    logger.addHandler(log_file)
    
if __name__ == '__main__':
    # 初始化日志
    init_log_config('../log/b.log',interval=3,backup_count=5)
    # 打印输出日志信息
    logging.debug('xxxx-debug 日志信息')

其他方法调用日志打印函数

(1)导入日志方法模块

(2)初始化日志

(3)使用日志打印

12.2JSON Schema

12.2.1全量字段校验

概念:校验接口返回响应结果的全部字段(更进一步的校验)

校验内容:

(1)字段值

(2)字段名或字段类型

校验流程:

(1)定义json语法校验格式

(2)比对接口实际响应数据是否符合json校验格式

安装jsonschema

pip install jsonschema -i https://pypi.tuna.tsinghua.edu.cn/simple

实例:

json数据----待校验的数据

{
  "success": true,
  "code": 1000,
  "message": "成功"
}

校验规则描述

(1)整个json数据是一个对象

(2)包含success、code、message字段,并且是必须存在的字段

(3)success字段为布尔类型

code为整数

message为字符串

校验规则语法

{
  "type": "object",
  "properties": {
    "success": {"type": "boolean"},
    "code": {"type": "integer"},
    "message": {"type": "string"}
  },
  "required": ["success","code","message"]
}

12.2.2校验方式

在线工具校验:JSON Schema Validator - Newtonsoft

python代码校验

实现步骤:

(1)导包import jsonschema

(2)定义jsonschema格式数据校验规则

(3)调用jsonschema.validate(instance="json数据",schema="jsonschema规则")

查验校验结果:

校验通过:返回None

校验失败

schema规则错误,返回SchemaError

json数据错误,返回ValidationError

import logging
from api_test_framework.common.logging_use import init_log_config
# 1.导包
import jsonschema
# 初始化日志
init_log_config("../log/reqes_login.log", interval=3, backup_count=5)
# 2.创建校验规则
jsonschema_rule = {
  "type": "object",
  "properties": {
    "success": {"type": "boolean"},
    "code": {"type": "integer"},
    "message": {"type": "string"}
  },
  "required": ["success","code","message"]
}
# 3.准备待校验数据
data = {
  "success": True,
  "code": 1000,
  "message": "成功"
}
# 4.调用 validate 方法,实现校验
result1=jsonschema.validate(instance=data,schema=jsonschema_rule)
logging.info(f"校验通过结果返回result1={result1}")

2025-05-17 10:49:38,443 [INFO] [json_schema.py(<module>:25)] - 校验通过结果返回result1=None

12.2.3jsonschema语法

jsonschema重点关键字
关键字 描述
type 表示待校验元素的类型
properties 定义待校验的json对象中,各个key-value对中value的限制条件
required 定义待校验的json对象中,必须存在的key
const json元素必须等于指定的内容
pattern 使用正则表达式约束字符串类型数据
12.2.3.1type关键字

integer----整数

string----字符串

object----对象

array----数组;python中叫列表list

number----整数/小数

null----空值;python中叫None

Boolean----布尔值

语法:

{

        "type":"数据类型"

}

12.2.3.2properties关键字

说明:是type关键字的辅助。用于type的值为object的场景

作用:指定对象中每个字段的校验规则。可以嵌套使用

语法:

{
  "type": "object",
  "properties": {
    "字段名1": {规则},
    "code": {"type": "integer"}
  }
12.2.3.3required关键字

作用:校验对象中必须存在的字段。字段名必须是字符串,且唯一

语法:

{   

        "required": ["字段名1","字段名2",......]
}
12.2.3.4const关键字

作用:校验字段值是一个固定值。

语法:

{

        "const":具体值

}

{
  "type": "object",
  "properties": {
    "success": {"const": True},
    "code": {"const": 1000},
    "message": {"const": "成功"}
  }
12.2.3.5pattern关键字

作用:指定正则表达式,对字符串进行模糊匹配

基础正则举例:响应结果 "hi,hello,world"

包含字符串:hell

以hell字符串开头,如 ^hell

以hell字符串结尾,如 hell$

匹配0到9任意一个数字,如[0-9]

匹配a到z任意一个小写字母,如[a-z]

匹配任意一个字符[abcde12345]

指定匹配次数,[0-9]{11}匹配11位数字

12.3日志在项目中的使用

自定义的日志打印方法初始化位置放在项目入口

整个项目的入口:所有的文件都可以用它运行起来

例如:我的项目入口就是run_suite.py

所以日志的初始化函数在run_suite.py中进行调用就可以

"""
生成测试报告的步骤:
1.创建测试套件实例 suite
2.添加 测试类
3.创建HTMLTestReport 类实例 runner
4.runner调用run(),传入suite
"""
import unittest
from api_test_framework.scripts.test_reqes_params import ReqresLoginParams
from htmltestreport import HTMLTestReport
from api_test_framework.common.logging_use import init_log_config
# 初始化日志的配置信息
init_log_config(
        filename='reqes.log',
        when='D',  # 每天轮换
        backup_count=5  # 保留5天日志
    )
# 创建测试套件,suite实列
suite = unittest.TestSuite()
# 添加 测试类
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(ReqresLoginParams))
# 创建HTMLTestReport 类实例 runner
runner = HTMLTestReport("./report/reqes项目API测试报告.html",description="reqes项目对登录注册API进行测试",title="reqes项目API测试报告")
# runner调用run(),传入suite
runner.run(suite)

十三、持续集成之Gitee

相关工具:

本地代码管理 git

远程代码管理:gitee(国内)、GitHub(国外)、gitlib(公司私有服务器)

持续集成:Jenkins

git与gitee安装:见第三章3.2.3和3.2.4

13.1git与gitee工作原理

13.2pycharm集成Gitee

13.2.1将Gitee新仓库,check out到pycharm中

(1)打开gitee中新建的仓库。点击 克隆下载 按钮,复制仓库 url地址

(2)在pycharm中,点击菜单中的VCS选项。选择 get from version control...(从版本控制中获取...)

----只有没有被GIT管理的项目才会在pycharm菜单中有此按钮选项

(3)将gitee中复制的URL地址,粘入URL中。点击clone克隆按钮

13.2.2推送pycharm新项目到gitee远程仓库

(1)pycharm下准备任意一个未被git管理(菜单中有VCS)的项目。将其推送到gitee保存

(2)点击VCS。选择share project on gitee

(3)在弹出页面,授予pycharm权限,登录gitee。输入注册gitee用的账号和密码

注:这种方式只会上传本地只有一个项目情况,如果有多个项目只会上传第一个。其余最好移除

-----建议使用GIT命令托管(Git管控本地项目及远程仓库代码(最详细基础篇)_git关联本地目录和远程仓库的命令-CSDN博客

13.2.3将pycharm代码push到gitee远程仓库

(1)确保,即将push的代码,是git项目

(2)确保本地有代码新增后推送

(3)先add添加-commit-push

注:推送前先拉取:养成每次 git push 前先 git pull 的习惯

13.2.4将gitee仓库的新代码pull到pycharm

pycharm中直接git拉取

13.3生成 SSH 密钥的步骤

(1)打开git bash

(2)重新运行密钥生成命令

ssh-keygen -t ed25519 -C "邮箱"

当看到提示时直接按 Enter(不要输入任何内容),一般连续3次enter

(3)启动 SSH 代理

ssh-add ~/.ssh/id_ed25519

(4)查看公钥

cat ~/.ssh/id_ed25519.pub

十四、持续集成之Jenkins

14.1离线安装Jenkins及配置

前提:已安装jdk且配置好环境变量

Jenkins.msi安装教程:https://www.jenkins.io/doc/book/installing/windows/

因我的JDK是1.8所以我需要安装2.346.1,但这个版本官方没有上传MSI文件所以接下来采用Jenkins.war包方式安装

下载路径:War Jenkins Packages

14.2在Windows上使用Tomcat部署Jenkins.war

14.2.1安装JDK并配置环境变量-略

14.2.2安装Tomcat

(1)版本选择:JDK是1.8所以tomcat9.0.105

(2)下载tomcatApache Tomcat® - Apache Tomcat 9 Software Downloads

(3)解压Tomcat:将压缩包解压到目标目录

(4)配置环境变量-系统变量

(5)编辑 Path 变量,添加 %CATALINA_HOME%\bin

(6)配置Tomcat用户权限

        打开文件 %CATALINA_HOME%\conf\tomcat-users.xml

   在 <tomcat-users> 标签内添加以下内容(用于管理Tomcat后台)

<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="admin" password="admin" roles="manager-gui,admin-gui"/>

(7)8080容易出现端口冲突:修改Tomcat端口为8081,编辑 %CATALINA_HOME%\conf\server.xml 中的 <Connector port="8080" ... />

(8)启动Tomcat

        运行 %CATALINA_HOME%\bin\startup.bat 启动Tomcat

        访问 http://localhost:8081,确认Tomcat正常运行

14.2.3部署Jenkins.war

(1)下载Jenkins.war

         下载路径:War Jenkins Packages

(2)部署到Tomcat

        将 jenkins.war 复制到 %CATALINA_HOME%\webapps 目录。

        Tomcat会自动解压并部署应用。

(3)访问Jenkins:打开 http://localhost:8081/jenkins

14.2.4初始化Jenkins

(1)获取初始密码:

        打开文件 %USERPROFILE%\.jenkins\secrets\initialAdminPassword

        复制密码并粘贴到Jenkins页面。

(2)进入.jenkins目录下打开hudson.model.UpdateCenter.xml将URL修改为国内镜像

原URL是官网访问国外服务器下载插件很慢

<?xml version='1.1' encoding='UTF-8'?>
<sites>
  <site>
    <id>default</id>
    原:<url>https://updates.jenkins.io/update-center.json</url>
	<url>https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json</url>
  </site>
</sites>

(3)安装插件:选择“安装推荐的插件”。

14.3windows通过Jenkins.msi安装Jenkins

14.3.1下载Jenkins.msi双击

端口修改为非8080


浏览器打开http://localhost:8088

网络不好跳过插件安装先:注:最好选择默认推荐插件,后续自己离线安装一定要安装兼容的版本

创建第一个管理员账户

14.3.2配置Jenkins时已注册账户,退出后再次不能登录问题

(1)将.jenkins目录下的config.xml中 <useSecurity>true</useSecurity>修改为<useSecurity>false</useSecurity>

(2)重新启动Jenkins服务,刷新浏览器进入

(3)打开Manage Jenkins   

(4)进入configure global security(配置全局安全性)

Security Realm 部分:

Jenkins’ own user database(Jenkins 内置用户数据库)

勾选 Allow users to sign up(允许用户注册,完成注册后建议关闭此选项)

(5)注册管理员用户

14.3.3汉化----离线安装(网络好可在线安装)

(1)版本选择

jenkins-plugins-localization-support-1.1安装包下载_开源镜像站-阿里云

14.4离线安装常用插件

14.4.1安装htmlpublisher插件

14.4.2配置Jenkins系统邮箱

14.4.2.1获取pop3/smtp授权码

(1)登录163邮箱,点击设置--pop3/smtp

14.4.2.2完成3处配置

(1)manage Jenkins-configure system进入系统配置页面

Jenkins Location-系统管理员邮件地址-写自己邮箱

(2)若没有Email Extension Plugin此插件需要先安装

Email Extension Template | Jenkins plugin

jenkins-plugin-manager:Release 2.8.0 · jenkinsci/plugin-installation-manager-tool · GitHub

14.5安装推荐插件时:安装过程中出现一个错误: No such plugin: cloudbees-folder

14.5.1查询Jenkins兼容的cloudbees-folder版本

14.5.2下载Jenkins兼容的cloudbees-folder版本

下载地址:Index of /jenkins/plugins/cloudbees-folder/6.740.ve4f4ffa_dea_54/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror​​​​​​

14.5.3直接访问, 默认:http:// IP:port/restart

http://localhost:8088/restart

14.5.4上传插件并返回插件安装推荐界面

跳过后,可以访问Jenkins的界面
点击【系统管理】–【管理插件】–【高级】–【上传插件】,手动安装下载好的插件

14.5.5插件安装完后继续配置Jenkins邮箱服务


(manage Jenkins -System Configuration-System
系统管理员邮件地址:添加自己需要发送的邮箱
Extended E-mail Notification
SMTP server--smtp.163.com
SMTP Port--465
点击高级
Credentials---增加Jenkins的用户
SMTP Port--25发送不成功时就需要勾选use ssl发送
Default user e-mail suffix---@163.com
Charset---UTF-8
Default Content Type---HTML(text/html)
Default Recipients---接收人的邮箱
Default Subject默认标题
Default Content默认内容
邮件通知-SMTP服务器smtp.163.com
用户默认邮件后缀@163.com
点击高级
勾选使用smtp认证
用户名:管理员邮箱
密码:邮箱第三方凭证
勾选使用ssl协议
smpt端口:465
测试邮箱发送是否成功

如上Jenkins邮箱部署完成

十五、基于Ubuntu 22.04 LTS 上安装 Jenkins

15.1更新系统包

sudo apt update

15.2安装Java(Jenkins依赖)

15.2.1确定需要安装的Jenkins和Java版本

Jenkins需要Java 11或17:

sudo apt install openjdk-17-jdk -y

验证安装成功:java -version

15.3添加Jenkins仓库

curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]" \
  https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt update

如果提示GPG错误没有公钥

解决办法:手动添加公钥:公钥替换成你安装提示缺失的公钥

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 5BA31D57EF5975CA

15.3.1重新添加Jenkins 的 Debian 包仓库

Debian Jenkins 软件包

 sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
    https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key

15.3.2然后添加一个 Jenkins apt 存储库条目

echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]" \
    https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
    /etc/apt/sources.list.d/jenkins.list > /dev/null

15.3.3更新本地包索引,然后最后安装 Jenkins

sudo apt-get update
  sudo apt-get install jenkins

15.3.4启动Jenkins服务

sudo service jenkins start

15.3.5检查服务状态

(1) sudo service jenkins status

 (2) 查看Jenkins进程 ps -ef |grep jenkins

(3)为防止8080端口使用冲突,可考虑修改端口

打开Jenkins配置文件

sudo nano /etc/default/jenkins

按 Ctrl + O 保存;按 Enter 确认;按 Ctrl + X 退出

或者直接按 Ctrl + X 输入Y再按 Enter 确

15.3.6获取初始管理员密码

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

复制输出的密码

15.3.7访问Jenkins

在 Windows浏览器 中打开:http://服务器IP:8080

http://192.168.10.6:8080

15.4安装插件

15.4.1点击“安装推荐的插件”框,安装过程将立即开始。等待推荐插件安装完成(安装失败,点击重试继续安装)。

15.4.2创建管理员用户

插件安装完毕,系统将提示设置第一个管理员用户。

15.4.3配置邮箱服务

(1)Manage Jenkins-系统配置-Jenkins Location-系统管理员邮件地址?

(2)Manage Jenkins-系统配置-Extended E-mail Notification-点击高级-添加Jenkins凭据提供者

(3) Jenkins 凭据提供者: Jenkins-用户名填邮箱,密码填授权码

(4) 邮件通知-高级-Jenkins-用户名填邮箱,密码填授权码---第(3)步添加了这步就不用添加

15.5postman集成Jenkins

15.5.1postman持续集成-准备工作

(1)打开已完成并测试无误的postman项目脚本,再次执行测试

(2)导出(测试用例集、环境变量两个文件),不支持中文全部改成英文

(3)文件所在目录地址栏输入cmd打开终端,注意用绝对路径测试。方便使用Jenkins

(4)执行无误,查看生成的测试报告文件

newman run D:\study\postmanwork\postman_jenkins\login.postman_collection.json -e D:\study\postmanwork\postman_jenkins\login.postman_environment.json -r htmlextra --reporter-htmlextra-export report.html

15.5.2使用Jenkins管理-手动构建

操作步骤:

(1)打开Jenkins首页,点击 新建item 创建一个任务

(2)输入任务名,选择freestyle project 点击确定

(3)回主页。可看到,多出任务PostmanJenkinsTest。点击任务名称,配置 可以继续刚才的配置

(4)跳至 构建 标签。(General、源码管理、Triggers、Environment)四个标签先跳过

        点击 增加构建步骤。选择 执行 Windows 批处理命令

        将cmd终端测试无误的命令,粘贴到 命令 编辑框中

(5)构建后操作 标签

        点击 增加构建后操作步骤 选择Publish HTML report -点击新增----如果没有此插件则需要先安装。     ​​​​​​

        将index page后的值改为report.html 名称应与上面命令中生成的测试报告名称一致

        说明:

        因为生成报告时,没有指定目录。所以上面HTML directory to archive是空的,如果指定报告生成到其他位置。要配置HTML directory to archive的值

        report title是生成的报告标题。可以修改

(6)点击应用保存

(7)在自动跳至的页面中,点击build now 可在下面build history中看到构建正在进行

        点击# 数字 点击控制台输出 查看执行命令

(8)完成后,左侧菜单中多出report777菜单栏,点击可查看测试报告

-------------点击构建时遇到如下报错

Started by user yuanft104417
Running as SYSTEM
Building in workspace /var/lib/jenkins/workspace/PostmanJenkinsTest
[PostmanJenkinsTest] $ cmd /c call /tmp/jenkins9956400003086679706.bat
FATAL: command execution failed
java.io.IOException: error=2, No such file or directory
	at java.base/java.lang.ProcessImpl.forkAndExec(Native Method)
	at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:314)
	at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:244)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1110)
Caused: java.io.IOException: Cannot run program "cmd" (in directory "/var/lib/jenkins/workspace/PostmanJenkinsTest"): error=2, No such file or directory
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1143)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1073)
	at hudson.Proc$LocalProc.<init>(Proc.java:252)
	at hudson.Proc$LocalProc.<init>(Proc.java:221)
	at hudson.Launcher$LocalLauncher.launch(Launcher.java:995)
	at hudson.Launcher$ProcStarter.start(Launcher.java:507)
	at hudson.tasks.CommandInterpreter.perform(CommandInterpreter.java:144)
	at hudson.tasks.CommandInterpreter.perform(CommandInterpreter.java:92)
	at hudson.tasks.BuildStepMonitor$1.perform(BuildStepMonitor.java:20)
	at hudson.model.AbstractBuild$AbstractBuildExecution.perform(AbstractBuild.java:818)
	at hudson.model.Build$BuildExecution.build(Build.java:199)
	at hudson.model.Build$BuildExecution.doRun(Build.java:164)
	at hudson.model.AbstractBuild$AbstractBuildExecution.run(AbstractBuild.java:527)
	at hudson.model.Run.execute(Run.java:1840)
	at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:44)
	at hudson.model.ResourceController.execute(ResourceController.java:101)
	at hudson.model.Executor.run(Executor.java:446)
Build step 'Execute Windows batch command' marked build as failure
[htmlpublisher] Archiving HTML reports...
[htmlpublisher] Archiving at PROJECT level /var/lib/jenkins/workspace/PostmanJenkinsTest to /var/lib/jenkins/jobs/PostmanJenkinsTest/htmlreports/HTML_20Report777
[htmlpublisher] Copying recursive using current thread
ERROR: Directory '/var/lib/jenkins/workspace/PostmanJenkinsTest' exists but failed copying to '/var/lib/jenkins/jobs/PostmanJenkinsTest/htmlreports/HTML_20Report777'.
Finished: FAILURE

报错原因:我的Jenkins服务是配置在Linux环境中,而我构建任务时命令是Windows中的cmd命令。且我的脚本是放在Windows目录下

错误分析

log
FATAL: command execution failed
java.io.IOException: Cannot run program "cmd" (in directory "...") error=2, No such file or directory
  • cmd 是 Windows 系统的命令处理器

  • Jenkins 服务器运行在 Linux 系统(路径 /var/lib/jenkins 是 Linux 目录结构)

  • Linux 系统无法识别/执行 Windows 的 cmd 命令

解决办法:

改用 Shell 脚本

适用场景:测试脚本可在 Linux 运行(如使用 Newman 执行 Postman

修改构建步骤:

        删除当前的 Execute Windows batch command

  添加 Execute shell 构建步

安装 Node.js(如果尚未安装)
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs

# 安装 Newman
npm install -g newman

15.2.3准备文件上传到 Jenkins

前提:已经注册了自己的gitlab或github账户

(1)gitlab创建一个空项目

(2)将项目克隆到本地做本地仓库

git clone git@jihulab.com:yuanfutao-group/postman_jenkins.git(换成自己的项目链接)

(3)将文件放入本地仓库

(4)进入postman_jenkins且设置为main分支

分支设置:git checkout main

(5)添加所有文件到暂存区,使用git status查看代码状态,修改了什么

git add .

(6) 提交文件到本地仓库

git commit -m "添加Postman测试脚本和环境文件"

(7)推送代码到远程仓库

git push

15.2.4在Linux终端测试Newman run命令可用

(1)将本地的postman脚本cp到Linux中(目录任意也可)

进入本地目录:cd /mnt/d/study/gitwork/postman_jenkins

(2)Linux终端进入要放置脚本的目录执行命令 

cp /mnt/d/study/gitwork/postman_jenkins/login.postman_collection.json ./

cp /mnt/d/study/gitwork/postman_jenkins/login.postman_environment.json ./

(3)使用绝对路径执行命令生成测试报告

newman run /var/lib/jenkins/login.postman_collection.json -e /var/lib/jenkins/login.postman_environment.json --reporters cli,html --reporter-html-export "/var/lib/jenkins/newman-report.html" --color off

# 验证报告
ls -lh /var/lib/jenkins/newman-report.html

(4)使用时间戳命令测试报告

newman run /var/lib/jenkins/login.postman_collection.json -e /var/lib/jenkins/login.postman_environment.json --reporters cli,html --reporter-html-export "/var/lib/jenkins/newman-report-$(date +%Y%m%d_%H%M%S).html" --color off

# 验证报告
sudo find /var/lib/jenkins/ -name "newman-report-*.html" -type f 2>/dev/null

十六、基于Ubuntu使用docker-compose部署Jenkins服务

16.1Linux版docker V28下载

16.1.1下载路径

docker-ce-linux-static-stable-x86_64安装包下载_开源镜像站-阿里云

16.1.2docker-compose版本下载

Release v2.27.1 · docker/compose · GitHub

电脑是inter 64处理器:docker-compose-linux-x86_64

电脑是ARM 64处理器:docker-compose-linux-aarch64

docker-28.1.1下载docker-compose v2.7.1

对于 Docker 28.1.1,安装 Docker Compose V2.27.1 可确保最佳兼容性和最新功能支持。

16.1.3 移除旧版

# 移除二进制文件
sudo rm /usr/local/bin/docker-compose
# 移除插件
rm ~/.docker/cli-plugins/docker-compose
# 重命名文件(去掉版本号)

mv docker-compose-linux-x86_64_V2.7.1 docker-compose

# 设置可执行权限

chmod +x docker-compose

# 系统级安装

sudo mv docker-compose /usr/local/bin/

# 配置环境变量(根据安装位置)

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc

# 验证安装

docker-compose --version
# 应输出:Docker Compose version v2.7.1

16.2docker-compose部署Jenkins

16.2.1创建 Jenkins 部署目录

mkdir jenkins-docker && cd jenkins-docker

16.2.3创建 docker-compose.yml 文件

nano docker-compose.yml

添加以下内容:

version: '3.8'

services:
  jenkins:
    image: jenkins/jenkins:lts-jdk17  # 官方LTS版本带JDK17
    container_name: jenkins
    user: root  # 避免权限问题
    ports:
      - "8080:8080"   # Web界面端口
      - "50000:50000"  # Agent通信端口
    volumes:
      - ./jenkins_data:/var/jenkins_home  # 配置数据持久化
      - /var/run/docker.sock:/var/run/docker.sock  # 允许使用宿主机Docker
      - ./casc_configs:/var/jenkins_home/casc_configs  # 配置即代码
    environment:
      - TZ=Asia/Shanghai  # 时区设置
      - JAVA_OPTS=-Duser.timezone=Asia/Shanghai -Xmx1024m -Djenkins.install.runSetupWizard=false
      - CASC_JENKINS_CONFIG=/var/jenkins_home/casc_configs  # 配置即代码路径
    restart: unless-stopped  # 自动重启策略
    networks:
      - jenkins-net

networks:
  jenkins-net:
    driver: bridge

16.2.4 创建必要的目录和配置文件

# 创建数据目录
mkdir -p jenkins_data casc_configs

# 创建基础配置即代码文件
echo 'jenkins:
  securityRealm: local
  authorizationStrategy: loggedInUsersCanDoAnything
  numExecutors: 4
  mode: NORMAL
  crumbIssuer: standard
  systemMessage: "Jenkins as Code 部署完成"' > casc_configs/jenkins.yaml

# 设置目录权限
sudo chown -R 1000:1000 jenkins_data

16.2.5启动 Jenkins 容器

docker compose up -d

此时出现报错:

# docker compose up -d报错信息
WARN[0000] /home/yft/jenkins-docker/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion 
unable to get image 'jenkins/jenkins:lts-jdk17': permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.49/images/jenkins/jenkins:lts-jdk17/json": dial unix /var/run/docker.sock: connect: permission denied


# 这个错误表明您的用户没有权限访问 Docker 守护进程的 Unix 套接字 (/var/run/docker.sock)

解决办法:将docker-compose.yml配置文件中version: '3.8'删除

出现如下报错

docker compose up -d
[+] Running 1/1
 ✘ jenkins Error Get "https://docker.mirrors.ustc.edu.cn/v2/": dial tcp: lookup docker.mirrors.ustc.edu.cn on 127.0.0.53:53: no such host                                       0.1s 
Error response from daemon: Get "https://docker.mirrors.ustc.edu.cn/v2/": dial tcp: lookup docker.mirrors.ustc.edu.cn on 127.0.0.53:53: no such host

解决办法:编辑 daemon.json 更换镜像加速器为如下

# sudo nano /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://hub-mirror.c.163.com",
    "https://registry.docker-cn.com",
    "https://mirror.baidubce.com"
  ]

出现如下报错

docker compose up -d
[+] Running 1/1
 ✘ jenkins Error Get "https://docker.mirrors.ustc.edu.cn/v2/": dial tcp: lookup docker.mirrors.ustc.edu.cn on 127.0.0.53:53: no such host                                       0.1s 
Error response from daemon: Get "https://docker.mirrors.ustc.edu.cn/v2/": dial tcp: lookup docker.mirrors.ustc.edu.cn on 127.0.0.53:53: no such host

解决办法:docker.io/jenkins/jenkins:lts-jdk17 - 镜像下载 | docker.io

 docker-compose.yml配置文件中Jenkins 镜像下载路径

image: jenkins/jenkins:lts-jdk17替换为国内镜像swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:lts-jdk17

修改后docker-compose.yml配置文件如下

services:
  jenkins:
    image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:lts-jdk17
    container_name: jenkins
    user: root  # 避免权限问题
    ports:
      - "8080:8080"   # Web界面端口
      - "50000:50000"  # Agent通信端口
    volumes:
      - ./jenkins_data:/var/jenkins_home  # 配置数据持久化
      - /var/run/docker.sock:/var/run/docker.sock  # 允许使用宿主机Docker
      - ./casc_configs:/var/jenkins_home/casc_configs  # 配置即代码
    environment:
      - TZ=Asia/Shanghai  # 时区设置
      - JAVA_OPTS=-Duser.timezone=Asia/Shanghai -Xmx1024m -Djenkins.install.runSetupWizard=false
      - CASC_JENKINS_CONFIG=/var/jenkins_home/casc_configs  # 配置即代码路径
    restart: unless-stopped  # 自动重启策略
    networks:
      - jenkins-net

networks:
  jenkins-net:
    driver: bridge

再次执行docker compose up -d验证,如下Jenkins 容器已成功启动

16.2.6获取初始管理员密码

docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

出现如下报错:docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
cat: /var/jenkins_home/secrets/initialAdminPassword: No such file or directory

原因:这个错误表明 Jenkins 容器没有生成初始管理员密码文件。这通常发生在您使用了 -Djenkins.install.runSetupWizard=false 参数跳过了初始设置向导

解决办法:

(1)停止并删除容器

docker compose down

(2)编辑 docker-compose.yml

sudo nano /usr/local/bin/docker-compose.yml

移除跳过向导的参数:

environment:
  - JAVA_OPTS=-Duser.timezone=Asia/Shanghai -Xmx1024m # 移除 -Djenkins.install.runSetupWizard=false

(3)重置数据卷

sudo rm -rf jenkins_data/*

(4)重新启动

docker compose up -d

16.2.7安装Jenkins推荐插件

16.2.8停止并删除容器(保留数据卷)

cd /usr/local/bin

docker-compose down

16.3Linux服务器本地部署Jenkins

16.3.1安装Java

java -version查询是否已安装

未安装则使用命令安装:

sudo apt install openjdk-17-jdk -y

16.3.2安装长期支持的LTS版本Jenkins

(1)使用Jenkins Debian 软件包安装Debian Jenkins 软件包

(2)启动Jenkins服务

sudo service jenkins start

(3)检查服务状态

sudo service jenkins status

16.3.3参考十五部分

16.3.​​​​​​4安装 Node.js(推荐使用 nvm 管理版本)

# 安装 nvm(Node 版本管理器)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# 重新加载 bash 配置
source ~/.bashrc

# 安装最新 LTS 版 Node.js
nvm install --lts
node -v  # 验证安装
npm -v   # 验证 npm

16.3.5全局安装 Newman

npm install -g newman newman-reporter-html

# 验证安装
newman --version

16.3.6 jenkins构建任务

出现如下报错:

原因:是https://reqres.in/这个AIP接口需要添加密钥

解决办法:添加密钥----如7.1.2创建环境操作

构建任务成功如下!

Execute shell命令如下:

cd /var/lib/jenkins
# 打印环境变量内容
echo "=== 环境变量文件内容 ==="
cat login.postman_environment.json
/home/yft/.nvm/versions/node/v22.17.0/bin/newman run /var/lib/jenkins/login_add_users.postman_collection.json -e /var/lib/jenkins/login_add_users.postman_environment.json --reporters cli,html --reporter-html-export "/var/lib/jenkins/newman-report.html" --color off --verbose  # 添加详细日志

16.4Jenkins管理-自动构建

说明:自动构建,就是设置一个定时器,定时时间到Jenkins自动执行测试用例

16.4.1操作步骤

(1)Jenkins首页新建Item

(2)其余设置和手动构建一致

(3)构建触发器标签Triggers-选择build periodically

(4)日程表中写入* * * * * 空格隔开分别对应分 时 日 月 年

修改30 21 * * *就是每天的21点30分自动执行

/var/lib/jenkins/jobs/AutoPostmanJenkinsTest/htmlreports/HTML_20Report

16.5持续集成-代码

16.5.1准备工作-将运行无误能生成报告代码的PyAPITest项目代码上传到GitLab中

操作步骤:参考文中的15.2.3准备文件上传到 Jenkins

 16.5.2Linux安装开发者工具VSCode

(1)官网下载最新版本VSCodeVisual Studio Code - Code Editing. Redefined

(2)通过命令安装sudo dpkg -i xxx.deb

(3)进入控制台安装插件python

(4)将gitlab上新项目克隆到Linux本地

git clone git@jihulab.com:yuanfutao-group/pyapitestlinux.git

(5)进入到工作空间目录后code .就使用VSCode打开项目

(6)linux本地运行程序无误,将能生成报告的项目代码上传到gitlab

运行命令:进入项目路径下执行 python3 run_suite.py

运行成功:1.生成测试报告2.控制台输出日志通过

(7)提交代码

# 进入项目目录
cd ~/pyapitestlinux

# 查看更改状态
git status

# 添加所有更改
git add .

# 提交更改
git commit -m "修复API测试错误,添加详细日志"

# 拉取最新更改(避免冲突)----首次提交不用
git pull

# 推送更改----git status查看时已显示当前就是main分支
git push

# 如果推送新分支
git push -u origin feature/login-fix

  16.5.3使用Jenkins管理-手动构建

(1)打开Jenkins首页,点击 新建Item 创建一个新任务

(2)输入任务名 选择freestyle project 点击确定,跳至配置页面

(3)源码管理标签 选择GIT 在repository URL后写入项目代码在gitlab的URL


Repository URL填入git@jihulab.com:yuanfutao-group/pyapitestlinux.git提示报错
Failed to connect to repository : Command "git ls-remote -h -- git@jihulab.com:yuanfutao-group/pyapitestlinux.git HEAD" returned status code 128:
stdout:
stderr: Host key verification failed.
fatal: 无法读取远程仓库。
请确认您有正确的访问权限并且仓库存在。

原因:这个错误 "Host key verification failed" 表示 Jenkins 服务器无法验证 GitLab 服务器的 SSH 主机密钥。这通常发生在 Jenkins 第一次尝试连接到 GitLab 服务器时。

解决方法:为 Jenkins 用户创建并配置 SSH 密钥

第一步:切换到 Jenkins 用户

sudo su - jenkins

第二步:创建 SSH 密钥对

ssh-keygen -t ed25519 -C "jenkins@$(hostname)"

第三步:查看并复制公钥

cat ~/.ssh/id_ed25519.pub

第四步:将公钥添加到 GitLab

  1. 进入 Settings > SSH Keys

第五步:添加 GitLab 主机到 known_hosts

ssh-keyscan jihulab.com >> ~/.ssh/known_hosts

第六步:测试连接

ssh -T git@jihulab.com

验证成功:输出Welcome to GitLab, @yuanfutao!

(4)构建标签

点击 增加构建步骤,选择 Execute shell

(5)构建后操作

点击增加构建后步骤,选择pulish HTML report 点击 新增按钮

在HTML directory to archive中写入报告生成的位置。如/home/yft/pyapitestlinux/report(与项目目录一致)

Index page[s]后的值与run_suite.py中代码,生成的测试报告名称保持一致。

(6)再次点击 增加构建后步骤 选择Editable Email Notification 设置右键发送测试报告

在Project Recipient List $DEFAULT_RECIPIENTS后使用英文逗号 分隔,添加邮箱地址

下面Content Type的值选择HTML(text/html)

测试报告模板代码写入Default Content

(7)点击右下角 Advanced settings 按钮将Triggers原有的内容点最外层红叉删除

点击add trigger 选择always

(8)点击应用保存

(9)点击 build now 开始手动构建

16.5.4本地运行项目是正常的但是构建任务出现如下报错:

本地运行项目是正常的但是构建任务出现如下报错:
Traceback (most recent call last):
  File "run_suite.py", line 11, in <module>
    from scripts.test_reqes_params import ReqresLoginParams
  File "/home/yft/pyapitestlinux/scripts/test_reqes_params.py", line 5, in <module>
    from parameterized import parameterized
ModuleNotFoundError: No module named 'parameterized'
Build step 'Execute shell' marked build as failure
[htmlpublisher] Archiving HTML reports...
[htmlpublisher] Archiving at PROJECT level /home/yft/pyapitestlinux/report to HTML_20Report
[htmlpublisher] Copying recursive using current thread
Finished: FAILURE

原因:Jenkins服务器环境缺少必要的Python包。
为什么本地运行正常但Jenkins服务器运行失败因为:
环境隔离:
本地环境已安装所有依赖
Jenkins服务器是"干净"环境,需要显式安装依赖
路径差异:
本地可能使用不同Python版本或虚拟环境
Jenkins使用系统默认Python环境
依赖管理:
没有在构建流程中声明依赖安装步骤
为什么每次构建任务时都需要重新安装依赖因为:
Jenkins 构建是独立的环境
每次构建都会清理工作区并重新检出代码
需要显式地在构建步骤中安装依赖

解决步骤:

(1)进入项目目录下创建文件

cd /home/yft/pyapitestlinux

sudo touch -c p requirements.txt

(2)将本地运行成功时需要的依赖写入requirements.txt

pip3 list查询

sudo nano requirements.txt

写入内容如:

parameterized==0.9.0 
requests==2.32.4 
pytest==8.2.0 
html-testRunner==1.2.1
requests-unixsocket==0.2.0 
HTMLTestReport==1.0.1

(3)将添加的声明文件requirements.txt上传到gitlab

git add requirements.txt

git status

git commit -m "添加声明配置文件"

git push

(4)在Jenkins构建项目配置中Build Steps-Execute shell出添加执行命令

cd /home/yft/pyapitestlinux

# 安装 Python 依赖(关键步骤!)
echo "正在安装依赖..."
pip3 install -r requirements.txt

# 检查安装结果(调试用)
echo "已安装的Python包:"
pip3 list

# 运行测试
echo "开始执行测试..."
python3 run_suite.py

(5)立即构建查看控制台输出日志

遇到此报错:

PermissionError: [Errno 13] Permission denied: '/home/yft/pyapitestlinux/log/reqes.log'

原因:Jenkins没有写入权限

解决方法:sudo chmod 777 /home/yft/pyapitestlinux/log/reqes.log

构建任务成功输出的日志如下:

16.5.5项目中配置邮箱

前提:14.5.5插件安装完后继续配置Jenkins邮箱服务这里已经配置成功!

(1)登录Jenkins进入项目-选择设置

(2)构建后操作选择Editable Email Notification

(3)Project Recipient Lis填写报告接收邮箱,多个邮箱之间英文逗号隔开

(4)Project Reply-To List默认$DEFAULT_REPLYTO就好

(5)Content Type选择HTML(text/html)

(6)Default Subject邮件默认主题可以自定义:如XXX项目API自动化测试

(7)Default Content默认内容可以自定义:如这是Jenkins自动构建测试报告!无须回复!

(8)Attachments邮件附件上传的测试报告:

如report/PyAPITestLinux项目测试报告-构建号${build_number}-*.html就是根据本次任务构建号进行匹配上传测试报告。如果使用report/PyAPITestLinux项目测试报告*.html就是上传report目录下所有html报告。

(9)Attach Build Log一般选择上传日志并且已压缩包上传Compress and AttachBuild Log

(10)点击右下角 Advanced settings 按钮将Triggers原有的内容点最外层红叉删除

点击add trigger 选择always、Failure - Any、Success的Send To删除默认Developers

全部新增为Recipient List

(11)检查构建命令:尤其注意report和log文件需要设置权限

# 进入到Linux本地项目目录下
cd /home/yft/pyapitestlinux

# 安装 Python 依赖(关键步骤!)
echo "正在安装依赖..."
pip3 install -r requirements.txt

# 检查安装结果(调试用)
echo "已安装的Python包:"
pip3 list

# 运行测试
echo "开始执行测试..."
python3 run_suite.py

# 复制报告到Jenkins workspace
cp -r /home/yft/pyapitestlinux/report/* ${WORKSPACE}/report/

16.5.6构建任务查看邮箱是否收到测试报告

16.5.7使用Jenkins管理(代码)-自动构建

前提:除自动触发器配置其余和手动构建配置一致

方法一:

(1)点击 配置 查看,在 Triggers构建触发器 的标签。选择Build periodically添加定时构建

(2)日程表中写入:* * * * * 分别对应 分 时 日 月 年

修改:00 8 * * *就是每天的早上8点执行

方法二:

(1)点击 配置 查看,在 Triggers构建触发器 的标签。选择Poll SCM。

(2)日程表中写入:*/1 * * * *代表1分钟检查一次gitlab上的代码,检查是否有更新

(3)本地修改代码commit、push或者直接在gitlab上修改,导致gitlab上的代码有变动。会触发Jenkins会自动构建。

新增项目代码后本地保存提示没有权限

filed to save 'run_suite.py': Insufficient permissions. Select
'Retry as Sudo' to retry as superuser.
Retry as Sudo..Save As..Revert

解决办法:给项目赋权755

(1)打开终端,执行以下命令修改项目所有权(将项目归属改为自己本地的用户)

sudo chown -R $USER:$USER /home/yft/autopyapitestlinux

-R:递归处理所有子目录和文件

$USER:自动获取当前用户名

(2)755 权限:用户可读/写/执行,组和其他用户可读/执行

sudo find /home/yft/autopyapitestlinux -type f -exec chmod 644 {} \;

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐