☰
自动化测试实验五实战:Selenium、pytest、PO模式与接口自动化
2026/9/30 8:41:44 网站建设 项目流程

1. 实验五要解决的核心问题:自动化测试到底在自动什么

1.1 先搞清楚这门实验课的真实目标

很多人第一次做软件测试实验五的时候,脑子里想的是"我要写一个能自动点按钮的脚本",于是打开编辑器就开始driver.find_element,一路写下去,脚本到三十行就报错,然后陷入无限调试。我带过几届学生的实验课,也看过不少同行的实验报告,发现一个共性:实验五真正考的不是你会不会调 API,而是你有没有把一个手工用例翻译成机器能重复执行、结果能判定、失败能定位的东西。

这句话听起来有点绕,拆开来看就是三件事。第一件,脚本要能重复跑,今天跑通明天也能跑通,换台机器还能跑通,这就涉及环境隔离和配置外置。第二件,结果要能自动判定,不能靠人盯着屏幕说"这里好像对了",得让断言说了算,断言失败要抛异常、要标红、要能区分是产品缺陷还是脚本缺陷。第三件,失败要能定位,脚本挂了以后你得在两分钟内知道是网络慢、元素改了、还是数据被别的用例吃掉了,而不是重新手动点一遍。

这三件事对应到技术点上,分别是元素定位与等待策略、断言体系与用例组织、日志与截图留证。你看那些自动化测试面试题,翻来覆去问的也就是这几个方向:显式等待和隐式等待的区别、PO 模式解决了什么问题、用例之间怎么保持独立、失败重试该不该加。这些问题的答案,其实就是实验五里你应该亲手踩一遍的东西。

所以我的建议是,动手之前先别急着敲代码。拿一张纸,把你上一周做手工测试时写的那几条用例抄下来,挑出其中最典型的一条,比如"用正确账号密码登录后能进入首页",然后在旁边标注:前置条件是什么、操作步骤有几步、预期结果怎么判断、用到的测试数据从哪来。这张纸就是你后面所有工作的蓝图,脚本只是把它翻译一遍而已。没有这张纸的自动化,本质上就是一堆随机点击,跑绿了也不知道覆盖了什么,跑红了也不知道坏在哪。

1.2 三条技术路线怎么选:Web、接口、移动端

实验五通常不会限定你只能做一种,但时间有限,得挑一条性价比最高的路线。我把三条路线的特点摊开说一下,你可以对照自己的实验环境选。

Web UI 自动化是最直观的,浏览器打开就能看到效果,Selenium 生态也最成熟。缺点是脆,页面结构稍微一变,定位就失效,而且执行速度慢,一条用例两三秒算快的。它的价值在于覆盖那些只有前端才有的逻辑,比如表单校验提示、弹窗交互、页面跳转,这些接口测试测不到。

接口自动化是我的首选推荐。用 requests 加 pytest,一条用例几十毫秒,几百条用例一分钟内跑完,维护成本也低得多,因为接口的契约比页面 DOM 稳定得多。实验里如果被测系统有开放接口,哪怕是本地起的一个小服务,我都建议你把主力放在这边,UI 那边只挑两三条关键路径做验证。

移动端自动化属于锦上添花。Appium 的环境搭建本身就是一道坎,安卓 SDK、模拟器、驱动版本,任何一个环节对不上就是几小时的消耗。如果你的实验周期只有一两周,移动端建议作为拓展部分写进报告,别当主线。

路线典型工具组合单条用例耗时维护成本适合在实验里承担的角色
Web UISelenium + pytest2 到 8 秒高,受页面结构影响大关键业务流程验证
接口requests + pytest0.05 到 0.5 秒低,契约稳定主力回归套件
移动端Appium + pytest5 到 15 秒很高,设备与驱动耦合拓展加分项

这张表不是让你三选一,而是让你分配精力。**我自己的习惯是七成时间给接口,两成给 Web UI 的关键路径,剩下一成处理报告和文档。**这个比例在实验场景下基本不会翻车,因为文档和报告的分值往往比你想象的高。

2. 动手前的环境准备:把地基打平

2.1 依赖清单与版本匹配这个老大难

环境这关,几乎所有人都会卡一次。我先给一份最小依赖清单,Python 3.8 以上就行,剩下的用 pip 装。

pip install selenium pytest pytest-html allure-pytest requests pyyaml

装完之后先别急着写用例,跑一个自检脚本,确认浏览器和驱动能对上。Selenium 4.6 之后内置了 Selenium Manager,一般不需要你手动下载 chromedriver 了,第一次运行会自动去拉匹配版本。但在一些网络受限的机器上,自动下载会失败,这时候你就得手动把对应版本的驱动放到 PATH 里。

from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--headless=new") options.add_argument("--window-size=1440,900") driver = webdriver.Chrome(options=options) driver.get("https://example.com") print(driver.title) driver.quit()

这段代码能打印出标题,说明地基是平的。这里有几个细节值得说。--headless=new是新版无头模式,跟老的--headless行为不完全一样,新版的渲染更接近真实浏览器,截图也更正常。--window-size一定要给,默认窗口尺寸很小,很多响应式页面会把元素藏进折叠菜单里,导致你明明能看到元素却定位不到。

注意:驱动版本和浏览器主版本号必须一致。Chrome 升到 120,驱动还停在 118,就会抛 SessionNotCreatedException。看到这个报错先去核对版本,别去改代码。

版本匹配这件事我踩过不止一次。有一次在实验室机器上跑得好好的脚本,换到自己笔记本就挂,排查了四十分钟,最后发现是浏览器自动更新了,驱动没跟上。从那以后我养成了一个习惯,每次准备跑整套用例之前,先跑一遍上面那个十行的自检脚本。三十秒的成本,省掉的是半小时的无效排查。

2.2 目录分层:让脚本活过第三周

很多人写实验脚本是单文件从头写到尾,第一周很爽,第三周加功能的时候整个文件八百行,改一个定位要在里面翻半天。我建议一开始就分成下面这个结构,看着麻烦,实际写起来反而快。

autotest_lab5/ ├── config/ │ └── settings.yaml ├── pages/ │ ├── base_page.py │ └── login_page.py ├── testcases/ │ ├── conftest.py │ ├── test_login.py │ └── test_api_order.py ├── data/ │ └── login_data.yaml ├── utils/ │ ├── logger.py │ └── driver_factory.py ├── reports/ └── pytest.ini

分层的核心逻辑是把变化的东西和不变的东西隔开。定位表达式会变,页面结构会变,所以它们放pages目录。测试数据会变,放data。浏览器类型、被测地址、超时时间会变,放config。用例逻辑相对稳定,放testcases。这样一来,被测系统改版的时候,你只需要改pages和data两处,用例文件基本不用动。

配置外置这件事,具体做法是把可变项写进 YAML,用一个加载函数读进来。

# utils/config_loader.py import yaml from pathlib import Path CONFIG_PATH = Path(__file__).parent.parent / "config" / "settings.yaml" def load_config(): with open(CONFIG_PATH, "r", encoding="utf-8") as f: return yaml.safe_load(f) CONFIG = load_config()
# config/settings.yaml base_url: "http://localhost:8080" browser: "chrome" headless: true implicit_wait: 0 explicit_wait: 10 screenshot_on_failure: true

这里有个我坚持了很多年的选择:隐式等待设成 0,全部用显式等待。隐式等待是全局的,一旦设了 10 秒,每次找元素找不到都会傻等 10 秒,而且它跟显式等待混用的时候行为很诡异,可能出现等待时间被放大、超时时间不可预期的情况。全用显式等待虽然每处都要多写一行,但每次等待都是明确可控的,排查问题的时候心里有底。

3. Web 端实操:从一条登录用例写到能维护的套件

3.1 第一版脚本:先跑通,再谈优雅

我强烈建议第一版脚本故意写得"丑"一点,一个函数从头到尾,不做任何封装。目的是把流程跑通,确认定位、等待、断言这三件事都能正常工作。

import pytest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def test_login_success(): driver = webdriver.Chrome() wait = WebDriverWait(driver, 10) try: driver.get("http://localhost:8080/login") wait.until(EC.visibility_of_element_located((By.ID, "username"))).send_keys("tester01") driver.find_element(By.ID, "password").send_keys("Test@123456") driver.find_element(By.ID, "submit-btn").click() welcome = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".welcome-text"))) assert "tester01" in welcome.text finally: driver.quit()

短短十几行,包含了实验五要考的绝大部分知识点。visibility_of_element_located而不是presence_of_element_located,这两者的区别很关键:元素存在于 DOM 里不代表用户能看见,也不能代表能点击。表单输入框如果只是存在于 DOM 但被遮罩挡住,send_keys会失败。所以除了纯粹判断"元素有没有被渲染出来"的场景,我一律用 visibility 系列。

finally里quit()是必须的。我见过太多人脚本报错之后浏览器窗口堆了七八个,机器卡死,还以为是代码问题。driver 是要释放的资源,跟打开的文件句柄一样,不关就会累积。

3.2 PO 模式重构:把定位表达式关进盒子里

跑通之后开始重构。PO 模式(Page Object)说白了就是一个页面一个类,页面的元素定位和操作都封装在类里,用例只调方法,不碰选择器。

# pages/base_page.py from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from utils.config_loader import CONFIG class BasePage: def __init__(self, driver): self.driver = driver self.wait = WebDriverWait(driver, CONFIG["explicit_wait"]) def find(self, locator): return self.wait.until(EC.visibility_of_element_located(locator)) def click(self, locator): self.wait.until(EC.element_to_be_clickable(locator)).click() def type_text(self, locator, text): el = self.find(locator) el.clear() el.send_keys(text)
# pages/login_page.py from selenium.webdriver.common.by import By from pages.base_page import BasePage class LoginPage(BasePage): USERNAME = (By.ID, "username") PASSWORD = (By.ID, "password") SUBMIT = (By.ID, "submit-btn") WELCOME = (By.CSS_SELECTOR, ".welcome-text") def open(self, url): self.driver.get(url) return self def login(self, username, password): self.type_text(self.USERNAME, username) self.type_text(self.PASSWORD, password) self.click(self.SUBMIT) return self def get_welcome_text(self): return self.find(self.WELCOME).text

重构完之后,用例变成了这样:

def test_login_success(login_page): login_page.open(CONFIG["base_url"] + "/login") login_page.login("tester01", "Test@123456") assert "tester01" in login_page.get_welcome_text()

用例文件从关心"怎么点"变成了关心"点完对不对"。这就是 PO 模式的价值:**当页面改版、ID 变了,你只改login_page.py一个文件,所有用例自动生效。**如果没做这层封装,十个用例里都硬编码了By.ID, "username",改版一次你就得改十处,改漏一处就是一条长期飘红的假失败。

这里有个容易做过头的地方。有些人喜欢把每个元素都写成一个get_xxx_element()方法,一个页面类写三百行 getter。我的做法是只暴露业务动作,比如login()、search()、submit_order(),元素定位作为类属性放在顶部集中管理。**类属性集中放,定位改动一眼可见;方法只描述业务语义,用例读起来像自然语言。**这个分寸感是重构时最需要拿捏的。

3.3 用 fixture 管理 driver 的生命周期

driver 的创建和销毁不能散落在每个用例里,交给 pytest 的 fixture 统一管。

# testcases/conftest.py import pytest from utils.driver_factory import create_driver from utils.config_loader import CONFIG from pages.login_page import LoginPage @pytest.fixture(scope="function") def driver(): d = create_driver(CONFIG["browser"], CONFIG["headless"]) yield d d.quit() @pytest.fixture(scope="function") def login_page(driver): return LoginPage(driver)

scope的选择是个权衡。用function级别,每个用例开一个新浏览器,用例之间完全隔离,但慢。用session级别,整个会话共用一个浏览器,快,但用例之间可能通过 cookie、localStorage 互相污染。我的经验是:**UI 用例一律用 function 级别,接口用例可以用 session 级别复用 Session 对象。**原因很简单,UI 的状态残留太隐蔽了,一个用例登录后没退出,下一个用例就可能在已登录状态下开始,跑出来的结果完全不是你以为的那回事。这种问题排查起来极其痛苦,因为它不是每次都复现。多花的那点启动时间,换来的是结果可信。

3.4 断言设计与失败留证

断言不是越少越好,也不是越多越好。我的原则是一条用例断一个核心结果,附带两三个边界校验。登录用例的核心结果是"进入首页且用户名正确",附带校验可以是"页面标题正确"、"没有错误提示元素"。

失败留证这块,很多人实验报告里只贴一段报错堆栈,看不出个所以然。加上截图和页面源码,报告质量立刻不一样。

@pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield report = outcome.get_result() if report.when == "call" and report.failed: driver = item.funcargs.get("driver") if driver: ts = int(time.time()) driver.save_screenshot(f"reports/fail_{item.name}_{ts}.png") with open(f"reports/fail_{item.name}_{ts}.html", "w", encoding="utf-8") as f: f.write(driver.page_source)

截图文件名带上用例名和时间戳,几个月后回来翻报告还能对得上。页面源码也存一份,因为有些元素问题只在特定时刻存在,事后手动打开页面不一定能复现。

注意:截图要在 driver 还活着的时候拍。如果你把quit()写在断言之前,或者 fixture 的清理顺序弄反了,拍出来的就是一片空白。这个坑我至少见过五次。

4. 数据驱动与报告:让一套脚本跑出几十条用例

4.1 用外部数据文件喂用例

把测试数据硬编码在用例里,是新手最常见的做法。想覆盖"密码错误""账号不存在""账号被锁定"三种场景,就得复制三份几乎一样的代码。正确做法是数据驱动。

# data/login_data.yaml - case: 正常登录 username: tester01 password: Test@123456 expect: success - case: 密码错误 username: tester01 password: WrongPass expect: fail error_tip: 用户名或密码错误 - case: 账号为空 username: "" password: Test@123456 expect: fail error_tip: 请输入用户名
import yaml import pytest from pathlib import Path def load_cases(): p = Path(__file__).parent.parent / "data" / "login_data.yaml" with open(p, "r", encoding="utf-8") as f: return yaml.safe_load(f) @pytest.mark.parametrize("case", load_cases(), ids=lambda c: c["case"]) def test_login_cases(login_page, case): login_page.open(CONFIG["base_url"] + "/login") login_page.login(case["username"], case["password"]) if case["expect"] == "success": assert "tester01" in login_page.get_welcome_text() else: assert case["error_tip"] in login_page.get_error_tip()

ids=lambda c: c["case"]这个参数很实用,不加的话报告里显示的是case0、case1,看报告的人得自己去对数据文件。加上之后直接显示中文用例名,一眼就知道哪条挂了。数据文件用 YAML 而不是 CSV,是因为 YAML 支持嵌套结构,后面要加前置条件、后置清理动作的时候写起来更自然。

4.2 报告规范与失败分类

命令行的输出在实验报告里不够看,加个 HTML 报告。

pytest testcases/ -v --html=reports/result.html --self-contained-html

想再好看一点可以用 Allure,用例分级、步骤、附件、趋势图都有,但对实验阶段来说有点重,pytest-html 足够了。关键是报告出来之后你怎么读它。

**我习惯把失败分成三类来统计:产品缺陷、脚本问题、环境问题。**这三类的处理方式完全不同。产品缺陷要提单跟踪,脚本问题要当场改,环境问题是不可控因素,需要从统计里剔除。实验报告里如果能体现这个分类,说明你真的理解了自动化的产出和局限。举个例子,十条失败用例里如果八条是"元素定位超时",那基本可以判定是脚本的问题,要么选择器写错了,要么等待策略不对,跟被测产品的质量没关系。如果失败集中在某几个接口返回 500,那才可能是真的缺陷。

这个分类习惯还有一个好处,就是逼你去区分"用例失败"和"脚本挂了"。很多人的报告里把两者混为一谈,说"自动化发现了 20 个 bug",仔细一看有一半是 NoSuchElementException 造成的脚本崩溃。这种情况在真实的测试团队里是要被 review 打回的。

5. 接口自动化:性价比最高的一条路

5.1 requests 加 pytest 的最小闭环

如果你在设计实验方案时犹豫不决,我建议把接口自动化做成主力。先看最小闭环长什么样。

# testcases/test_api_login.py import pytest import requests BASE = "http://localhost:8080/api" @pytest.fixture(scope="session") def session(): s = requests.Session() s.headers.update({"Content-Type": "application/json"}) return s def test_login_api(session): resp = session.post(f"{BASE}/login", json={ "username": "tester01", "password": "Test@123456" }) assert resp.status_code == 200 body = resp.json() assert body["code"] == 0 assert "token" in body["data"] session.headers.update({"Authorization": "Bearer " + body["data"]["token"]}) def test_get_profile(session): resp = session.get(f"{BASE}/user/profile") assert resp.status_code == 200 assert resp.json()["data"]["username"] == "tester01"

用requests.Session()而不是每次requests.post,好处是 cookie 和 header 会自动保持,登录拿到的 token 直接塞进 session 的 header 里,后续所有请求自动带上。这一个小改动就能省掉每个用例里重复写认证头的麻烦。

5.2 接口断言要断到字段级别

HTTP 200 不代表业务成功。很多系统在业务失败的时候也返回 200,只是在 body 里带一个code字段。所以断言至少要分三层:协议层看状态码,业务层看 code 和 message,数据层看关键字段的值和类型。

数据层这块,字段类型校验特别容易被忽略。接口返回的id应该是数字,结果某次重构变成了字符串 "123",前端可能因为弱类型没报错,但下游依赖这个字段的服务就可能出问题。这种问题靠肉眼比对很难发现,靠断言就很轻松。

def test_order_detail_schema(session): resp = session.get(f"{BASE}/order/1001") data = resp.json()["data"] assert isinstance(data["orderId"], int) assert isinstance(data["amount"], (int, float)) assert data["status"] in ["CREATED", "PAID", "SHIPPED", "DONE"] assert data["amount"] >= 0

status那个枚举校验是我特别推荐的写法。它不但能验证当前返回值合法,还能在被测系统新增了一个状态值而测试没同步时立刻报警,提醒你去看是不是需求变更了。

5.3 Appium 的最小可用配置

移动端我不建议当主线,但如果你的实验要求必须涉及,下面这套配置能让 Appium 跑起来。

from appium import webdriver from appium.options.android import UiAutomator2Options options = UiAutomator2Options() options.platform_name = "Android" options.platform_version = "13" options.device_name = "emulator-5554" options.app_package = "com.example.app" options.app_activity = ".MainActivity" options.automation_name = "UiAutomator2" options.no_reset = False driver = webdriver.Remote("http://127.0.0.1:4723", options=options)

跑之前确认三件事:Appium 服务已经启动并在监听 4723 端口、模拟器已经开机且能通过 adb devices 看到、platform_version填的是模拟器的真实安卓版本号而不是你想当然填的数字。第三个是我见过最频繁的失误,填错版本不会立刻报错,而是一系列莫名其妙的行为异常,比如元素找得到但点不动。

元素定位在移动端用 Appium Inspector 这个工具去看,UI Automator Viewer 已经不太维护了。定位策略优先用resource-id或者accessibility id,xpath在移动端性能很差,层级深一点的页面能等十几秒。

6. 常见问题与排查实录

6.1 高频报错速查表

下面这张表是我这些年攒下来的,基本覆盖了实验阶段能遇到的大部分问题。

报错信息大概率原因处理方式
SessionNotCreatedException浏览器与驱动版本不匹配核对主版本号,重装匹配驱动
NoSuchElementException定位表达式写错或元素未渲染用开发者工具重新取定位,加显式等待
ElementNotInteractableException元素不可交互,被遮挡或禁用检查遮罩层,滚动到可视区域再操作
ElementClickInterceptedException点击被其他元素拦截等待遮挡消失,或用 JS 直接触发点击
StaleElementReferenceException页面刷新后旧元素引用失效重新定位,不要缓存元素对象
TimeoutException等待条件始终不满足确认等待条件是否写错,检查网络
ConnectionError被测服务未启动或端口不对先手工访问一次确认服务可用
UnicodeEncodeError报告或日志文件编码问题打开文件时显式指定 encoding="utf-8"

StaleElementReferenceException这个最值得单独说。它的本质是你拿到了一个元素对象的引用,然后页面发生了局部刷新,这个引用就失效了。典型场景是"点击下拉框,等选项列表加载出来,再点击某一项"。如果你在下拉框点击之前就把选项列表的元素存进了变量,那列表刷新后这个变量就是废的。解决办法是每次操作前重新定位,不要为了省几行代码去缓存元素对象。我刚开始写自动化的时候特别喜欢把元素存成变量复用,觉得这样代码干净,结果被这个异常折磨了很久,后来彻底改成不缓存,问题再没出现过。

6.2 让脚本稳定运行的几个习惯

第一个习惯,尽量不用绝对 XPath。/html/body/div[3]/div[2]/form/input[1]这种写法,产品加一个广告位就全废了。优先用 ID,其次是 name,然后是稳定的 class 或 data 属性,实在不行再考虑相对路径 XPath。CSS 选择器在大多数情况下比 XPath 快,能用的地方优先用。

第二个习惯,给等待加语义。不要写time.sleep(3),这是新手最爱的写法之一。固定睡眠的问题在于它要么不够(网络慢的时候还是失败),要么浪费(多数情况下 0.5 秒就够了却硬等 3 秒)。几百条用例累计起来,浪费的时间是以分钟计的。用WebDriverWait加明确的条件,比如"等待这个元素可见""等待这个按钮可点击""等待这个文本出现在页面上"。

第三个习惯,用例之间不共享状态。不要指望用例 A 创建的数据能被用例 B 使用,也不要指望执行顺序固定。pytest 默认按文件名和函数定义顺序执行,但一旦你用了并行插件或者随机排序,这个假设就崩了。需要前置数据的用例,自己在 setup 里创建,用完在 teardown 里清理。数据命名带上时间戳或随机后缀,避免并发时撞名。

第四个习惯,把日志打出来。自动化脚本挂了以后,如果只有一行AssertionError,你完全不知道它走到了哪一步。在关键动作后面打日志,记录请求参数、响应摘要、当前 URL,排查效率会提升好几倍。

import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.FileHandler("reports/run.log", encoding="utf-8"), logging.StreamHandler() ] ) logger = logging.getLogger(__name__)

encoding="utf-8"这个参数一定要加。Windows 上默认是 GBK,日志里有中文的时候要么乱码要么直接抛异常,这个坑非常隐蔽,因为脚本在 Linux 上跑得好好的。

第五个习惯,失败重试要谨慎。pytest-rerunfailures 这个插件能让失败的用例自动重跑,看起来很美好,但它会掩盖真实的问题。如果一条用例第一次失败第二次成功,说明它本身是不稳定的,真正要做的是找出不稳定的原因,而不是用重试把它压下去。我的做法是在实验阶段不开重试,把所有的不稳定都暴露出来,逐个修掉,等到套件稳定之后再对少数已知的网络抖动场景开启重试。

7. 实验报告怎么写才不算白做

7.1 别贴代码,贴决策和证据

实验报告最容易写砸的地方是把代码原封不动贴上去。老师要看的是你的思考过程,不是代码仓库的备份。我的建议是每个用例模块只贴关键片段,重点写清楚三件事:为什么这么设计、跑出来什么结果、过程中遇到什么问题怎么解决的。

报告结构可以照着这个来。第一部分写被测对象和范围,说明你测了哪些功能,为什么选这些,哪些没覆盖以及为什么。第二部分写框架设计,放一张目录结构图(用文字描述行,画个树形结构就够),说明分层理由和配置项含义。第三部分写用例设计和执行结果,用表格列用例编号、场景、预期、实际、结论。第四部分写问题记录,把执行过程中遇到的技术问题和发现的产品问题分开写。第五部分写分析和改进方向。

7.2 用数据说话,别用形容词

"自动化测试效率很高"这种话没有任何说服力。换成具体的数字:手工执行这 20 条用例需要 40 分钟,自动化执行需要 2 分 15 秒,其中环境启动占 8 秒,实际执行占 127 秒。首次脚本开发耗时 6 小时,按每次回归节省 37 分钟计算,跑到第 10 次回归的时候开始回本。

覆盖率的计算也要说清楚口径。是按接口数量算,还是按业务路径算,还是按代码行算,三者差别很大。实验场景下用业务路径覆盖率最直观:列出系统的关键业务路径有 12 条,本次覆盖了 7 条,覆盖率 58%,未覆盖的 5 条分别是哪些,原因是什么(比如涉及第三方支付,测试环境无法模拟)。能说清楚"哪些没测"和"为什么没测"的报告,比号称覆盖了 100% 的报告可信得多。

还有一点,验证一下脚本的稳定性。同一套用例连续跑三次,记录每次的通过率。如果三次都是 100%,说明稳定。如果有波动,把波动的原因分析出来。这个数据在报告里非常有分量,因为它直接回答了自动化最核心的问题:这套东西能不能被信任。

最后分享我自己在实验阶段踩过的一个印象最深的坑。有一次我写的登录用例本地全绿,交给同学在另一台机器上跑,十条挂了七条。查了半天发现是测试数据的问题,我在本地手工往数据库插了一个tester01账号,脚本直接用了这个账号,但没把初始化脚本写进去。从那以后,我所有的自动化项目里都会有一个init_data.py,负责在用例执行前把所需数据准备好,并且这个脚本被纳入版本管理。**凡是脚本依赖的东西,都要能被脚本自己创造出来,包括数据、账号、配置。**这条规则看起来简单,但它是自动化能否脱离你的电脑、在别人机器上、在持续集成环境里跑起来的真正分水岭。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询