...

Pytest测试框架基础及进阶

Pytest测试框架基础

image

Pytest测试框架介绍

Pytest是Python一款三方测试框架,用于编写和运行单元测试、集成测试和功能测试。Pytest测试框架具有简单、灵活、易于扩展等特点,被广泛应用于Python项目的测试工作中。

Pytest主要特点:

  • 简单易用:Pytest测试框架的API简单易用,可以快速编写测试用例。
  • 灵活多样:Pytest测试框架支持多种测试方式,包括函数式测试、类式测试、参数化测试、fixture测试等。
  • 插件机制:Pytest测试框架支持插件机制,可以通过插件扩展测试框架的功能。
  • 断言机制:Pytest测试框架支持多种断言方式,包括assert语句、assert关键字、assert表达式等。
  • 报告机制:Pytest测试框架支持生成多种测试报告,包括控制台报告、HTML报告、JUnit报告等。

安装方法:

$ pip install pytest

Pytest基本用法

编写及运行测试用例

  1. 新建test开头的测试脚本,如test_calc.py,编写测试函数 或 测试类
def test_add(): # 测试函数需以test_开头
    “”“测试加法”“”
    s = 1 + 2
    assert s == 3, f'断言失败, {s} != 3' # 断言

class TestAdd:  # 测试类
        def test_add_01(self):  # 测试方法
        “”“测试加法01”“”
            s = 1 + 2
             assert s == 3, f'断言失败, {s} != 3'  # 断言
  1. 运行测试用例
$ pytest test_calc.py

if __name__ == '__main__':
    import pytest
    pytest.main([__file__]) # pytest测试当前文件

测试准备及清理

Pytest中可以使用不同范围的setup/teardown方法进行测试准备及清理。

def setup_module():
    print('测试模块准备')

def teardown_module():
    print('测试模块清理')

def setup_function():
    print('测试函数准备')

def teardown_function():
    print('测试函数清理')

def test_add():  # 测试函数
    """测试加法"""
    s = 1 + 2
    assert s == 3, f'断言失败, {s} != 3'  # 断言

class TestAdd:  # 测试类
 def setup_class(self):
        print('测试类准备')

    def teardown_class(self):
        print('测试类清理')

    def setup_method(self):
        print('测试方法准备')

    def teardown_method(self):
        print('测试方法清理')

    def test_add_01(self):  # 测试方法
        """测试加法01"""
        s = 1 + 2
        assert s == 3, f'断言失败, {s} != 3'  # 断言

参数化(数据驱动)

Pytest中可以@pytest.mark.paramitrize()装饰器将每条数据变成一条用例。

@pytest.mark.parametrize('a', [1,2,3]) # 参数变量名,数据列表
def test_data(a):  # 需要添加和上面同名的参数
    """测试参数化数据"""
    print('a =', a)

支持多个参数变量,也支持为每个数据添加自定义说明(id)。

data = [(1,2,3), (0,0,0), (-1,2,1)]
ids = ['test1+2', 'test0+0', 'test-1+2’]

@pytest.mark.parametrize(‘a,b,excepted’, data, ids=ids)  # 多个参数变量名写到同一字符串里
def test_add(a,b,excepted):  # 需要添加和上面同名的参数
    “”“测试加法"""
    s = a + b
    assert s == excepted, f'断言失败, {s} != {excepted}'  # 断言

跳过及期望失败

Pytest中可以@pytest.mark.skip()@pytest.mark.skipif()装饰器来跳过或根据条件跳过用例。

无条件跳过

@pytest.mark.skip('待实现')
def test_sub():
    pass

或根据条件跳过

from platform import platform

@pytest.mark.skipif(platform == 'Windows', reason='不支持Windows')
def test_linux_cmd():
    pass

也可以使用@pytest.mark.xfail()来期望用例失败。

@pytest.mark.xfail(reason='期望失败,1+1!=3')
def test_add():
    assert 1 + 1 == 3

Pytest测试框架的命令行参数

用例挑选相关命令行参数

  • pytest <目录或文件路径1> <目录或文件路径2> :运行指定目录或文件中所有的测试用例,支持指定多个路径。
  • pytest <文件路径::测试函数名>:运行指定测试函数
  • pytest <文件路径::测试类名>:运行指定测试类中所有用例
  • pytest <文件路径::测试类::测试方法名>:运行指定测试类中指定测试方法
  • pytest –k=<正则表达式>:指定测试类/测试函数名称匹配规则,筛选指定用例。
  • pytest –m=<标签>:指定标签筛选用例,支持and、or、not,例如 –m 'api not web'

Pytest内置makers标记
在命令行使用pytest –markers可以查看所有可使用(包含用户已注册)标记。
@pytest.mark.filterwarnings(warning):过滤指定警告
@pytest.mark.skip(reason=None):无条件跳过用例个
@pytest.mark.skipif(condition, ..., *, reason=...):根据条件跳过用例
@pytest.mark.xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict):根据条件期望用例失败
@pytest.mark.parametrize(argnames, argvalues):参数化数据驱动
@pytest.mark.usefixtures(fixturename1, fixturename2, ...):标记引用(依赖)某些Fixture函数

报告(命令行显示)相关命令行参数

  • --verbosity=<数值>:指定显示详细级别,0-1,默认0。
  • -v / --verbose:提高显示详细级别
  • -q / --quiet:安静模式
  • -s:不捕获用例输出(用例print信息直接输出到命令行)
  • --capture=<捕获方法>:选择捕获用例输出到方法,支持fd、sys、no、tee-sys
  • --no-header:不显示头部信息(测试开始环境信息)
  • --no-summary:不显示运行总结
  • -r <字符>:显示额外测试状态的详情信息,支持f-失败用例,E-异常用例,s-跳过用例,x-预期失败用例,X-非预期成功用例,p-通过用例,P-通过用例带输出,a-所有成功用例,A-全部用例
  • --duration=<数字>:显示运行最慢的前几条用例
  • --duration-min: 设置运行时间最小阈值(大于该值视为运行较慢)

缓存(重复运行)相关命令行参数

  • --lf / --last-failed:运行上次失败的用例
  • --ff / --failed-first:先运行上次失败的用例,再运行其他用例
  • --nf / --last-failed:先运行新的测试用例文件,在运行其他用例
  • --cache-clear:清理之前测试的缓存结果
  • ... ...

用例收集相关命令行参数

  • --log-level=LEVEL:设置日志等级
  • --log-format=LOG_FORMAT:设置日志格式
  • --log-date-format=LOG_DATE_FORMAT:设置日志日期格式
  • --log-cli-level=LOG_CLI_LEVEL:设置命令行日志登记
  • --log-cli-format=LOG_CLI_FORMAT:设置命令行日志格式
  • --log-cli-date-format=LOG_CLI_DATE_FORMAT:设置命令行日志日期格式
  • --log-file=LOG_FILE:设置日志文件路径
  • --log-file-level=LOG_FILE_LEVEL:设置日志文件等级
  • --log-file-format=LOG_FILE_FORMAT:设置日志文件日志格式
  • --log-file-date-format=LOG_FILE_DATE_FORMAT:设置日志文件日期格式
  • --log-auto-indent=LOG_AUTO_INDENT:设置多行文本日志缩进,支持true|on, false|off 或整数值。
  • --log-disable=LOGGER_DISABLE:根据名称禁用某个logger,支持指定多次。

Pytest测试框架的配置项

pytest.ini文件[pytest]中常用配置项如下

配置项 说明
addopts 默认额外的参数;
cache_dir 缓存目录,用于缓存上次执行失败的用例;
markers 注册的自定义标记及说明;
norecursedirs 不遍历的目录;
testpaths 测试目录;
python_files 测试脚本匹配规则,默认test_开头的py文件视为用例,如果有的测试脚本不是以test_开头,可以配置为pythonfiles = *;
python_class 测试类匹配规则,默认Test开头的类视为测试类;
python_functions 测试函数及测试方法匹配规则,默认test_开头的函数及方法视为测试用例;
console_output_style 命令行输出样式,支持classic、progress、counts三种样式;
filterwarnings 过滤的警告;
xfail_strict 启用时,标记为xfail的用例通过时状态为Fail,否则为XPassed。

日志相关配置如下

配置项 说明
log_print 用例失败时是否显示相关日志;
log_cli 配置为ture时开启命令行日志;
log_file 配置日志文件路径,每次覆盖,不支持追加模式;v
log_cli_level/log_file_level 配置输出到命令行及文件的日志等级;
log_cli_format/log_file_format 配置输出到命令行及文件的日志格式;
log_cli_date_format/log_file_date_format 配置日志的日期格式。

Pytest测试框架进阶

Pytest测试框架的扩展(钩子)机制

钩子函数(Hooks)是一种特殊的函数,可以在执行过程中“顺带”执行一些自定义的操作。
Pytest测试框架提供了许多钩子函数,可以在测试过程的不同阶段执行自定义的操作。

image

  1. 初始化
  • 添加钩子pytest_addhooks
  • 添加参数pytest_addoption
  • 注册插件pytest_plugin_registered
  1. 启动
  • 启动命令行主流程pytest_cmdline_main
  • 生成配置 pytest_configure
  • 启动运行会话 pytest_sessionstart
  1. 收集用例
  • 判断是否忽略pytest_ignore_collect
  • 创建待收集文件对象pytest_collect_file
  • 创建待收集模块对象pytest_pycollect_makemodule
  • 开始收集当前模块用例pytest_collectstart
  • 创建当前模块收集报告对象pytest_make_collect_report
  • 创建待收集用例对象pytest_pycollect_makeitem
  • 生成用例pytest_generate_tests
  • 收集用例完毕pytest_itemcollected
  • 生成收集报告pytest_collectreport
  • 调整收集的用例pytest_collection_modifyitems
  • 用例收集完毕pytest_report_collectionfinish
  1. 执行测试
  • 记录警告信息pytest_warning_recorded
  • 启动执行循环pytest_runtestloop
  • 开始执行当前用例pytest_runtest_protocol
  • 开始记录日志pytest_runtest_logstart
  • 开始运行测试准备pytest_runtest_setup
  • 执行某个测试准备方法pytest_fixture_setup
  • 执行测试用例函数pytest_pyfunc_call
  • 开始运行测试清理pytest_runtest_teardown
  • 执行某个测试清理方法pytest_fixture_post_finalizer
  • 停止记录日志pytest_runtest_logfinish
  1. 结束
  • 测试会话结束 pytest_sessionfinish
  • 生成命令行测试总结 pytest_terminal_summary
  • 恢复配置 pytest_unconfigure

初始化时的钩子函数

  • pytest_addhooks(pluginmanager):添加新的钩子函数
  • pytest_addoption(parser, pluginmanager):添加命令行参数及ini配置项
  • pytest_configure(config):初始化配置
  • pytest_unconfigure(config):恢复配置,退出测试进程前执行
  • pytest_sessionstart(session):测试会话开始
  • pytest_sessionfinish(session, exitstatus):测试会话结束
  • pytest_plugin_registered(plugin, manager):注册了新插件

示例-添加自定义参数及配置项

def pytest_addoption(parser):  # 初始化是的钩子放假,用来增加命令行参数
    # 添加命令行参数--send_email,执行时带上该参数则视为ture
    parser.addoption("--send_email", action="store_true", help="send email after test")
    # 添加邮件主题、收件人、正文配置项
    parser.addini('email_subject', help='test report email subject')
    parser.addini('email_receivers', help='test report email receivers')
    parser.addini('email_body', help='test report email body')

示例-修改配置-组装绝对路径

from datetime import datetime

def pytest_configure(config):  # 初始化是的配置方法
    log_file = config.getini('log_file')  # 如果配置文件配置了log_file
    if log_file:
        # 使用当前时间格式化后log_file名称,替换原有的配置
        # 如果配置的是不带时间格式的如log_file=last_run.log,格式化后不变,
        # 如果配置log_file=%Y%m%d%H%M%S.log,格式后成当前时间
        config.option.log_file = datetime.now().strftime(log_file)

启动时的钩子函数

  • pytest_load_initial_conftests(early_config, parser, args):调用初始化conftest.py文件(仅作为安装插件时)
  • pytest_cmdline_parse(pluginmanager, args):初始化config配置,解析命令行参数
  • pytest_cmdline_main(config):调用执行主命令行操作,启动测试主循环

收集用例时的钩子函数

  • pytest_collection(session):开始收集用例
  • pytest_ignore_collect(collection_path, path, config):判断是否忽略该目录
  • pytest_collect_file(file_path, path, parent):创建待收集文件对象
  • pytest_pycollect_makemodule(module_path, path, parent):创建待收集模块
  • pytest_pycollect_makeitem(collector, name, obj):创建待收集用例对象
  • pytest_generate_tests(metafunc):生成用例
  • pytest_make_parametrize_id(config, val, argname):生成参数化用例id
  • pytest_collection_modifyitems(session, config, items):调整收集结果
  • pytest_collection_finish(session):收集用例结束

示例-调整用例搜集结果-收集用例

import pytest

class TestCollection:
    def __init__(self):
        self.collected = []

    def pytest_collection_modifyitems(self, items):
        for item in items:
            self.collected.append(item.nodeid)

def get_testcases(testpath):
    coll = TestCollection()
    pytest.main([testpath, '--collect-only', '-q'], plugins=[coll]) # 指定插件
    return coll.collected

get_testcases('./testcases')

执行测试时的钩子函数

  • pytest_runtestloop(session):开始执行用例
  • pytest_runtest_protocol(item, nextitem):开始执行某条用例
  • pytest_runtest_logstart(nodeid, location):开始记录日志
  • pytest_runtest_logfinish(nodeid, location):停止记录日志
  • pytest_runtest_makereport(item, call):创建报告对象
  • pytest_runtest_setup(item):开始运行测试准备方法
  • pytest_runtest_call(item):开始调用测试方法
  • pytest_pyfunc_call(pyfuncitem):调用测试函数
  • pytest_runtest_teardown(item, nextitem):开始执行测试清理方法

报告相关钩子函数

  • pytest_collectstart(collector):开始收集某个模块的用例
  • pytest_make_collect_report(collector):为当前模块创建收集报告对象
  • pytest_itemcollected(item):收集到一条用例
  • pytest_collectreport(report):收集当前模块结束
  • pytest_deselected(items):排除部分用例
  • pytest_report_header(config, start_path, startdir):命令行输出收集信息
  • pytest_report_collectionfinish(config, start_path, startdir, items):收集完毕
  • pytest_report_teststatus(report, config):报告setup/用例/teardown执行状态
  • pytest_report_to_serializable(config, report):序列化报告内容
  • pytest_report_from_serializable(config, data):加载序列化报告内容
  • pytest_terminal_summary(terminalreporter, exitstatus, config):生成命令行运行总结
  • pytest_fixture_setup(fixturedef, request):执行某个测试准备方法
  • pytest_fixture_post_finalizer(fixturedef, request):执行某个测试清理方法
  • pytest_warning_recorded(warning_message, when, nodeid, location):记录到警告信息
  • pytest_runtest_logreport(report):输出setup/用例/teardown日志
  • pytest_assertrepr_compare(config, op, left, right):处理断言
  • pytest_assertion_pass(item, lineno, orig, expl):处理用例断言通过

示例-命令行总结-运行后额外操作

def pytest_terminal_summary(config):  # 生成报告时的命令行最终总结方法
    send_email = config.getoption("--send-email")
    email_receivers = config.getini('email_receivers').split(',')
    if send_email is True and email_receivers:
        log_file = config.getoption('log_file')
        email_subject = config.getini('email_subject') or 'TestReport'
        email_body = config.getini('email_body') or 'Hi'
        if email_receivers:
            print('发送邮件 ...')

调试/交互相关的钩子函数

  • pytest_internalerror(excrepr, excinfo):运行时出现内部错误
  • pytest_keyboard_interrupt(excinfo):运行时用户Ctrl+C中断测试
  • pytest_exception_interact(node, call, report):引发可交互的异常
  • pytest_enter_pdb(config, pdb):进入pdb调试器
  • pytest_leave_pdb(config, pdb):退出pdb调试器

Pytest测试框架的Fixture机制

在Pytest测试框架中,Fixture机制是一种用于管理测试用例依赖关系的机制。Fixture机制可以在测试用例执行之前和之后执行一些操作,例如创建测试数据、打开数据库连接、清理测试数据等。
在Pytest中使用@pytest.fixture装饰器装饰一个函数,该函数可以yield或return返回数据、对象或者一个函数。在测试用例中,使用@pytest.mark.usefixtures装饰器来或将Fixture函数名作为参数或使用Fixture。

import pytest

@pytest.fixture()
def user():
    return 'admin', '123456'

@pytest.fixture
def login(user):  # Fixture函数可以引用(依赖)其他Fixture函数
  # 使用的user参数实际为调用user()函数后的返回值
  username, password = user
    print(f'{username} 登录')
    token = 'abcdef'
    yield token  # yield上为测试准备,yield下为测试清理
  print('退出登录’)

def test_add_project(login):  # 引用(依赖)login方法
 # 使用login参数实际是调用login()函数的返回值即'abcdef'
    print('token =', login)

# 也可以标记使用指定Fixture函数,执行其测试准备即清理方法
@pytest.mark.userfixtures('login')
def test_add_project02():
    pass # 这种情况下拿不到Fixture函数的返回结果

Fixture生效范围

Fixture函数可以通过scope指定作用范围,Pytest中的Fixture函数支持以下5种范围:

  • Session会话级:scope=‘session’,运行一次Pytest算一次会话。运行期间只setup/teardown一次
    Package包级:scope=‘pacakge’,对每个包Python包setup/teardown一次
  • Module模块级:scope=‘module’,对每个Python脚本setup/teardown一次
  • Class级:scope=‘class’,对每个测试类setup/teardown一次
  • Function级:scope=‘function’,默认,每个测试函数或测试方法setup/teardown一次。
from selenium import webdriver

# 整个运行过程中仅启动关闭一次浏览器
@pytest.fixture(scope='session')
def driver():
    dr = webdriver.Chrome()
    yield dr
    dr.quit()

Fixture共享及同名覆盖

Fixture函数一般作为公用的辅助方法或全局变量来使用,因此需要在不同用例中都能使用。Pytest框架中使用固定名称的conftest.py文件,来集中管理Fixture函数 。
conftest.py文件同级即下级目录中的所有用例可以无需导入,直接使用conftest.py中的所有Fixture函数。
conftest.py文件在不同的目录中可以有多个(生效范围不同),同时用例文件中也可以编写Fixture函数,当Fixture函数有重名时,采用“就近”原则,离当前用例最近的Fixture函数生效。

@pytest.fixture(scope='session')
def base_url():
    return 'http://localhost:3000'

def test_base_url(base_url):
    print('base_url =', base_url)

if __name__ == '__main__':
    # pip install pytest-base-url
    import pytest
    pytest.main([__file__, '-sq','--base-url=http://www.xxx.com'])

conftest.py文件的作用
用来编写Fixture函数
编写钩子Hooks函数
导入其所在目录或包的路径(可以确保项目根目录被导入)

conftest.py所在导入规则:
如果conftest.py所在目录没有__init__.py文件,则Pytest自动导入conftest.py所在目录。
如果有则向上找到最上层的包(最上层一个包含__init__.py的目录),并导入包的所在目录,以确保conftest.py可以使用

Fixture返回函数对象

由于Fixture函数不接收普通参数,无法根据用例需要进行特定的测试准备,此时我们一般需要封装一个实用函数并导入使用,也可以把函数定义到Fixture函数的内部,并返回该函数对象,这样用例无需导入便可直接使用功能函数。示例如下:

常规导入模块方式
auth.py

def login(username, password):
    # ...
    token = 'abcdef'
    return token

test_project_api.py

from .auth import login

def test_add_project():
    token = login('admin', 'abc123')
    # ...

封装成Fixture方式
conftest.py

@pytest.fixture
def login():
    def _login(username, password):
        # ...
        token = ‘abcdef’
        return token
    return _login

test_project_api.py

def test_add_project(login):  # 无需导入直接使用
 # 使用的login参数实际是login()函数的调用结果,
 # 即内部的_login函数
  token = login('admin', 'abc123')
    # ...

Pytest测试框架的参数化机制

Pytest提供了三种参数化方式:

使用@pytest.mark.paramitrize()标记进行数据驱动测试

users = [
    ('admin', '123456'),
    ('kevin','abc123'),
    ('lily', 'abcdef')
]

@pytest.mark.parametrize('user,pwd', users)
def test_login(user, pwd):
    print('登录', user, pwd)

使用Fixture函数的params参数,进行参数化测试准备

 users = [
    ('admin', '123456'),
    ('kevin','abc123'), 
    ('lily', 'abcdef')
]

@pytest.fixture(params=users)
def login(request): # request是系统内置Fixture
    user, pwd = request.param
    print('登录', user, pwd)

def test_add_project(login):    """测试不同用户添加项目"""    
    # ...

使用钩子函数pytest_generate_tests(),动态成多条用例
* conftest.py*

def pytest_addoption(parser): # 添加命令行参数
     parser.addoption("--filepath", action="append",default=[], help="run file list")

def pytest_generate_tests(metafunc):
    if "filepath" in metafunc.fixturenames:
        filepaths = metafunc.config.getoption("filepath")
        metafunc.parametrize("filepath",filepaths)

test_a.py

def test_a(filepath):
    print('test_a', filepath)

Pytest测试框架的收集机制

在Pytest测试框架中,收集机制是一种用于自动收集测试用例的机制。Pytest测试框架会自动搜索指定目录下的测试文件,并收集其中的测试用例。
以下是Pytest测试框架的收集机制的一些常用用法:

默认收集规则
Pytest测试框架的默认收集规则是搜索以test_开头或以_test结尾的文件,并收集其中的测试用例。例如,test_sum.py、sum_test.py、test_sum.py::test_sum等文件和测试用例都会被收集。
根据正则匹配收集用例
可以使用pytest命令的-k选项来指定收集指定目录下的测试文件。例如,可以使用以下命令来收集tests目录下的所有测试文件:

$ pytest -k tests

在运行上面的命令后,Pytest测试框架会自动搜索tests目录下的测试文件,并收集其中的测试用例。

自定义收集规则
可以使用pytest_collection_modifyitems钩子函数来自定义收集规则。
例如,可以使用以下代码来自定义收集规则:

def pytest_collection_modifyitems(items):
    for item in items:
        if "slow" in item.keywords:
            item.add_marker(pytest.mark.slow)

在上面的代码中,使用pytest_collection_modifyitems钩子函数来修改测试用例列表。如果测试用例的关键字中包含slow,则为该测试用例添加@pytest.mark.slow标记。

Pytest测试框架高级应用

Pytest插件开发

  1. 新建一个项目,项目中新建一个包例如pytest_data_file,包含空白的__init__.py文件和一个plugin.py文件。
  2. 在plugin.py中使用添加自动参数及Fixture函数。
  3. 编辑项目中的setup.py文件,在setup函数中指定入口entry_points为pytest11,例如:
entry_points={
     'pytest11': [
        'pytest-data-file = pytest_data_file.plugin',
    ]
}

image

以下是一个自定义标记插件开发的示例,为用例添加owner归宿人标记,并可以根据—owner筛选用例。

示例插件安装脚本setup.py代码
from setuptools import setup, find_packages
setup(
    name='pytest-data-file',
    version='0.1',
    author="Han Zhichao",
    author_email='superhin@126.com',
    description='Fixture "data" and for test from yaml file',
    license="MIT license",
    url='https://github.com/hanzhichao/pytest-data-file',
    include_package_data=True,
    packages=find_packages(include=['pytest_data_file']),
    zip_safe=True,
    setup_requires=['pytest-runner'],
    install_requires=['pytest','pytest-runner','pyyaml'],
    entry_points={
        'pytest11': [
            'pytest-data-file = pytest_data_file.plugin',
        ]
    }
)

示例插件核心代码
import pytest

def pytest_configure(config):
    config.addinivalue_line(
        "markers",
        "owner: Mark tests with owner and run tests by use --owner")

def pytest_addoption(parser):
    parser.addoption('--owner', action='append', help='Run tests which mark the same owners')

@pytest.fixture
def owner(config):
    """获取当前指定的owner"""
    return config.getoption('--owner’)

def pytest_collection_modifyitems(session, config, items):
    selected_owners = config.getoption('--owner')
    print(selected_owners)
    if selected_owners == None:
        return

    deselected_items = set()
    for item in items:
        for marker in item.iter_markers('owner'):
            [marker_owner] = marker.args
            if marker_owner not in selected_owners:
                deselected_items.add(item)
    selected_items = [item for item in items if item not in deselected_items]
    items[:] = selected_items
    config.hook.pytest_deselected(items=list(deselected_items))

练习

  1. 发布一款Pytest插件
  2. 基于某款测试框架(pytest, unittest, qtaf..) 编写一个简单的用例测试平台(基于文件系统的测试平台)
  • 用例列表页面,可以选择调试用例,可以选择一批用例保存为一个测试计划
  • 用例计划列表,可以选择执行某个测试计划,生成测试报告
  • 测试报告页面
posted @ 2023-10-10 19:16  韩志超  阅读(6392)  评论(0编辑  收藏  举报