背景痛点:毕设“测试”为何总被当成“截图”
每到毕业季,答辩教室里的测试章节往往长成这样:
- 十几张手工操作截图,配上“输入正确,期望通过”的苍白描述;
- 代码仓库空空如也,或仅存一段
if __driver.find_element(...)的线性脚本; - 没有断言、没有数据驱动、没有失败重跑,更谈不上持续集成。
结果导师一句“如何证明你的系统真的可用?”就让全场沉默。
要想让毕设既“跑起来”又“说得通”,必须交付一套可运行、可复现、可度量的自动化测试框架。本文记录作者从 0 到 1 落地的完整过程,可直接复刻到任何 Web 项目。
技术选型:Pytest + Selenium 为何胜出
测试运行器:Pytest vs Unittest
- 断言原生语法
assert即可,无需记忆self.assert*; - fixture 机制让“浏览器启停、数据准备、截图清理”天然解耦;
- 2800+ 插件生态,Allure、并行、失败重跑一键即得;
- 结论:unittest 适合教学,Pytest 适合工程。
- 断言原生语法
浏览器引擎:Selenium vs Playwright
- Playwright 自带等待策略、并发原生,但对旧系统(教学平台常基于 jQuery 1.x)偶现兼容坑;
- Selenium 社区庞大,驱动管理工具 WebDriverManager 已成熟,毕设场景更稳;
- 结论:求稳用 Selenium,求快用 Playwright;本文以 Selenium 演示,后续可无缝迁移。
报告与持续集成
- Allure 生成带步骤截图、耗时曲线的交互式报告,答辩演示效果极佳;
- GitHub Actions 免费 2000 分钟,足够学生账号跑完整CI。
核心实现:模块化框架四件套
目录结构
thesis-test/ ├─ commons/ # 工具层 │ ├─ driver_factory.py │ └─ config_handler.py ├─ pages/ # PO 模式 │ ├─ base_page.py │ ├─ login_page.py │ └─ project_page.py ├─ test_data/ # 数据驱动 │ └─ login.yml ├─ tests/ # 用例层 │ ├─ conftest.py │ └─ test_login.py └─ outputs/ # 报告与日志 ├─ allure-results/ └─ screenshots/Page Object 封装示例
# 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.CSS_SELECTOR, ".btn-submit") def login(self, username: str, password: str): self.input_text(self._username, username) self.input_text(self._password, password) self.click(self._submit) return self数据驱动:Pytest 参数化 + YAML
# tests/test_login.py import yaml, pytest, os from pages.login_page import LoginPage @pytest.mark.parametrize("case", yaml.safe_load( open(os.path.join("test_data", "login.yml")))) def test_login(case, browser): page = LoginPage(browser) page.login(case["user"], case["pwd"]) assert page.get_text(LoginPage._error_tips) == case["expect"]Allure 报告集成
- 安装:
pip-req.txt含allure-pytest - 运行:
pytest --alluredir outputs/allure-results - 生成:
allure serve outputs/allure-results
效果:每条用例自动附带失败截图、步骤耗时,答辩演示可直接双击打开。
- 安装:
完整可运行代码(最小闭环)
以下代码已能直接跑通一个登录成功/失败场景,体现 Clean Code 三原则:显式命名、函数短小、单一职责。
# commons/driver_factory.py from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager def get_chrome(): options = webdriver.ChromeOptions() options.add_argument("--headless=new") # 无头模式,CI 必备 return webdriver.Chrome(ChromeDriverManager().install(), options=options)# tests/conftest.py import pytest, os, datetime from commons.driver_factory import get_chrome @pytest.fixture(scope="session") def browser(): driver = get_chrome() driver.implicitly_wait(8) yield driver driver.quit() @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() if rep.when == "call" and rep.failed: driver = item.funcargs["browser"] ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") file_name = f"outputs/screenshots/{item.nodeid}-{ts}.png".replace("::", "_") driver.save_screenshot(file_name)# test_data/login.yml - user: admin pwd: correctpass expect: 登录成功 - user: admin pwd: wrongpass expect: 用户名或密码错误执行命令
pip install -r requirements.txt pytest --alluredir outputs/allure-results -n auto allure serve outputs/allure-results即可在浏览器看到带截图的交互式报告。
性能与可靠性:让用例跑得稳
元素等待策略
- 弃用固定
time.sleep(),统一封装显式等待:WebDriverWait(driver, 10).until(EC.element_to_be_clickable(locator)) - 在 BasePage 层提供
wait_for_ajax()方法,检测 jQueryactive==0,解决教学平台大量异步请求。
- 弃用固定
测试幂等性
- 每条用例执行前清理 Cookie / LocalStorage,确保登录态不串扰;
- 对写操作(新增、删除)使用 UUID 作为业务主键,并发跑也不冲突;
- 数据库回滚:pytest fixture 中利用
transaction.atomic()包裹,用例结束自动回滚。
并发冲突
- 本机并行
-n 4时,为每个 worker 启动独立 Chrome 实例; - 共享下载目录加锁,防止自动下载文件重名覆盖;
- 若用 GitHub Actions,矩阵策略
matrix.browser: [chrome, edge]可横向扩展。
- 本机并行
生产环境避坑指南
浏览器驱动管理
- WebDriverManager 默认每次联网查询最新版本,CI 内可缓存:
export WDM_LOCAL=1 && export WDM_CACHE_DIR=$HOME/.cache - 若服务器无外网,提前下载驱动,放入
bin/目录,路径注入PATH。
- WebDriverManager 默认每次联网查询最新版本,CI 内可缓存:
CI/CD 配置陷阱
- GitHub Actions 的
ubuntu-latest镜像已自带 Chrome,但版本可能滞后,显式指定chrome-version: stable; - 若用 Jenkins 本地部署,节点需安装
Xvfb提供虚拟桌面,否则 headless 之外会报DevToolsActivePort错误。
- GitHub Actions 的
测试数据隔离
- 不同 worker 使用独立数据库 schema (
CREATE SCHEMA test_${BUILD_NUMBER}); - 用 Docker-compose 一键拉起
mysql:5.7 + redis + web,--abort-on-container-exit保证用例结束即销毁,避免脏数据。
- 不同 worker 使用独立数据库 schema (
报告体积控制
- Allure 结果目录随历史累增,CI 后追加
rm -rf outputs/allure-results; - 对历史趋势有需求可接入 Allure Enterprise 或上传至第三方托管。
- Allure 结果目录随历史累增,CI 后追加
可拓展方向:让测试成为质量门禁
框架跑通后,不妨继续深耕:
接口层补充
基于requests + pytest十分钟即可把登录、查询等高频接口抽象成api/包,与 UI 用例组成端到端金字塔。引入 Jenkins Pipeline
将上述pytest命令封装为Jenkinsfile,配合post { always { publishHTML ... } }实现失败即邮件通知,测试通过率低于 95 % 时禁止合并主干。质量门禁思考
“测试”不应只是毕设报告里的装饰章节,而应成为交付流水线的一个门禁。
当框架能在每次 commit 自动给出通过率、覆盖率、缺陷趋势,你的毕设就拥有了工业级说服力——导师提问“如何保障质量”时,只需打开大屏,让数字说话。
动手把代码推到 GitHub,跑通第一条绿色流水线,你会发现:
测试不是毕设的“附加题”,而是让系统真正“站起来”的脊梁。祝各位答辩顺利,代码常绿。