Skip to content

【线上】单元测试框架与自动化测试框架

单元测试框架与自动化测试框架

直播前准备

专题课 阶段 章节 录播地址
Pytest测试框架 L1 Pytest 简介、安装与准备 录播地址
Pytest测试框架 L1 Pytest 命名规则
Pytest测试框架 L1 pycharm 配置与界面化运行
Pytest测试框架 L1 Pytest 测试用例结构
Pytest测试框架 L1 Pytest 测试用例断言
Pytest测试框架 L1 Pytest 测试框架结构
Pytest测试框架 L2 Pytest 参数化用例
Pytest测试框架 L2 Pytest 标记测试用例
Pytest测试框架 L2 Pytest 运行用例
Pytest测试框架 L2 Pytest 测试用例调度与运行
Pytest测试框架 L2 Pytest 异常处理
Pytest测试框架 L3 Pytest 结合数据驱动-yaml
Allure 测试报告 L1 Allure2 安装 录播地址
Allure 测试报告 L1 Allure2 运行方式
Allure 测试报告 L2 Allure2 报告生成

课程目标

  • 掌握 Pytest 编写用例的结构与断言
  • 掌握 Pytest 自动测试实战能力
  • 熟悉 Pytest 参数化与基本装饰器用法
  • 掌握 Pytest 测试用例调度与运行
  • 掌握 Allure 报告生成

知识点总览

点击查看:自动化测试框架知识点梳理

需求说明

被测对象

  • 学生管理系统:
    • 随着学校的规模变大,对应的学员回越来越多,相应的管理越来越难。
    • 学员信息管理系统主要是对学员的各种信息进行管理,能够让学员的信息关系变得科学化、系统化和规范化。

测试需求

  • 测试学生管理系统的添加、修改、查询和删除功能。
  • 使用 pytest 编写自动化测试用例。
  • 输出 allure 测试报告。

实战思路

uml diagram

被测代码分析

# 定义一个学生类
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]
markers = P0
          P1
addopts = -v --alluredir=./result --clean-alluredir

搜集结果直接执行 pytest 即可。

总结

  • Pytest 编写用例的结构与断言
  • Pytest 参数化与基本装饰器用法
  • Pytest 测试用例调度与运行
  • Allure 报告生成

课后练习

点击查看课后练习