mock模拟,skip,skipif,mark分类

本文详细介绍了三种测试技巧:使用mock模拟第三方接口返回值以调试代码;使用skip和skipif跳过有缺陷或不符合当前版本需求的测试用例;利用pytest的mark特性对测试用例进行分类管理。

1:mock

mock使用场景:被测试的接口需要调用第三方接口,而第三方接口还没有调试通过,此时可以使用mock模拟接口的返回值,来调试自己的代码,第三方接口调通后,再真实调用第三方接口

# 第三方未通的支付接口
def pay(data):
    """
    支付接口请求参数:
    param data:{'id':'','money':,'type':''}
        id:字符串,支付编号
        money:浮点数,支付金额
        type:支付类型
    return:{'status':1,'msg':'支付成功'} {'status':0,'msg':'支付失败'}
    """
    r = requests.post('', data=data)
    return r.json()

#使用mock模拟
def test_pay_success():
    # 请求参数
    cs = {"id": "D202208040001", "money": 45.8, "type": "支付"}
    # 使用mock.MOCK(return_value={}) 模拟第三方接口的返回值
    pay = mock.Mock(return_value={'status': 0, 'msg': '支付成功'})
    # 调用第三方接口
    res = pay(cs)
    #断言操作
    assert res['status'] == 1
    assert res['msg'] == '支付成功'

2:skip,skipif

skip/skipif使用场景:

  • skip:用例对应的功能有缺陷,使用skip跳过该用例的执行,等缺陷解决后再执行该用例
  • skipif:条件性跳过,产品存在多个版本,而自动化脚本只有一套,按照版本的不同,跳过用例
#版本号
version='V1R1'

@pytest.mark.skip(reason='缺陷id为:XXXX')
def test_01():
    print("用例1")

#V1R2支持  V1R1不支持
@pytest.mark.skipif(version='V1R1',reason='V1R1版本不支持该功能')
def test_02():
    print("用例2")

3:mark

mark进行测试分类:自动化测试包含,冒烟测试,接口测试,界面测试,性能测试,这些自动化脚本存在重合的用例,通常一个项目中会把所有测试的脚本在放一起管理,通过自定义mark标记,定义在用例或者类上

在pytest.ini文件中配置各种标记:

[pytest]
addopts= -m="api  or  web"
; -m=参数来配置执行那些标记的用例 可以使用 and or not 
; -m='api'
; -m='api and web'
; -m='api or web'
; -m='not api'
; 通过markers配置不同的标记 标记: 解释
markers= smoke: smoke test case
         web: web test case
         api: api test case
@pytest.mark.web #界面测试
def test_01():
    print("用例1")

@pytest.mark.api  #接口测试
@pytest.mark.smoke #冒烟测试
def test_02():
    print("用例2")

@pytest.mark.api #接口测试
class Test001():
    def test_01(self):
        print("用例1")

    def test_02(self):
        print("用例2")

    @pytest.mark.web #界面测试
    def test_03(self):
        print("用例3")

    def test_04(self ):
        print("用例4")
# -*- coding: utf-8 -*- """ ============================ pytest 大师课:从入门到精通 ============================ 版本:2.1 | 修复版 """ # ==================== # 第一部分:pytest 核心概念 # ==================== """ 1. 为什么选择 pytest? - 更简洁的测试代码(相比 unittest) - 强大的断言机制(无需记忆特定方法) - 灵活的夹具系统(fixtures) - 丰富的插件生态(1000+ 插件) - 智能测试发现与执行 2. 核心组件: • 测试发现:自动查找 test_*.py 文件 • 测试执行:并行、选择性和顺序执行 • 报告系统:多种格式输出 • 插件架构:高度可扩展 3. 安装与升级: pip install -U pytest pytest-html pytest-xdist pytest-cov pytest-mock """ # ==================== # 第二部分:基础到进阶示例 # ==================== import os # 导入操作系统接口模块,用于文件路径操作 import pytest # 导入pytest测试框架 import sys # 导入系统模块,用于访问Python解释器相关功能 from datetime import datetime # 导入日期时间模块,用于时间相关操作 from unittest.mock import MagicMock, patch # 导入mock模块,用于模拟对象和函数 # -------------------- # 1. 测试函数与断言 # -------------------- def test_addition(): """基础断言示例:验证基本算术运算""" assert 1 + 1 == 2, "加法计算错误" # 使用assert进行简单断言 def test_container_operations(): """容器操作断言:验证列表操作""" numbers = [1, 2, 3, 4, 5] # 定义测试数据列表 assert 3 in numbers # 检查元素是否在列表中 assert numbers == [1, 2, 3, 4, 5] # 检查列表是否相等 assert all(n > 0 for n in numbers) # 检查所有元素是否满足条件 assert any(n % 2 == 0 for n in numbers) # 检查是否有元素满足条件 def test_floating_point(): """浮点数近似断言:处理浮点数精度问题""" result = 0.1 + 0.2 # 计算0.1+0.2 assert result == pytest.approx(0.3, rel=1e-5) # 使用approx进行近似比较 # -------------------- # 2. 夹具(fixture)系统详解 # -------------------- @pytest.fixture(scope="module") # 定义模块级夹具,整个测试模块共享 def database_connection(): """模块级夹具 - 整个测试模块共享""" print("\n>>> 创建数据库连接 (模块级)") # 夹具初始化时打印信息 conn = {"status": "connected", "version": "1.2.3"} # 创建模拟数据库连接对象 yield conn # 返回连接对象,测试结束后继续执行清理 print("\n>>> 关闭数据库连接 (模块级)") # 夹具清理时打印信息 @pytest.fixture # 定义函数级夹具(默认作用域) def temporary_user(database_connection): # 依赖database_connection夹具 """函数级夹具 - 依赖其他夹具""" print("\n>> 创建临时用户") # 夹具初始化时打印信息 user = {"id": 1001, "name": "test_user", "created_at": datetime.now()} # 创建模拟用户 yield user # 返回用户对象,测试结束后继续执行清理 print("\n>> 删除临时用户") # 夹具清理时打印信息 def test_user_creation(temporary_user): # 使用temporary_user夹具 """测试用户创建:验证夹具功能""" assert temporary_user["id"] == 1001 # 验证用户ID assert "test" in temporary_user["name"] # 验证用户名包含特定字符串 # -------------------- # 3. 参数化高级用法 (修复版) # -------------------- def is_prime(n): """判断质数的函数:检查数字是否为质数""" if n < 2: # 小于2的数不是质数 return False for i in range(2, int(n ** 0.5) + 1): # 只需检查到平方根 if n % i == 0: # 如果可整除则不是质数 return False return True # 通过所有检查则是质数 # 修复的参数化测试 @pytest.mark.parametrize( # 参数化测试装饰器 "number, expected", # 参数名称 [ # 参数值列表 (2, True), # 普通参数 (3, True), # 普通参数 (4, False), # 普通参数 (17, True), # 普通参数 (25, False), # 普通参数 pytest.param(1, False, id="edge_case_1"), # 使用pytest.param定义特殊参数 pytest.param(0, False, id="edge_case_0"), # 使用pytest.param定义特殊参数 ], ids=lambda param: f"num_{param[0]}" if isinstance(param, tuple) else param.id # 智能ID生成函数 ) def test_prime_numbers(number, expected): """测试质数判断函数:验证is_prime函数""" assert is_prime(number) == expected # 验证函数结果与预期一致 # -------------------- # 4. 标记系统高级应用 # -------------------- @pytest.mark.slow # 标记为慢速测试 @pytest.mark.integration # 标记为集成测试 def test_external_api_call(): """集成测试:模拟外部API调用""" import time # 导入时间模块 time.sleep(1.5) # 模拟API调用延迟 assert True # 简单断言 @pytest.mark.skipif(sys.version_info < (3, 8), reason="需要Python 3.8+") # 条件跳过装饰器 def test_walrus_operator(): """测试海象运算符:验证Python 3.8+特性""" data = [1, 2, 3, 4, 5] # 测试数据 if (n := len(data)) > 3: # 使用海象运算符 assert n == 5 # 验证结果 @pytest.mark.xfail(sys.platform == "win32", reason="Windows平台已知问题") # 预期失败装饰器 def test_filesystem_case_sensitivity(): """测试文件系统大小写敏感性:验证不同OS行为差异""" with open("tempfile.txt", "w") as f: # 创建临时文件 f.write("test") # 写入内容 assert not os.path.exists("TEMPFILE.txt") # Linux/Mac大小写敏感 os.remove("tempfile.txt") # 清理测试文件 # -------------------- # 5. 异常处理与警告 # -------------------- def test_expected_exception(): """测试预期异常:验证异常处理""" with pytest.raises(ValueError) as exc_info: # 捕获预期异常 int("not_a_number") # 故意引发异常的操作 assert "invalid literal" in str(exc_info.value) # 验证异常信息 def test_warnings(): """测试警告捕获:验证警告处理""" with pytest.warns(DeprecationWarning): # 捕获预期警告 import warnings # 导入警告模块 warnings.warn("This is deprecated", DeprecationWarning) # 发出警告 # -------------------- # 6. Mock与猴子补丁 # -------------------- def fetch_weather_data(api_url): """获取天气数据的函数:模拟外部API调用""" # 实际实现会调用外部API return {"temp": 25, "condition": "sunny"} # 返回模拟数据 def test_mock_external_api(): """使用Mock替代外部API调用:验证mock功能""" # 创建Mock替代实际函数 with patch(__name__ + ".fetch_weather_data") as mock_weather: # 使用patch上下文管理器 mock_weather.return_value = {"temp": 30, "condition": "cloudy"} # 设置mock返回值 result = fetch_weather_data("https://weather-api.com") # 调用被mock的函数 assert result["temp"] == 30 # 验证mock结果 mock_weather.assert_called_once_with("https://weather-api.com") # 验证调用参数 # -------------------- # 7. 临时文件与目录 # -------------------- def test_create_temp_files(tmp_path): # 使用pytest内置的tmp_path夹具 """测试临时文件操作:验证文件系统功能""" # 创建临时目录结构 data_dir = tmp_path / "data" # 创建子目录路径 data_dir.mkdir() # 创建目录 # 创建文件 file_path = data_dir / "test.csv" # 创建文件路径 file_path.write_text("id,name\n1,Alice\n2,Bob") # 写入文件内容 # 验证内容 content = file_path.read_text() # 读取文件内容 assert "Alice" in content # 验证内容包含Alice assert "Bob" in content # 验证内容包含Bob # ==================== # 第三部分:高级工程实践 # ==================== # -------------------- # 8. 测试覆盖率分析 # -------------------- """ 使用pytest-cov生成覆盖率报告: 1. 基本报告:pytest --cov=my_module 2. HTML报告:pytest --cov=my_module --cov-report=html 3. 阈值控制:pytest --cov=my_module --cov-fail-under=90 """ # 被测代码 def calculate_discount(price, discount_percent): """计算折扣后的价格:业务逻辑函数""" if discount_percent < 0 or discount_percent > 100: # 验证折扣率范围 raise ValueError("折扣率必须在0-100之间") # 抛出异常 return price * (1 - discount_percent / 100) # 计算折扣后价格 # 测试用例 def test_discount_calculation(): """测试折扣计算:正常情况""" assert calculate_discount(100, 20) == 80 # 验证20%折扣 assert calculate_discount(50, 10) == 45 # 验证10%折扣 def test_discount_edge_cases(): """测试折扣计算:边界情况""" with pytest.raises(ValueError): # 验证异常 calculate_discount(100, -5) # 负折扣率 with pytest.raises(ValueError): # 验证异常 calculate_discount(100, 110) # 超过100%的折扣率 # -------------------- # 9. 并行测试执行 # -------------------- """ 使用pytest-xdist进行并行测试: 1. 安装:pip install pytest-xdist 2. 运行:pytest -n auto # 自动检测CPU核心数 3. 指定核心数:pytest -n 4 注意:确保测试是独立的,无共享状态 """ @pytest.mark.parametrize("x", range(10)) # 参数化测试,10组数据 def test_parallel_execution(x): """模拟大量可并行测试:验证并行执行能力""" assert x * 0 == 0 # 简单断言 # -------------------- # 10. 自定义夹具参数化 # -------------------- def generate_test_data(): """生成测试数据组合:创建参数化数据""" return [ # 返回测试数据列表 {"input": "admin", "role": "administrator"}, # 管理员数据 {"input": "user", "role": "standard"}, # 普通用户数据 {"input": "guest", "role": "limited"} # 访客数据 ] @pytest.fixture(params=generate_test_data()) # 参数化夹具 def user_account(request): # request提供参数信息 """参数化夹具:根据参数生成不同用户账户""" return request.param # 返回当前参数 def test_user_roles(user_account): # 使用参数化夹具 """测试不同用户角色:验证权限系统""" assert user_account["role"] in ["administrator", "standard", "limited"] # 验证角色 # ==================== # 第六部分:完整测试示例 # ==================== class UserManagementSystem: """用户管理系统模拟类:业务逻辑封装""" def __init__(self): """初始化用户系统""" self.users = {} # 用户存储字典 self.next_id = 1 # 下一个用户ID def add_user(self, name, email): """添加用户:创建新用户""" if not email or "@" not in email: # 验证邮箱格式 raise ValueError("无效的邮箱地址") # 无效邮箱抛出异常 user_id = self.next_id # 分配用户ID self.users[user_id] = {"name": name, "email": email} # 存储用户信息 self.next_id += 1 # ID自增 return user_id # 返回新用户ID def get_user(self, user_id): """获取用户信息:根据ID查询用户""" return self.users.get(user_id) # 返回用户信息或None def delete_user(self, user_id): """删除用户:根据ID删除用户""" if user_id not in self.users: # 检查用户是否存在 raise KeyError("用户不存在") # 不存在则抛出异常 del self.users[user_id] # 删除用户 @pytest.fixture # 定义用户系统夹具 def user_system(): """用户管理系统夹具:创建预配置的用户系统""" system = UserManagementSystem() # 创建用户系统实例 # 添加初始用户 system.add_user("Admin", "admin@example.com") # 添加管理员用户 return system # 返回系统实例 class TestUserManagement: # 测试类 """用户管理系统测试套件:完整业务逻辑测试""" def test_add_user(self, user_system): # 测试添加用户 """测试添加用户:验证用户创建功能""" user_id = user_system.add_user("Alice", "alice@example.com") # 添加新用户 assert user_id == 2 # 验证用户ID(初始用户已占1) assert user_system.get_user(2)["name"] == "Alice" # 验证用户名 def test_add_invalid_email(self, user_system): # 测试无效邮箱 """测试无效邮箱:验证异常处理""" with pytest.raises(ValueError) as excinfo: # 捕获预期异常 user_system.add_user("Bob", "invalid-email") # 添加无效邮箱用户 assert "无效的邮箱地址" in str(excinfo.value) # 验证异常信息 def test_delete_user(self, user_system): # 测试删除用户 """测试删除用户:验证用户删除功能""" user_system.delete_user(1) # 删除初始用户 with pytest.raises(KeyError): # 捕获预期异常 user_system.get_user(1) # 尝试获取已删除用户 @pytest.mark.parametrize("email", [ # 参数化测试 "test@example.com", # 标准邮箱 "user.name@domain.co", # 带点的邮箱 "unicode@例子.中国" # 国际化邮箱 ]) def test_valid_emails(self, user_system, email): # 测试有效邮箱格式 """参数化测试有效邮箱格式:验证多种邮箱格式""" user_id = user_system.add_user("Test", email) # 添加不同格式邮箱的用户 assert user_id > 1 # 验证用户创建成功 # ==================== # 修复后的执行部分 # ==================== if __name__ == "__main__": # 主程序入口 import subprocess # 导入子进程模块,用于执行外部命令 import os # 导入操作系统接口模块 import sys # 导入系统模块 # 获取当前文件所在目录的绝对路径 current_dir = os.path.dirname(os.path.abspath(__file__)) # 构建正确的pytest命令列表 command = [ "pytest", # pytest主命令 __file__, # 当前文件路径 "-v", # 详细输出模式 f"--html={os.path.join(current_dir, 'test_report.html')}", # HTML报告输出路径 f"--cov={current_dir}", # 指定覆盖率测量范围(当前目录) "--cov-report=html" # 生成HTML格式的覆盖率报告 ] # 添加Mac系统特定优化 if sys.platform == "darwin": # 检查是否为macOS系统 command.append("--durations=10") # 显示最慢的10个测试 command.append("--color=yes") # 启用彩色输出 # 打印执行信息 print("=" * 50) # 分隔线 print("执行测试命令:") # 标题 print(" ".join(command)) # 打印完整命令 print("=" * 50) # 分隔线 try: # 执行测试命令 result = subprocess.run(command, check=True) # 运行pytest命令 # 构建报告文件路径 report_path = os.path.join(current_dir, "test_report.html") # HTML报告路径 cov_path = os.path.join(current_dir, "htmlcov", "index.html") # 覆盖率报告路径 # 打印报告信息 print("\n" + "=" * 50) # 分隔线 print("测试报告已生成:") # 标题 print(f"• 测试报告: file://{report_path}") # 测试报告路径 print(f"• 覆盖率报告: file://{cov_path}") # 覆盖率报告路径 print("=" * 50) # 分隔线 # 退出程序,返回pytest的退出码 sys.exit(result.returncode) except subprocess.CalledProcessError as e: # 处理命令执行错误 print(f"测试执行失败,退出码: {e.returncode}") # 打印错误信息 print("请检查是否安装了必要插件: pip install pytest pytest-html pytest-cov") # 提示安装插件 sys.exit(e.returncode) # 返回错误码 except FileNotFoundError: # 处理pytest命令未找到错误 print("错误:未找到pytest命令") # 打印错误信息 print("请确保已安装pytest:pip install pytest pytest-html pytest-cov") # 提示安装 sys.exit(1) # 返回错误码 这是我给0基础同学讲的pytest课,我觉得不太好,重新设计下
09-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值