目录

  • 接口自动化测试用例设计

  • Excel接口测试用例访问

  • MySQL接口测试用例访问

  • PyTest测试框架

  • 接口自动化测试必备技能-HTTP协议

  • requests库实现接口请求

接口自动化测试用例设计

目录

  • 接口测试的用例设计思路
  • 接口自动化测试用例设计要点
  • 接口自动化测试用例书写

接口测试的用例设计思路

  • 思考:接口测试,测试的是什么?

    • 根据接口的参数,设计输入数据,验证接口的返回值

    • 所以,其测试范围就是围绕着参数进行用例的设计 , 就是准备各种各样的数据,对接口的返回值进行验证

      • 正确的值
      • 边界的值
      • 错误的值
      • 根据场景设计的值
      • 异常的值
      • …..
  • 最终,只是看不见页面的黑盒用例设计方法 , 但是,我们自己要尽量根据接口要产生画面感

接口自动化测试用例设计要点

  • 尽量让人和代码都能够看懂且用得上 , 但是,很多时候我们做不到那就只能分开

    • 用例编号

    • 请求地址

    • 用例标题

    • 输入数据(分字段)

    • 请求接口类别

    • 数据格式

    • 请求方式

    • 是否需要登录

    • 期望结果

  • 接口自动化实施的前提 :

    • 接口已趋于稳定

接口自动化测试用例书写

image-20240214135530525

pandas安装与使用

安装与使用

  • 终端命令 : pip install pandas
    • pip install openpyxl
1
2
3
4
5
6
7
8
9
10
11
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")
print(sheet1_data)

Excel数据的访问

获取工作表数据

1
2
3
4
5
6
7
8
9
10
11
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的列数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")
print(sheet1_data) # 默认获取到的是第 1 个工作表的数据

获取指定工作表数据

1
2
3
4
5
6
7
8
9
10
11
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中指定工作表的数据
sheet2_data = pd.read_excel("第三章接口测试用例.xlsx", sheet_name="Sheet2")
print(sheet2_data)

获取到的数据的类型

1
2
3
4
5
6
7
8
9
10
11
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的列数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")
print(type(sheet1_data)) # 数据类型为 <class 'pandas.core.frame.DataFrame'>

获取指定列数据

1
2
3
4
5
6
7
8
9
10
11
12
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的列数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")
print(sheet1_data["编号"]) # 单独访问 Excel 表格指定列的数据
print(type(sheet1_data["编号"])) # 数据类型为 <class 'pandas.core.series.Series'>

获取多列数据

1
2
3
4
5
6
7
8
9
10
11
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的列数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")
print(sheet1_data[["编号", "标题"]]) # 单独访问 Excel 表格指定多列的数据

获取指定列指定行数据

1
2
3
4
5
6
7
8
9
10
11
12
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的列数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")
print(sheet1_data["编号"][0]) # 访问 Excel 表格指定列的数据后,再访问指定行的数据
print(sheet1_data[["编号", "标题"]][1:4]) # 单独访问 Excel 表格指定多列的数据后,再访问指定行,行数从0开始,右半开区间

获取指定行数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的列数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")

# 获取 Excel 表格中的行数据
print(sheet1_data.iloc[[1]]) # 获取第 1 行数据
print(sheet1_data.iloc[[1, 2, 3], [1, 2]]) # 获取第1,2,3 行数据后,再获取 第1,2 列数据
print(sheet1_data.iloc[0:2, 0:4]) # 获取 第 0到 第2行数据后,再获取第0 到第4列数据

行与列数据的遍历访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的列数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")

# 循环把所有列的值,依次取出
for i in sheet1_data:
print(i)
print(sheet1_data[i])

# 按照行来进行依次数据访问
for i in sheet1_data.index:
print(i)
print(sheet1_data.iloc[[i]])

# 访问 excel 中每一个单元格中的数据
for i in sheet1_data.index:
for j in sheet1_data.iloc[[i]]:
print(sheet1_data[j][i])

数据筛选访问和解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 导入模块
import pandas as pd

# 显示所有的列
pd.set_option("display.max_columns", None)
# 显示所有的行
pd.set_option("display.max_rows", None)

# 获取 Excel 表格中的列数据
sheet1_data = pd.read_excel("第三章接口测试用例.xlsx")

# 数据的筛选访问
login_case_type = sheet1_data[sheet1_data["请求接口类别"] == "登录"]
print(login_case_type)

# json(字典) 数据解析
login_case_data = login_case_type["输入数据"][0]
print(type(login_case_data)) # 数据类型为 <class 'str'>

# 借用第三方库,进行数据类型转换
import json

login_case_data_dict = json.loads(login_case_data)
print(login_case_data_dict)
print(type(login_case_data_dict)) # 数据类型为 <class 'dict'>
userName = login_case_data_dict["userName"]
passWord = login_case_data_dict["password"]
print(userName) # imooc
print(passWord) # 12345678

MySQL数据库接口测试用例访问

企业中测试团队工作场景

  • 多人共同测试同一个项目
  • 同事之间的工作有不同的分工和交叉

Excel存储测试用例的优势 | 劣势

  • 优势 :

    • 数据操作方便
  • 劣势 :

    • 团队中数据不易同步
    • 数据复用性差

MySQL存储测试用例的优势 | 劣势

  • 优势
    • 数据同步与共享方便
    • 数据复用性强,用例留有痕迹
  • 劣势
    • 有一定的技术门槛

MySQL测试用例管理思路

测试用例的书写

  • 可以与Excel测试用例字段一致
  • 但是,字段名称要使用英文,这样更符合规范

Excel测试用例数据导入

  • 在 Navicat 中 , 在 数据表-右键 - 导入向导 , 再按提示点下一步导入即可

结合Pandas操作MySQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 导入模块
import pymysql
import pandas as pd

# 连接数据库
db_info = {
"host": "127.0.0.1",
"user": "root",
"password": "123456",
"database": "interface_case",
"charset": "utf8"
}
conn = pymysql.connect(**db_info)

# 结合 pandas 执行 sql
sql = "select * from mumu"
result = pd.read_sql(sql, conn)
print(result)
print(type(result)) # <class 'pandas.core.frame.DataFrame'>

PyTest测试框架

PyTest基本介绍与安装

PyTest基本介绍

  • PyTest是Python的一个第三方的单元测试库
  • 自动识别测试模块和测试函数
  • 支持非常丰富的断言(assert)语句

PyTest中的使用约束

  • PyTest中的使用约束 :

    • 测试文件的文件名必须以 test_ 开头或以 _test 结尾

    • 测试类必须以 Test 开头

    • 测试的函数名必须以test_开头

    • 测试类里面不能使用init方法

PyTest的安装

  • PyTest的安装 :
    • 终端命令 : pip install pytest

PyTest的案例

1
2
3
4
5
6
7
8
9
10
11
12
# 导入模块
import pytest


# 定义函数
def test_demo():
assert 1 == 1


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_501_pytest.py"])

PyTest中的断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 导入模块
import pytest


# 定义函数
def add(a, b):
return a + b

def test_dayu():
assert 5 > add(2, 2)


def test_baohan():
assert 1 in [1, 2, 3]


def test_bubaohan():
assert 1 not in [1, 2, 3] # 不通过


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_502_pytest断言.py"])

PyTest中的参数化

PyTest中的参数化写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 导入模块
import pytest


# 定义函数
def add(a, b):
return a + b


# 第 1 种参数化写法
@pytest.mark.parametrize(["x", "y"], [(1, 2), (0, 3), (1, 4)])
def test_add1(x, y):
assert 3 == add(x, y)


# 第 2 种参数化写法
xy = [(1, 2), (0, 3), (1, 4)]
@pytest.mark.parametrize(["x", "y"], xy)
def test_add2(x, y):
assert 3 == add(x, y)


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_503_pytest参数化.py"])

读取数据库测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 导入模块
import pytest
import pymysql


# 定义函数
def add(a, b):
return a + b


# 把存储在mysgl当中的测试用例参数化出来
# 连接数据库
db_info = {
"host": "127.0.0.1",
"user": "root",
"password": "123456",
"database": "interface_case",
"charset": "utf8"
}
conn = pymysql.connect(**db_info)
cursor = conn.cursor()
sql = "select * from mumu"
cursor.execute(sql)
result = cursor.fetchall()


# 定义函数
@pytest.mark.parametrize(
["id", "case_id", "title", "interface_type", "uri", "method", "if_login", "input_data", "data_type", "expect"],
result)
def test_mysql_param(id, case_id, title, interface_type, uri, method, if_login, input_data, data_type, expect):
print(title)

# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_503_pytest参数化.py"])

PyTest中的组织管理

  • 在pytest当中,测试方法执行的顺序,默认是从上到下
  • 我们使用 pytest.mark.run 进行测试函数执行顺序的标记时 , 需要先安装 pytest_ordering
    • 终端命令 : pip install pytest_ordering
  • 在优先级方面 pytest.mark.first 是最高优先级
  • 如果我们在进行标记顺序时,假设我们采用了数字的形式,那么就都用数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 导入模块
import pytest


def test_01():
print(1)


# 控制 test_02 函数第 1 个执行, 优先级大于 order = 1
@pytest.mark.first
def test_02():
print(2)


def test_03():
print(3)


# 控制 test_04 函数第 1 个执行
@pytest.mark.run(order=1)
def test_04():
print(4)


class TestLogin():
def test_05(self):
print(5)

# 控制 test_06 函数最后执行
@pytest.mark.run(order=-1)
def test_06(self):
print(6)

# 控制 test_06 函数最后执行 , 优先级大于 order = -1
@pytest.mark.last
def test_07(self):
print(7)

def test_08(self):
print(8)


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_504_测试用例的组织管理.py"])

PyTest的前置与后置操作

前置函数与后置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 导入模块
import pytest


# 前置的模块级别的函数,每个模块只会执行一次,会先于前置函数级别的函数运行
def setup_module():
print("这是前置的模块级别的函数")


# 后置的模块级别的函数,每个模块只会执行一次
def teardown_module():
print("这是后置的模块级别的函数")


# 前置函数级别运行的函数,在每个函数调用前,都会执行一次
def setup_function():
print("这是前置函数级别运行的函数")


# 后置函数级别运行的函数,在每个函数调用后,都会执行一次, 会先于模块级别的函数执行
def teardown_function():
print("这是后置函数级别运行的函数")


"""
在Python3.6的版本中, 下边的两个前置和后置函数setup和teardown是可以运行的,但是优先级没有setup_function和teardown_function的优先级高,
如果同时写,则下边的两个前置和后置函数setup和teardown是不执行的
"""
# 前置函数
def setup():
print("前置函数级别的 1111")


# 后置函数
def teardown():
print("后置函数级别的 2222")


def test_01():
print("测试1运行了")
assert 1 == 1


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_505_pyTest前置后置操作.py"])

前置类方法和后置类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 导入模块
import pytest

class TestMyClass():
def setup_class(self):
print("这是前置的class级别的方法")

def teardown_class(self):
print("这是后置的class级别的方法")

def setup_method(self):
print("我是前置的类中的方法级别的")

def teardown_method(self):
print("我是后置的类中的方法级别的")

def test_of_method1(self):
print("我是类中的普通测试方法")


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_505_pyTest前置后置操作.py"])

Fixture

Fixture介绍

  • Fixture 是pytest最大的优势之一
  • Fixture 可以将测试的复杂需求简单化、组件化
  • Fixture 为测试提供了已定义的、可靠的和一致的上下文
  • Fixture 定义了构成测试准备阶段的步骤和数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 导入模块
import pytest


# 定义方法
@pytest.fixture()
def first_fixture():
print("第 1 个 fixture")
return 1


# fixture 之间的相互调用
@pytest.fixture()
def second_fixture(first_fixture):
print("第 2 个 fixture")
return first_fixture + 2


def test_case1(first_fixture):
print("这是我的第 1 个测试用例")
r = first_fixture
print(r)


def test_case2(second_fixture):
print("这是我的第 2 个测试用例")
print(second_fixture)


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_507_Fixture介绍.py"])

多Fixture应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 导入模块
import pytest


# 定义方法
@pytest.fixture()
def first_fixture():
print("第 1 个 fixture")
return 1, 2, 3


# fixture
@pytest.fixture()
def second_fixture():
return "第 2 个值"


def test_case1(first_fixture, second_fixture):
print("这是我的第 1 个测试用例")
print(first_fixture) # (1, 2, 3)
print(second_fixture)

a, b, c = first_fixture
print(a)
print(b)
print(c)
assert (1, 2, 3) == first_fixture


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_508_多fixture应用.py"])

conftest的统一配置

  • conftest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 导入模块
import pytest


# fixture
@pytest.fixture()
def first_fixture():
print("第 1 个 fixture")
return 1, 2, 3


# fixture
@pytest.fixture()
def second_fixture():
return "第 2 个值"
  • test_xxx.py
    • 会自动加载上面的 conftest 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
# 导入模块
import pytest


def test_case1(first_fixture, second_fixture):
print("这是我的测试用例")
print(first_fixture)
print(second_fixture)


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_509_conftest配置.py"])

作用域控制与自动运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 导入模块
import pytest


# 会话级别作用域
@pytest.fixture(scope="session", autouse=True)
def session_fixture():
print("我是会话级别的作用域")


# 模块级别的作用域
@pytest.fixture(scope="module", autouse=True)
def module_fixture():
print("我是模块级别的作用域")


# 类级别的作用域
# 当我们使用fixture时,把scope的值设置成class时每一个测试类之前都会被运行
# pytest 在测试函数上会自动的把函数包装成一个测试类
@pytest.fixture(scope="class", autouse=True)
def class_fixture():
print("我是类级别的作用域")


# 函数级别的作用域
# 在使用 fixture 时,scope=function 时无论时在函数中,还是在方法中,都是作用域都funtion在生效
# 在我们使用的最新的Python版本中,去掉了scope=method这个作用域在Python3.6的版本上边这种写法会生效
@pytest.fixture(scope="function", autouse=True)
def function_fixture():
print("我是函数级别的作用域")


# 定义方法
def test_case1():
print("---- 我是函数的测试用例 1111 -----")


def test_case2():
print("---- 我是函数的测试用例 2222 -----")


class TestMyClass():
def test_of_method1(self):
print("***** 我是类中的测试方法 1111 *****")

def test_of_method2(self):
print("***** 我是类中的测试方法 2222 *****")


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_510_Fixture作用域.py"])

使用fixture实现参数化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 导入模块
import pytest

test_data = [
{
"case_name": "登录成功的测试",
"username": "admin",
"password": "123"
},
{
"case_name": "登录失败的测试",
"username": "aaaa",
"password": "1111"
},
{
"case_name": "用户名为空的登录",
"username": "",
"password": "2222"
}
]


@pytest.fixture(params=test_data)
def param_data(request):
# request是pytest内置的fixture,必须这样写才能传进来, 它的作用主要就是用来帮助我们传递参数
return request.param


# 测试方法
def test_login(param_data):
username = param_data.get("username")
print(username)
password = param_data.get("password")
print(password)
case_name = param_data.get("case_name")
print(case_name)


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_511_fixture实现参数化.py"])

使用fixture实现测试控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 导入模块
import pytest


# 无理由跳过
@pytest.mark.skip
def test_dengyu():
assert 3 == 3


# 有条件的跳过
a = 1
@pytest.mark.skipif(a > 0, reason="a 大于 0,我跳过了 ")
def test_dayu():
assert 5 != 3


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["test_512_fixture实现测试控制.py"])

集成allure生成测试报告

环境安装

  • 1, allure 下载地址 : https://github.com/allure-framework/allure2/releases
    • 将解压后的文件夹复制到项目根目录中即可
  • 2, 安装 JDK
    • 安装略…
  • 3, 配置环境变量
    • 配置 JDK 环境变量过程略…
    • 配置 allure 环境变量
      • 将项目目录中的 allure 目录中的 bin 目录,配置到环境变量的 path 变量中
      • 在终端输入 allure --version , 出现版本号即为配置成功

生成测试报告

  • 安装 allure :

    • 终端命令 :pip install allure-pytest
  • 终端执行命令 : pytest .\test_513_生成测试报告.py --alluredir ./result , 生产测试数据,存储于当前目录下的 result 目录中

  • 终端执行命令 : allure generate ./result -o ./report_allure , 即可在 report_allure 目录中生成测试报告

HTTP协议

HTTP协议简介

  • HTTP协议,中文名称为超文本传输协议
  • 它是一个应用层的协议,是由请求和响应构成
  • 其典型的应用场景就是浏览器和服务器之间进行数据传输

HTTP协议的基本工作流程

image-20240214211121328

HTTP协议状态码解析

  • 5 种状态分类 :
    • 1**:服务器收到请求,需要请求者继续执行操作**
    • 2**:操作被成功接收并处理
    • 3**:请求重定向,需要进一步的操作以完成请求
    • 4**:客户端错误,请求包含语法错误或无法完成请求
    • 5**:服务器错误,服务器在处理请求的过程中发生了错误

HTTP协议常见的状态码

  • 200:请求成功
  • 400:客户端请求的语法错误,服务器无法理解
  • 401:请求要求用户的身份认证
  • 403:服务器理解请求客户端的请求,但是拒绝执行此请求
  • 404:服务器无法根据客户端的请求找到资源
  • 405:客户端请求中的方法被禁止
  • 500:服务器内部错误,无法完成请求
  • 503:由于超载或系统维护,服务器暂时的无法处理客户端的请求
  • 505:服务器不支持请求的HTTP协议的版本,无法完成处理

HTTP协议请求头解析

  • Accept:告诉服务器自己接受什么数据类型
  • Accept-Encoding:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法
  • Accept-Language:浏览器声明自己接收的语言
  • Connection:表示是否需要持久连接
  • Cookie:保存客户端部分信息发送到服务端供服务端验证和使用
  • Host:客户端指定自己想访问的WEB服务器的域名/IP地址和端口号
  • jwt_token:代表了客户端向服务端请求时带有什么验证信息
  • Origin:代表了客户端的请求请求来自于哪个站点
  • Referer:浏览器向 WEB 服务器表明自己是从哪个网页URL获得点击当前请求中的网址/URL
  • User-Agent:浏览器表明自己的身份(是哪种浏览器)
  • X-Requested-With:标识着请求是同步请求还是异步请求
  • Cache-Control:指定请求和响应遵循的缓存机制,决定了如果管理和使用缓存

HTTP协议响应头解析

  • Access-Control-Allow-Credentials:请求的响应是否可以暴露于该页面,当true值返回时它可以被暴露
  • Access-Control-Allow-Origin:服务器端允许进行跨域资源访问的来源域
  • Content-Type:服务端告诉客户端响应体的内容类型
  • Date:创建响应数据(报文)的日期时间,就是消息发送的时间
  • Transfer-Encoding:传输编码,chunked是分块传输的意思
  • Vary:服务器用这个头的内容告诉缓存服务器,在什么条件下才能用本响应所返回的对象响应后续的请求
  • Allow:服务器支持哪些请求方法(如GET、POST等)
  • Server:服务器表明自己是什么软件及版本等信息

Cookie和Session

什么是Cookie

  • Cookie是一些网站为了识别用户和跟踪会话而存储在用户本地终端中的文本数据

Cookie的工作流程

image-20240214212630413

什么是Session

  • Session是指会话控制,是保存在服务器上一种机制,当客户端访问服务器的时候,服务器会把信息以某种形式记录在服务器上,恰恰和Cookie相反

Session的工作流程

image-20240214213121737

Cookie和Session的区别

  • 1、存放位置不同,Cookie放在客户端,Session存在服务端
  • 2、存放位置不同导致安全性不同,Cookie存在客户端中,用户可以任意分析Cookie,而Session在服务端,用户分析不到
  • 3、数据存储大小不同,Cookie保存的数据不能超过4KSession存在服务端,所以没有限制
  • 4、性能使用情况不同,当访问过多时Session比较占用服务器资源,而Cookie不会

requests接口请求

requests库介绍

  • requests是一款非常火爆且常用的Python三方库
  • 能够实现HTTP协议的各种请求方法
  • 使用简单易上手

requests库的安装

  • 终端命令 : pip install requests

requests发送get接口请求

  • 注意要开启后端服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 导入模块
import requests

# 发起get请求
# response = requests.get("http://www.baidu.com")
# print(response) # <Response [200]>
# print(response.status_code) # 200
# print(response.text) # 响应体

# baseURL
url = "http://localhost:5000/"

# 无参数的 get 请求
r1 = requests.get(url)
print(r1.text) # hello world

# 带路径的 get 请求
r2 = requests.get(url + "hello")
print(r2.text) # hello

# 路径上带参数的 get 请求
r3 = requests.get(url + "hello/我是参数")
print(r3.text) # hello我是参数

# 路径上带参数,并且带有参数值的 get 请求
r4 = requests.get(url + "hello/args/我是参数11?key=我是key&value=我是value")
print(r4.text) # hello我是参数11::::我是key我是value

# 第二种带参数的方式
r5 = requests.get(url + "hello/args/我是参数22", {"key": "我是key2", "value": "我是value2"})
print(r5.text) # hello我是参数22::::我是key2我是value2

requests发送post接口请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 导入模块
import requests

# baseURL
url = "http://localhost:5555/"

r1 = requests.post(url + "mypost")
print(r1.text) # post request

# 请求数据是表单类型的数据
r2 = requests.post(url + "mypost1", data={
"username": "小红",
"sex": "男"
})
print(r2.text) # post request小红男

# 请求是 json 类型的数据
r3 = requests.post(url + "mypost2", json={
"user": "小蓝",
"sex": "男"
})
print(r3.text) # post request{'user': '小蓝', 'sex': '男'}

requests发送请求头信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 导入模块
import requests
import json

# baseURL
url = "http://111.231.103.117:8083"
# 登录接口
login_uri = "/loginWithJwt"
# 登录的用户名和密码
login_data = {
"userName": "imooc",
"password": "12345678"
}

# 发起登录请求
login_response = requests.get(url + login_uri, login_data)
print(login_response.text)
data = json.loads(login_response.text) # 转为 json 类型
print(data)
jwt_token = data["data"]
print(jwt_token) # 获取到了用户 token

# 请求头
headers = {
"jwt_token": jwt_token
}

# 添加购物车的接口地址
cart_add_uri = "/cart/add"

# 发起添加到购物车的请求,同时携带用户 token
cart_add_response = requests.post(url + cart_add_uri, data={
"count": 10,
"productId": 2
}, headers=headers)

# 添加购物车请求的响应
print(cart_add_response.text)

接口自动化测试项目实战

前言

  • 思考的问题
    • 如何实现单个接口请求的测试
    • 如何实现登录的Excel文件测试用例参数化
    • 如何实现登录的MySQL数据库测试用例参数化
    • 添加购物车的登录状态该如何处理
    • 测试用例中的字段该如何使用
    • 如何优雅的实现添加购物车的测试用例

读取数据库数据

  • ./utils/mysql_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 操作数据库的方法
import pymysql


# 连接数据库
def get_sql_connection():
db_info = {
"host": "127.0.0.1",
"user": "root",
"password": "123456",
"database": "interface_case",
"charset": "utf8"
}
conn = pymysql.connect(**db_info)
return conn


# 读取数据库
def get_mysql_case_data(interface_type):
conn = get_sql_connection()
sql = "select * from mumu where interface_type = '{}'".format(interface_type)
cursor = conn.cursor()
cursor.execute(sql)
result = cursor.fetchall()
return result

  • ./test_login_case.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用数据库原生集合fixture进行参数化
import pytest
import requests
# from utils.mysql_utils import get_mysql_case_data
import utils.mysql_utils as um


# 定义一个 fixture
@pytest.fixture(params=um.get_mysql_case_data("登录"))
def get_mysql_login_data(request):
return request.param


# 定义测试方法
def test_mysql_login(get_mysql_login_data):
print(get_mysql_login_data)


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["-vs", "mysql_utils.py"])

测试代码开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 使用数据库原生集合fixture进行参数化
import pytest
import requests
# from utils.mysql_utils import get_mysql_case_data
import utils.mysql_utils as um
import json

# baseUrl
url = "http://111.231.103.117:8083/"


# 定义一个 fixture
@pytest.fixture(params=um.get_mysql_case_data("登录"))
def get_mysql_login_data(request):
return request.param


# 定义测试方法
def test_mysql_login(get_mysql_login_data):
id = get_mysql_login_data[0]
case = get_mysql_login_data[1]
title = get_mysql_login_data[2]
interface_type = get_mysql_login_data[3]
uri = get_mysql_login_data[4]
method = get_mysql_login_data[5]
if_login = get_mysql_login_data[6]
input_data = get_mysql_login_data[7]
data_type = get_mysql_login_data[8]
expect = get_mysql_login_data[9]
if method == "get":
response = requests.get(url + uri, json.loads(input_data))
assert 200 == response.status_code
assert int(expect) == json.loads(response.text)["status"]
elif method == "post":
if data_type == "form":
response = requests.post(url + uri, data=json.loads(input_data))
elif data_type == "json":
response = requests.post(url + uri, json=json.loads(input_data))

assert 200 == response.status_code
assert int(expect) == json.loads(response.text)["status"]


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["-vs", "mysql_utils.py"])

Pandas实现登录的自动化测试

  • ./utils/mysql_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 操作数据库的方法
import pymysql
import pandas as pd


# 连接数据库
def get_sql_connection():
db_info = {
"host": "127.0.0.1",
"user": "root",
"password": "123456",
"database": "interface_case",
"charset": "utf8"
}
conn = pymysql.connect(**db_info)
return conn


# 读取数据库
def get_mysql_case_data(interface_type):
conn = get_sql_connection()
sql = "select * from mumu where interface_type = '{}'".format(interface_type)
cursor = conn.cursor()
cursor.execute(sql)
result = cursor.fetchall()
return result


def get_mysql_case_data_for_pandas(interface_type):
conn = get_sql_connection()
sql = "select * from mumu where interface_type = '{}'".format(interface_type)
interface_type_data = pd.read_sql(sql, conn)
# 解析数据
final_data = []
for i in interface_type_data.index:
inner_data = {}
for d in interface_type_data.iloc[[i]]:
inner_data[d] = interface_type_data[d][i]
final_data.append(inner_data)

# return interface_type_data
return final_data

  • ./test_login_case.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 使用数据库原生集合fixture进行参数化
import pytest
import requests
# from utils.mysql_utils import get_mysql_case_data
import utils.mysql_utils as um
import json

# baseUrl
url = "http://111.231.103.117:8083/"


# 定义一个 fixture
@pytest.fixture(params=um.get_mysql_case_data("登录"))
def get_mysql_login_data(request):
return request.param


# 定义测试方法
def test_mysql_login(get_mysql_login_data):
id = get_mysql_login_data[0]
case = get_mysql_login_data[1]
title = get_mysql_login_data[2]
interface_type = get_mysql_login_data[3]
uri = get_mysql_login_data[4]
method = get_mysql_login_data[5]
if_login = get_mysql_login_data[6]
input_data = get_mysql_login_data[7]
data_type = get_mysql_login_data[8]
expect = get_mysql_login_data[9]
if method == "get":
response = requests.get(url + uri, json.loads(input_data))
assert 200 == response.status_code
assert int(expect) == json.loads(response.text)["status"]
elif method == "post":
if data_type == "form":
response = requests.post(url + uri, data=json.loads(input_data))
elif data_type == "json":
response = requests.post(url + uri, json=json.loads(input_data))

assert 200 == response.status_code
assert int(expect) == json.loads(response.text)["status"]


@pytest.fixture(params=um.get_mysql_case_data_for_pandas("登录"))
def get_mysql_case_data_for_pandas(request):
return request.param


def test_mysql_login2(get_mysql_case_data_for_pandas):
# print(get_mysql_case_data_for_pandas)
if get_mysql_case_data_for_pandas["method"] == "get":
response = requests.get(url + get_mysql_case_data_for_pandas["uri"],
json.loads(get_mysql_case_data_for_pandas["input_data"]))
assert 200 == response.status_code
assert int(get_mysql_case_data_for_pandas['expect']) == json.loads(response.text)["status"]
elif get_mysql_case_data_for_pandas["method"] == "post":
if get_mysql_case_data_for_pandas["data_type"] == "form":
response = requests.post(url + get_mysql_case_data_for_pandas["uri"],
data=json.loads(get_mysql_case_data_for_pandas["input_data"]))
elif get_mysql_case_data_for_pandas["data_type"] == "json":
response = requests.post(url + get_mysql_case_data_for_pandas["uri"],
json=json.loads(get_mysql_case_data_for_pandas["input_data"]))
assert 200 == response.status_code
assert int(get_mysql_case_data_for_pandas['expect']) == json.loads(response.text)["status"]


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["-vs", "mysql_utils.py"])

Excel参数化实现登录自动化测试

  • ./utils/excel_utils.px
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd


def get_excel_case_data(interface_type):
data = pd.read_excel("./case_data/第三章接口测试用例.xlsx")
interface_type_data = data[data["请求接口类别"] == interface_type]
# 解析数据
final_data = []
for i in interface_type_data.index:
inner_data = {}
for d in interface_type_data.iloc[[i]]:
inner_data[d] = interface_type_data[d][i]
final_data.append(inner_data)

# return interface_type_data
return final_data

  • ./test_login_case.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# 使用数据库原生集合fixture进行参数化
import pytest
import requests
# from utils.mysql_utils import get_mysql_case_data
import utils.mysql_utils as um
import utils.excel_utils as ue
import json

# baseUrl
url = "http://111.231.103.117:8083/"


# 定义一个 fixture
@pytest.fixture(params=um.get_mysql_case_data("登录"))
def get_mysql_login_data(request):
return request.param


# 定义测试方法
def test_mysql_login(get_mysql_login_data):
id = get_mysql_login_data[0]
case = get_mysql_login_data[1]
title = get_mysql_login_data[2]
interface_type = get_mysql_login_data[3]
uri = get_mysql_login_data[4]
method = get_mysql_login_data[5]
if_login = get_mysql_login_data[6]
input_data = get_mysql_login_data[7]
data_type = get_mysql_login_data[8]
expect = get_mysql_login_data[9]
if method == "get":
response = requests.get(url + uri, json.loads(input_data))
assert 200 == response.status_code
assert int(expect) == json.loads(response.text)["status"]
elif method == "post":
if data_type == "form":
response = requests.post(url + uri, data=json.loads(input_data))
elif data_type == "json":
response = requests.post(url + uri, json=json.loads(input_data))

assert 200 == response.status_code
assert int(expect) == json.loads(response.text)["status"]


@pytest.fixture(params=um.get_mysql_case_data_for_pandas("登录"))
def get_mysql_case_data_for_pandas(request):
return request.param


def test_mysql_login2(get_mysql_case_data_for_pandas):
# print(get_mysql_case_data_for_pandas)
if get_mysql_case_data_for_pandas["method"] == "get":
response = requests.get(url + get_mysql_case_data_for_pandas["uri"],
json.loads(get_mysql_case_data_for_pandas["input_data"]))
assert 200 == response.status_code
assert int(get_mysql_case_data_for_pandas['expect']) == json.loads(response.text)["status"]
elif get_mysql_case_data_for_pandas["method"] == "post":
if get_mysql_case_data_for_pandas["data_type"] == "form":
response = requests.post(url + get_mysql_case_data_for_pandas["uri"],
data=json.loads(get_mysql_case_data_for_pandas["input_data"]))
elif get_mysql_case_data_for_pandas["data_type"] == "json":
response = requests.post(url + get_mysql_case_data_for_pandas["uri"],
json=json.loads(get_mysql_case_data_for_pandas["input_data"]))
assert 200 == response.status_code
assert int(get_mysql_case_data_for_pandas['expect']) == json.loads(response.text)["status"]


@pytest.mark.parametrize(
["case_id", "title", "interface_type", "uri", "method", "if_login", "input_data", "data_type", "expect"],
ue.get_excel_case_data("登录"))
def test_excel_data_login(case_id, title, interface_type, uri, method, if_login, input_data, data_type, expect):
if method == "get":
response = requests.get(url + uri, json.loads(input_data))
assert 200 == response.status_code
assert int(expect) == json.loads(response.text)["status"]
elif method == "post":
if data_type == "form":
response = requests.post(url + uri, data=json.loads(input_data))
elif data_type == "json":
response = requests.post(url + uri, json=json.loads(input_data))

assert 200 == response.status_code
assert int(expect) == json.loads(response.text)["status"]


# 使用 pyTest 执行文件
if __name__ == "__main__":
pytest.main(["-vs", "-p", "no:warnings", "mysql_utils.py"])