之前写个函数是这样定义的
def test_TC41306_create_contact_point(alarm_contact_point_page: Page):
alarm_contact_point_page.pause()
alarm_contact_point_page.get_by_role("button", name="创建联络点").click()
alarm_contact_point_page.get_by_placeholder("请输入名称").fill("test_Webhook")
alarm_contact_point_page.locator("form i").click()
alarm_contact_point_page.locator("li").filter(has_text="Webhook").click()
alarm_contact_point_page.get_by_placeholder("请输入内容").fill("http://172.17.140.249/")
alarm_contact_point_page.get_by_role("button", name="保存联络点").click()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").to_be_in_viewport()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").not_to_be_in_viewport()
alarm_contact_point_page.get_by_role("row", name="test_Webhook").get_by_role("button").click()
alarm_contact_point_page.locator("tr", has_text="test_Webhook").locator("a", has_text="编辑").click()
alarm_contact_point_page.wait_for_timeout(1000)
CommonUtil.assert_screenshot(alarm_contact_point_page.get_by_role("dialog", name="编辑联络点"), [], "test_TC41306_create_contact_point_1.png",True)
alarm_contact_point_page.get_by_label("close 编辑联络点").click()
alarm_contact_point_page.get_by_role("button", name="创建联络点").click()
alarm_contact_point_page.get_by_placeholder("请输入名称").fill("test_Email")
alarm_contact_point_page.locator("form i").click()
alarm_contact_point_page.locator("li").filter(has_text="Email").click()
alarm_contact_point_page.get_by_placeholder("请输入内容").fill("yuling.zhang@greatdb.com")
alarm_contact_point_page.get_by_role("button", name="保存联络点").click()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").to_be_in_viewport()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").not_to_be_in_viewport()
alarm_contact_point_page.get_by_role("row", name="test_Email").get_by_role("button").click()
alarm_contact_point_page.locator("tr", has_text="test_Email").locator("a", has_text="编辑").click()
alarm_contact_point_page.wait_for_timeout(1000)
CommonUtil.assert_screenshot(alarm_contact_point_page.get_by_role("dialog", name="编辑联络点"), [], "test_TC41306_create_contact_point_2.png",True)
alarm_contact_point_page.get_by_label("close 编辑联络点").click()
alarm_contact_point_page.get_by_role("button", name="创建联络点").click()
alarm_contact_point_page.get_by_placeholder("请输入名称").fill("test_Syslog")
alarm_contact_point_page.locator("form i").click()
alarm_contact_point_page.locator("li").filter(has_text="Syslog").click()
alarm_contact_point_page.get_by_placeholder("请输入内容").first.fill("172.17.140.249")
alarm_contact_point_page.locator("textarea").fill("CONTROL|+|平台|+|1002|+|1001|+|{{ address }}|+|{{ hostname }}|+|(RECOVER) {{ alertname }}|+|{{ resource }}|+|{{ value }}|+|DB|+|GREATDB|+|{{ resource_type }}|+|{{ severity_int }}|+|触发值: {{ value }} {{ expr_condition }} 告警阈值: {{ threshold_value }}|+|{{ timestamp }}")
alarm_contact_point_page.get_by_placeholder("请输入内容").nth(2).fill("20MNIBUS")
alarm_contact_point_page.get_by_label("描述文字").first.fill("11")
alarm_contact_point_page.get_by_label("描述文字").nth(1).fill("12")
alarm_contact_point_page.get_by_label("描述文字").nth(2).fill("13")
alarm_contact_point_page.get_by_label("描述文字").nth(3).fill("14")
alarm_contact_point_page.get_by_label("描述文字").nth(4).fill("15")
alarm_contact_point_page.get_by_role("radio", name="false").click()
alarm_contact_point_page.get_by_role("button", name="保存联络点").click()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").to_be_in_viewport()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").not_to_be_in_viewport()
alarm_contact_point_page.get_by_role("row", name="test_Syslog").get_by_role("button").click()
alarm_contact_point_page.locator("tr", has_text="test_Syslog").locator("a", has_text="编辑").click()
alarm_contact_point_page.wait_for_timeout(1000)
CommonUtil.assert_screenshot(alarm_contact_point_page.get_by_role("dialog", name="编辑联络点"), [], "test_TC41306_create_contact_point_3.png",True)
alarm_contact_point_page.get_by_label("close 编辑联络点").click()
alarm_contact_point_page.get_by_role("button", name="创建联络点").click()
alarm_contact_point_page.get_by_placeholder("请输入名称").fill("test_SMC")
alarm_contact_point_page.locator("form i").click()
alarm_contact_point_page.locator("li").filter(has_text="SMC").click()
alarm_contact_point_page.get_by_placeholder("请输入内容").first.fill("1")
alarm_contact_point_page.get_by_placeholder("请输入内容").nth(1).fill("2")
alarm_contact_point_page.get_by_placeholder("请输入内容").nth(2).fill("1")
alarm_contact_point_page.get_by_placeholder("请输入内容").nth(3).fill("2")
alarm_contact_point_page.get_by_placeholder("请输入内容").nth(4).fill("http://172.17.140.249/")
alarm_contact_point_page.get_by_role("button", name="保存联络点").click()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").to_be_in_viewport()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").not_to_be_in_viewport()
alarm_contact_point_page.get_by_role("row", name="test_SMC").get_by_role("button").click()
alarm_contact_point_page.locator("tr", has_text="test_SMC").locator("a", has_text="编辑").click()
alarm_contact_point_page.wait_for_timeout(1000)
CommonUtil.assert_screenshot(alarm_contact_point_page.get_by_role("dialog", name="编辑联络点"), [], "test_TC41306_create_contact_point_4.png",True)
alarm_contact_point_page.get_by_label("close 编辑联络点").click()
这里有非常多的重复操作,可以简单的修改为这样的写法
def test_TC41306_create_contact_point(alarm_contact_point_page: Page):
"""
测试创建不同类型的联络点。
"""
def create_and_validate_contact_point(name: str, contact_type: str, content: dict, screenshot_name: str):
"""
创建联络点并验证保存和编辑操作。
:param name: 联络点名称
:param contact_type: 联络点类型(如 "Webhook", "Email", "Syslog", "SMC")
:param content: 联络点内容(字典形式,键为字段类型,值为内容列表)
:param screenshot_name: 截图文件名
"""
# 点击“创建联络点”按钮
alarm_contact_point_page.get_by_role("button", name="创建联络点").click()
# 填写名称
alarm_contact_point_page.get_by_placeholder("请输入名称").fill(name)
# 选择联络点类型
alarm_contact_point_page.locator("form i").click()
alarm_contact_point_page.locator("li").filter(has_text=contact_type).click()
# 填写内容
for field_type, field_values in content.items():
if field_type == "placeholder":
for placeholder_text, value, index in field_values:
if index == 0:
alarm_contact_point_page.get_by_placeholder(placeholder_text).first.fill(value)
else:
alarm_contact_point_page.get_by_placeholder(placeholder_text).nth(index).fill(value)
elif field_type == "textarea":
alarm_contact_point_page.locator("textarea").fill(field_values)
elif field_type == "label":
for index, value in enumerate(field_values):
if index == 0:
alarm_contact_point_page.get_by_label("描述文字").first.fill(value)
else:
alarm_contact_point_page.get_by_label("描述文字").nth(index).fill(value)
elif field_type == "radio":
alarm_contact_point_page.get_by_role("radio", name=field_values).click()
# 保存联络点
alarm_contact_point_page.get_by_role("button", name="保存联络点").click()
# 验证保存成功的提示信息
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").to_be_in_viewport()
expect(alarm_contact_point_page.get_by_text("保存成功"), "未弹出提示信息").not_to_be_in_viewport()
# 编辑联络点并截图
alarm_contact_point_page.get_by_role("row", name=name).get_by_role("button").click()
alarm_contact_point_page.locator("tr", has_text=name).locator("a", has_text="编辑").click()
CommonUtil.assert_screenshot(alarm_contact_point_page.get_by_role("dialog", name="编辑联络点"), [], screenshot_name, True)
alarm_contact_point_page.get_by_label("close 编辑联络点").click()
# 测试创建 Webhook 联络点
create_and_validate_contact_point(
name="test_Webhook",
contact_type="Webhook",
content={
"placeholder": [("请输入内容", "http://172.17.140.249/", 0)]
},
screenshot_name="test_TC41306_create_contact_point_1.png"
)
# 测试创建 Email 联络点
create_and_validate_contact_point(
name="test_Email",
contact_type="Email",
content={
"placeholder": [("请输入内容", "yuling.zhang@greatdb.com", 0)]
},
screenshot_name="test_TC41306_create_contact_point_2.png"
)
# 测试创建 Syslog 联络点
create_and_validate_contact_point(
name="test_Syslog",
contact_type="Syslog",
content={
"placeholder": [("请输入内容", "172.17.140.249", 0), ("请输入内容", "20MNIBUS", 1)],
"textarea": "CONTROL|+|平台|+|1002|+|1001|+|{{ address }}|+|{{ hostname }}|+|(RECOVER) {{ alertname }}|+|{{ resource }}|+|{{ value }}|+|DB|+|GREATDB|+|{{ resource_type }}|+|{{ severity_int }}|+|触发值: {{ value }} {{ expr_condition }} 告警阈值: {{ threshold_value }}|+|{{ timestamp }}",
"label": ["11", "12", "13", "14", "15"],
"radio": "false"
},
screenshot_name="test_TC41306_create_contact_point_3.png"
)
# 测试创建 SMC 联络点
create_and_validate_contact_point(
name="test_SMC",
contact_type="SMC",
content={
"placeholder": [
("请输入内容", "1", 0),
("请输入内容", "2", 1),
("请输入内容", "1", 2),
("请输入内容", "2", 3),
("请输入内容", "http://172.17.140.249/", 4)
]
},
screenshot_name="test_TC41306_create_contact_point_4.png"
)
疑问
为什么要写成这样 ???????????
def test_TC41306_create_contact_point(alarm_contact_point_page: Page):
"""
测试创建不同类型的联络点。
"""
def create_and_validate_contact_point(name: str, contact_type: str, content: dict, screenshot_name: str): 而不是只定义一个函数
这是一个很好的问题!将代码拆分为 主函数 和 内部函数 的设计是为了提高代码的 可读性、复用性 和 可维护性。以下是对这种设计的具体解释:
代码复用性
在测试 test_TC41306_create_contact_point 中,创建和验证联络点的逻辑是重复的(如填写名称、选择类型、填写内容、保存、验证提示信息、编辑并截图等)。如果将这些逻辑直接写在主函数中,会导致大量重复代码。
通过将这部分逻辑提取到一个内部函数 create_and_validate_contact_point 中,可以实现代码复用。每次测试不同类型的联络点时,只需调用这个内部函数并传入不同的参数即可。
可读性
将复杂逻辑拆分为多个函数,可以使代码更清晰、更易读。主函数 test_TC41306_create_contact_point 专注于测试流程的组织,而内部函数 create_and_validate_contact_point 专注于具体的操作和验证逻辑。
这种分层设计使得代码更易于理解:
主函数:描述测试的整体流程(如测试哪些联络点类型)。
内部函数:描述具体的操作和验证逻辑(如如何创建和验证联络点)。
这里我们再解释下 主函数和内部函数的执行逻辑
代码结构
def test_TC41306_create_contact_point(alarm_contact_point_page: Page):
# 内部函数定义
def create_and_validate_contact_point(...):
...
# 调用内部函数 4 次
create_and_validate_contact_point(...) # Webhook
create_and_validate_contact_point(...) # Email
create_and_validate_contact_point(...) # Syslog
create_and_validate_contact_point(...) # SMC
执行逻辑详解
步骤 1:主函数启动
当测试框架(如 Pytest)运行 test_TC41306_create_contact_point 时:
主函数 test_TC41306_create_contact_point 被调用。
参数 alarm_contact_point_page(Playwright 的 Page 对象)被传入。
步骤 2:内部函数定义
主函数内部定义了 create_and_validate_contact_point 函数:
此时内部函数只是被定义,并未执行。
它像一个“工具包”,等待被调用。
步骤 3:调用内部函数
主函数依次调用 create_and_validate_contact_point 4 次,每次传入不同的参数:
python
create_and_validate_contact_point(name=“test_Webhook”, …) # 第一次调用
create_and_validate_contact_point(name=“test_Email”, …) # 第二次调用
create_and_validate_contact_point(name=“test_Syslog”, …) # 第三次调用
create_and_validate_contact_point(name=“test_SMC”, …) # 第四次调用
步骤 4:内部函数执行
每次调用 create_and_validate_contact_point 时:
根据传入的参数(如 name=“test_Webhook”),执行以下操作:
点击“创建联络点”按钮。
填写名称。
选择联络点类型。
填写内容。
保存并验证提示信息。
编辑联络点并截图。
执行完成后,返回到主函数,继续下一次调用。
参数传递示例
以 Webhook 联络点 为例:
create_and_validate_contact_point(
name="test_Webhook",
contact_type="Webhook",
content={"placeholder": [("请输入内容", "http://172.17.140.249/", 0)]},
screenshot_name="test_TC41306_create_contact_point_1.png"
)
参数如何生效:
name="test_Webhook" → 联络点名称是 test_Webhook。
contact_type="Webhook" → 选择类型为 Webhook。
content → 填写 Webhook 的 URL。
screenshot_name → 截图保存为 test_TC41306_create_contact_point_1.png。
执行顺序流程图
5. 为什么不用一个函数?
如果将所有逻辑写在一个函数中:
def test_TC41306_create_contact_point(...):
# 创建 Webhook 的代码(重复 4 次)
# 创建 Email 的代码(重复 4 次)
# 创建 Syslog 的代码(重复 4 次)
# 创建 SMC 的代码(重复 4 次)
缺点:
代码冗余:相同逻辑重复多次。
难以维护:修改一个地方需要改 4 次。
可读性差:代码臃肿,难以理解。
通过拆分为主函数和内部函数:
优点:
复用代码:只需写一次核心逻辑。
参数驱动:通过参数控制不同行为。
易于扩展:新增联络点类型只需加一行调用。
6. 总结
主函数 是测试用例的入口,负责组织测试流程。
内部函数 是具体操作的实现,通过参数复用逻辑。
执行顺序:主函数调用内部函数 4 次,每次传入不同参数。
参数作用:告诉内部函数“创建什么类型的联络点”。
这样设计后,代码更简洁、更易维护。如果有其他疑问,欢迎继续提问! 😊