学生管理系统单元测试实战
课程目标
- 掌握 Pytest 编写用例的结构与断言
- 掌握 Pytest 自动测试实战能力
- 熟悉 Pytest 参数化与基本装饰器用法
- 掌握 Pytest 测试用例调度与运行
- 掌握 Allure 报告生成
知识点总览
点击查看:自动化测试框架知识点梳理
需求说明
被测对象
- 学生管理系统:
- 随着学校的规模变大,对应的学员回越来越多,相应的管理越来越难。
- 学员信息管理系统主要是对学员的各种信息进行管理,能够让学员的信息关系变得科学化、系统化和规范化。
测试需求
- 测试学生管理系统的添加、修改、查询和删除功能。
- 使用 pytest 编写自动化测试用例。
- 输出 allure 测试报告。
实战思路
被测代码分析
# 定义一个学生类
class Student:
def __init__(self, sid, name, age, gender):
self.sid = sid
self.name = name
self.age = age
self.gender = gender
# 重写对象的显示格式 方法
def __str__(self):
return f"SID: {self.sid} --- Name: {self.name} --- Age: {self.age} --- Gender: {self.gender}"
# 封装管理类
class StudentManagement:
def __init__(self):
# 定义一个全局变量,用来保存学生的信息,方法各个方法之间进行访问
self.students = []
# 菜单方法
def __menu(self):
print("****************************************")
print("* 学生管理系统 *")
print("* 1. 添加新学生信息 *")
print("* 2. 通过学号修改学生信息 *")
print("* 3. 通过学号删除学生信息 *")
print("* 4. 通过姓名删除学生信息 *")
print("* 5. 通过学号查询学生信息 *")
print("* 6. 通过姓名查询学生信息 *")
print("* 7. 显示所有学生信息 *")
print("* 8. 退出系统 *")
print("****************************************")
select_op = input("输入编号选择操作:")
return select_op
# 获取学号
def __get_sid(self):
sid = input("请输入学生ID:")
return sid
# 获取姓名
def __get_name(self):
name = input("请输入学生姓名:")
return name
# 获取年龄
def __get_age(self):
while True:
age = input("请输入学生年龄:")
if age.isdigit():
return int(age)
else:
print("输入年龄不合法,请输入数字")
# 获取性别
def __get_gender(self):
gender = input("请输入学生性别:")
return gender
# 添加学生
def add_student(self, sid, name, age, gender):
for s in self.students:
if s.sid == sid:
print("学号已存在,添加失败")
return "添加失败"
else:
student = Student(sid, name, age, gender)
self.students.append(student)
print("添加学生信息成功")
return '添加成功'
# 通过学号修改学生信息
def modify_student_by_id(self, sid, name, age, gender):
for s in self.students:
if s.sid == sid:
s.name = name
s.age = age
s.gender = gender
print("修改成功")
return "修改成功"
else:
print(f'没有 {sid} 对应的学生信息')
return "修改失败"
# 通过ID删除学生信息
def delete_student_by_id(self, sid):
for s in self.students:
if s.sid == sid:
self.students.remove(s)
print("删除成功")
return "删除成功"
else:
print(f'没有 {sid} 对应的学生信息')
return "删除失败"
# 通过学生姓名 删除所有符合的学生
def delete_student_by_name(self, name):
exist_s = []
# 找出所有要删除的学生
for s in self.students:
if s.name == name:
exist_s.append(s)
# 开始删除
if len(exist_s) > 0:
for s in exist_s:
self.students.remove(s)
print(f"姓名为 { name } 的学生删除成功")
else:
print(f"成功删除 {len(exist_s)} 个学生")
return "删除成功"
else:
print("学号不存在,无法删除")
return "删除失败"
# 通过学号查询学生信息
def query_student_by_id(self, sid):
for s in self.students:
if s.sid == sid:
print(f"学号 {sid} 的学生信息如下:")
print(s)
return "查询成功"
else:
print(f"学号 {sid} 的学生不存在")
return "查询失败"
# 通过姓名查询学生信息
def query_student_by_name(self, name):
result = []
for s in self.students:
if s.name == name:
result.append(s)
if len(result) > 0:
print(f"姓名为 {name} 的学生共 {len(result)} 名,信息如下:")
for s in result:
print(s)
return "查询成功"
else:
print(f"姓名为 {name} 的学生不存在")
return "查询失败"
# 显示所有学生信息
def __show(self):
print("所有学生信息如下:")
for s in self.students:
print(s)
# 管理方法
def manager(self):
while True:
select_op = self.__menu()
if len(select_op) == 1 and select_op in "12345678":
if select_op == "1":
sid = self.__get_sid()
name = self.__get_name()
age = self.__get_age()
gender = self.__get_gender()
self.add_student(sid, name, age, gender)
elif select_op =="2":
sid = self.__get_sid()
name = self.__get_name()
age = self.__get_age()
gender = self.__get_gender()
self.modify_student_by_id(sid, name, age, gender)
elif select_op =="3":
sid = self.__get_sid()
self.delete_student_by_id(sid)
elif select_op =="4":
name = self.__get_name()
self.delete_student_by_name(name)
elif select_op =="5":
sid = self.__get_sid()
self.query_student_by_id(sid)
elif select_op =="6":
name = self.__get_name()
self.query_student_by_name(name)
elif select_op =="7":
self.__show()
else:
break
else:
print("输入的数据不合法,请输入在合法范围内的操作编号!!!")
# 程序入口
if __name__ == '__main__':
StudentManagement().manager()
自动化测试用例编写
对以下方法完成冒烟测试:
- 添加学生:sid, name, age, gender
- 通过学号修改学生信息:sid, name, age, gender
- 通过学号查询学生信息:sid
- 通过姓名查询学生信息:name
- 通过 ID 删除学生信息:sid
- 通过学生姓名 删除所有符合的学生:name
# test_student_management.py
class TestStudentManagement:
def setup_class(self):
self.sm = StudentManagement()
# 添加学生
def test_add_student(self):
result = self.sm.add_student('s01', "tom", 22, "male")
assert result == "添加成功"
# 通过学号修改学生信息
def test_modify_student_byid(self):
result = self.sm.modify_student_by_id('s01', "jack", 22, "male")
assert result == "修改成功"
# 通过学号查询学生信息
def test_query_student_byid(self):
result = self.sm.query_student_by_id('s01')
assert result == "查询成功"
# 通过姓名查询学生信息
def test_query_student_byname(self):
result = self.sm.query_student_by_name("tom")
assert result == "查询成功"
# 通过 ID 删除学生信息
def test_delete_student_byid(self):
result = self.sm.delete_student_by_id('s01')
assert result == "删除成功"
# 通过学生姓名 删除所有符合的学生
def test_delete_student_byname(self):
self.sm.add_student('s01', "tom", 22, "male")
result = self.sm.delete_student_by_name("tom")
assert result == "删除成功"
运行测试用例
# 运行当前路径下所有的测试用例
pytest
# 运行时打印详细日志与控制台输出结果
pytest -vs
# 运行指定测试文件中的某一个测试用例
pytest -vs test_student_management.py::TestStudentManagement::test_add_student
添加标签
为用例添加优先级标签。
class TestStudentManagement:
def setup_class(self):
self.sm = StudentManagement()
# 添加学生
@pytest.mark.P0
def test_add_student(self):
result = self.sm.add_student('s01', "tom", 22, "male")
assert result == "添加成功"
# 通过学号修改学生信息
@pytest.mark.P0
def test_modify_student_byid(self):
result = self.sm.modify_student_by_id('s01', "jack", 22, "male")
assert result == "修改成功"
# 通过学号查询学生信息
@pytest.mark.P0
def test_query_student_byid(self):
result = self.sm.query_student_by_id('s01')
assert result == "查询成功"
# 通过姓名查询学生信息
def test_query_student_byname(self):
result = self.sm.query_student_by_name("tom")
assert result == "查询成功"
# 通过 ID 删除学生信息
@pytest.mark.P0
def test_delete_student_byid(self):
result = self.sm.delete_student_by_id('s01')
assert result == "删除成功"
# 通过学生姓名 删除所有符合的学生
@pytest.mark.P1
def test_delete_student_byname(self):
self.sm.add_student('s01', "tom", 22, "male")
result = self.sm.delete_student_by_name("tom")
assert result == "删除成功"
运行指定标签的用例
# 只运行标签为 PO 的用例
pytest -vs -m P0
为自定的标签添加配置,添加 pytest.ini 文件
[pytest]
markers = P0
P1
参数化
对添加学生功能完成测试
# test_add_student.py
class TestAddStudent:
def setup_class(self):
self.sm = StudentManagement()
@pytest.mark.parametrize(
"sid, name, age, gender",
[
["s01", "tom", 22, "male"],
["s02", "jack", 30, "male"],
["s03", "lily", 18, "female"],
["s04", "ema", 16, "female"]
],
ids=["add s01", "add s02", "add s03", "add s04"]
)
@pytest.mark.P1
def test_add_student_byparams(self, sid, name, age, gender):
'''
参数化添加学生
'''
result = self.sm.add_student(sid, name, age, gender)
assert result == "添加成功"
@pytest.mark.P1
def test_add_student_abnormal(self):
'''
重复 id 添加学生失败
'''
stu_info = ("s01", "tom", 22, "male")
self.sm.add_student(*stu_info)
result = self.sm.add_student(*stu_info)
assert result == "添加失败"
控制用例顺序
pip install pytest-ordering
添加顺序标记
class TestStudentManagement:
def setup_class(self):
self.sm = StudentManagement()
# 添加学生
@pytest.mark.P0
@pytest.mark.run(order=1)
def test_add_student(self):
result = self.sm.add_student('s01', "tom", 22, "male")
assert result == "添加成功"
# 通过学号修改学生信息
@pytest.mark.P0
def test_modify_student_byid(self):
result = self.sm.modify_student_by_id('s01', "jack", 22, "male")
assert result == "修改成功"
# 通过学号查询学生信息
@pytest.mark.P0
@pytest.mark.run(order=3)
def test_query_student_byid(self):
result = self.sm.query_student_by_id('s01')
assert result == "查询成功"
# 通过姓名查询学生信息
@pytest.mark.run(order=2)
def test_query_student_byname(self):
result = self.sm.query_student_by_name("tom")
assert result == "查询成功"
分布式并发执行测试用例
pip install pytest-xdist
指定执行的线程数
pytest -vs -n 3
数据驱动
准备 yaml 格式测试数据
# stu_info.yaml
add:
P1:
data:
- ["s05", "tom", 22, "male"]
- ["s06", "jack", 30, "male"]
- ["s07", "lily", 18, "female"]
- ["s08", "ema", 16, "female"]
ids:
- add s05
- add s06
- add s07
- add s08
创建 utils 工具
# utils/util.py
import yaml
class Utils:
# 获取 yaml 数据
@classmethod
def get_yaml_data(cls, file_path, name, level):
with open(file_path, encoding="utf-8") as f:
# safe_load() 将 yaml 格式转成 python 对象
result = yaml.safe_load(f)
print(f"yaml 文件读取结果为 {result}")
# 测试数据
data = result.get(name).get(level).get('data')
# 测试用例别名
ids = result.get(name).get(level).get('ids')
print(f"测试数据:{data}, 测试用例别名:{ids}")
return data, ids
使用数据驱动方式完成添加学生功能测试
stu_info = Utils.get_yaml_data("./datas/stu_info.yaml", "add", "P1")
class TestAddStudent:
def setup_class(self):
self.sm = StudentManagement()
@pytest.mark.parametrize(
"sid, name, age, gender", stu_info[0], ids=stu_info[1]
)
def test_add_student_byyaml(self, sid, name, age, gender):
'''
数据驱动添加学生
'''
result = self.sm.add_student(sid, name, age, gender)
assert result == "添加成功"
生成测试报告
添加 allure 描述
@allure.feature("学生管理系统")
class TestAddStudent:
def setup_class(self):
self.sm = StudentManagement()
@pytest.mark.parametrize(
"sid, name, age, gender",
[
["s01", "tom", 22, "male"],
["s02", "jack", 30, "male"],
["s03", "lily", 18, "female"],
["s04", "ema", 16, "female"]
],
ids=["add s01", "add s02", "add s03", "add s04"]
)
@pytest.mark.P1
@allure.story("添加学生")
@allure.title("参数化添加学生 {sid}, {name}")
def test_add_student_byparams(self, sid, name, age, gender):
'''
参数化添加学生
'''
with allure.step("添加学生,获取添加结果"):
result = self.sm.add_student(sid, name, age, gender)
assert result == "添加成功"
生成 allure 报告
# 执行用例,搜集执行结果
pytest -v --alluredir=./result --clean-alluredir
# 生成在线 allure 报告
allure serve ./result
# 生成静态 allure 报告
allure generate --clean alluredir result -o result/html
# 打开静态报告
allure open -h 127.0.0.1 -p 8883 ./result/html
在配置文件 pytest.ini 中添加运行参数。配置完毕后,搜集结果直接执行 pytest
即可。
[pytest]
markers = P0
P1
addopts = -v --alluredir=./result --clean-alluredir
使用 fixture
使用 fixture 管理生命周期,将类的实例化过程移到 fixture 中。
# conftest.py
@pytest.fixture(scope="class")
def sm():
sm = StudentManagement()
yield sm
# test_student_management.py
@allure.feature("学生管理系统")
class TestAddStudent:
@pytest.mark.parametrize(
"sid, name, age, gender",
[
["s01", "tom", 22, "male"],
["s02", "jack", 30, "male"],
["s03", "lily", 18, "female"],
["s04", "ema", 16, "female"]
],
ids=["add s01", "add s02", "add s03", "add s04"]
)
@pytest.mark.P1
@allure.story("添加学生")
@allure.title("参数化添加学生 {sid}, {name}")
def test_add_student_byparams(self, sm, sid, name, age, gender):
'''
参数化添加学生
'''
with allure.step("添加学生,获取添加结果"):
result = sm.add_student(sid, name, age, gender)
assert result == "添加成功"
数据的清理
# conftest.py
@pytest.fixture()
def add_for_test(sm):
sid = "for_test1"
sm.add_student(sid, "for_test", 22, "male")
yield sid
sm.delete_student_by_id(sid)
# test_student_management.py
@allure.feature("学生管理系统")
class TestAddStudent:
@pytest.mark.parametrize(
"name, age, gender",
[
["tom", 22, "male"],
["jack", 30, "male"],
["lily", 18, "female"],
["ema", 16, "female"]
],
ids=["modify tom", "modify jack", "modify lily", "modify ema"]
)
@pytest.mark.P1
@allure.story("修改学员信息")
@allure.title("参数化添加学生 {sid}, {name}")
# 通过学号修改学生信息
def test_modify_student_byid(self, sm, add_for_test, name, age, gender):
result = sm.modify_student_by_id(add_for_test, name, age, gender)
assert result == "修改成功"
assert sm.query_student_by_name(name) == "查询成功"
总结
- Pytest 编写用例的结构与断言
- Pytest 参数化与基本装饰器用法
- Pytest 测试用例调度与运行
- Allure 报告生成