ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Selenium自动化测试报告:unittest与pytest实战

Selenium自动化测试报告:unittest与pytest实战 1. 先把“跑用例”和“看结果”拆成两件事很多人第一次接触 selenium 自动化测试注意力全在“怎么把元素点出来”上等脚本能跑通了就觉得自己会了。真正进到项目里才发现几十个测试用例堆在一起跑一次要等十分钟跑完还不知道哪个挂了、为什么挂、挂了之后看哪张截图。这就是典型的“用例能跑但跑得不明白”。这篇我接着之前的 selenium 系列往下聊主题是测试用例运行和报告面向的是已经会写一点点 python、能看懂 selenium 基本定位但在用例组织和结果呈现上还是新手的同学。核心要解决三个问题怎么把散落的用例组织成一个能一键执行的整体怎么用运行器把执行结果稳定地吐出来怎么把这些结果变成一份别人或者明天的你自己能看懂的测试报告。标题里“非 python 新手”这个限定我理解成两层意思。第一层你不需要是 python 专家能写函数、能看懂类和装饰器就够了第二层这一篇不讲 python 语法入门默认你已经装好了 selenium 和数据驱动那套东西。所以我会把重心放在运行机制和报告产出上把背后“为什么这么做”讲透。我自己踩过的坑里一半以上都不在业务逻辑而在于用例怎么被发现的、报告为什么是空的、截图为什么存到了奇怪的地方。这些坑才是这一篇真正想给你的东西。2. 运行器的选型unittest 和 pytest 到底怎么挑2.1 两种运行器的本质差别python 生态里能跑测试用例的运行器不止一个但日常用得最多的就是标准库自带的 unittest 和第三方库 pytest。unittest 是 python 标准库的一部分装好 python 就有它的设计参考了 Java 的 JUnit讲究类继承、方法命名、断言语义统一。你写一个类继承unittest.TestCase方法名以test_开头运行器就会自动把它当作一条用例。这个约定的好处是零依赖、结构清晰坏处是写起来啰嗦参数化、夹具、跳过条件的写法都不够顺手。pytest 则是另一套思路。它不要求你继承任何类普通的函数只要名字以test_开头就能被识别成用例断言直接用 python 原生的assert。它自带的夹具fixture机制比 unittest 的 setUp/tearDown 灵活得多参数化、失败重跑、并行执行都有成熟的插件生态。代价是你得额外装一个包团队里如果要求“绝对不能引入第三方依赖”那就只能退回 unittest。我给新手一个很朴素的判断标准如果这个自动化项目是个一次性验证脚本或者团队严格限制依赖用 unittest如果这是要长期维护、用例会从几十条涨到几百条的工程化项目直接上 pytest。别纠结这个决定拖久了反而是浪费。2.2 一个入口脚本的价值不管你选哪个运行器我都强烈建议你把“执行入口”和“用例代码”分开。所谓执行入口就是单独一个run_tests.py或者直接一条命令行它不关心具体用例写了什么只负责找到用例、依次执行、生成报告。用例文件里只放业务操作和断言不放任何运行相关的配置。这样做的理由很实在用例代码是要经常改的执行入口基本不动把两者混在一起换个报告格式就得翻遍所有用例文件那才是真的给自己找事。我见过不少人把unittest.main()直接写在每个用例文件末尾然后靠手动一个个跑文件来执行。这种方式在只有两三条用例时没问题一旦用例多了你会发现自己大部分时间花在“记住今天该跑哪几个文件”上而不是在验证功能。把入口收拢成一个点是自动化测试从玩具走向工具的第一步。2.3 目录结构先定好后期少折腾在写运行逻辑之前先把目录结构定下来能省掉后面大半的路径问题。我常用的结构是这样项目根目录下放testcases目录存用例pages目录存页面对象封装common目录存公共方法reports目录存报告输出screenshots目录存失败截图。运行入口放在根目录通过相对路径引用各个子目录。这个结构不是唯一解但它的好处是每个目录职责单一。尤其注意报告和截图这两个输出目录一定要提前建好并且在脚本里用代码判断目录是否存在、不存在就创建。很多新手第一次跑报告脚本报错说找不到输出路径其实就是因为reports目录压根不存在。这种问题排查起来很快但发生频率高得离谱不如一开始就用os.makedirs(..., exist_okTrue)兜底。3. 测试用例怎么写才能被运行器正确发现3.1 命名约定不是形式主义运行器找用例靠的是命名约定这是最容易踩坑的地方。unittest 的discover方法默认匹配的文件模式是test*.py注意是test开头中间没有下划线也照样匹配。很多人习惯写test_login.py这没问题但也有人写成login_test.py结果运行器根本没找到这个文件还以为是运行命令写错了。pytest 默认匹配test_*.py和*_test.py两种宽容一些但类名和方法名还是有要求。我建议团队内部统一成test_开头因为这是最主流的写法无论换哪个运行器都不会出问题。类名用Test开头方法名用test_开头这两条守住了运行器的发现机制基本不会出错。不要在这个地方耍个性比如用中文命名或者特殊前缀短期看没什么等接手的人来了会骂人的。3.2 前置和后置操作的放置位置每条用例执行前后往往要做一些准备工作比如打开浏览器、登录、清理数据。unittest 提供了setUp和tearDown分别在每条用例前后执行还有setUpClass和tearDownClass在整个测试类前后各执行一次。这个区分非常重要如果你的用例是“登录后验证 A”“登录后验证 B”那么登录这个动作应该放在setUpClass里只做一次而不是每条用例都重登一遍。省下来的时间在几十条用例规模下会非常可观。pytest 里对应的概念是 fixture通过scope参数控制作用范围function级别相当于 setUpclass级别相当于 setUpClasssession级别则贯穿整个测试会话。对于 selenium 这种启动浏览器开销较大的场景我通常把浏览器初始化放在 class 或 session 级别把页面登录放在 class 级别用例本身只做断言。这样一次跑几十条用例浏览器只开几次速度能快好几倍。注意setUpClass里初始化的对象在setUp里要用类变量而不是实例变量来引用否则每个用例都会拿到不同的实例共享状态就失效了。3.3 用例之间不要互相依赖这一点我要单独拎出来讲因为它是新手最容易犯、后果又最严重的错误。假设你有三条用例测试登录、测试下单、测试支付。如果你写成“支付用例依赖下单用例先执行”那么一旦下单用例失败支付用例也会连带失败报告上会显示一片红但真正的根因只有一个。更糟的是如果运行器调整了执行顺序pytest 在某些配置下并不保证严格按字母序整个结果就乱了。正确做法是每条用例都应该是自洽的能从干净状态开始跑完能自己收拾干净。需要登录就在自己的前置里登录需要商品数据就自己造。这确实会让单条用例的执行时间变长但换来的是结果可信。报告的意义在于告诉你“哪个功能坏了”如果用例之间有依赖报告就变成了“哪个组合坏了”诊断价值大打折扣。如果你实在有共享的前置数据用 fixture 或 setUpClass 来提供而不是靠一条用例给另一条用例铺路。4. 命令行运行测试用例的完整实操4.1 unittest 方式的运行命令先说不依赖任何第三方的跑法。假设你的用例都在testcases目录下在项目根目录执行下面这条命令python -m unittest discover -s testcases -p test_*.py -v参数逐个拆解一下。-s指定从哪个目录开始搜索-p指定文件名匹配模式-v是 verbose会输出每条用例的名字和执行结果而不是只给一个点号。新手阶段我强烈建议始终加-v因为你要靠这个输出来判断用例到底有没有被发现、执行顺序对不对。等用例稳定了再考虑去掉做静默执行。如果你只想跑某一个测试类可以这样python -m unittest testcases.test_login.TestLogin -v这里用的是模块路径加点类名的方式模块之间用点分隔注意不要写成文件路径带斜杠那是另一个概念。我见过有人写python -m unittest testcases/test_login.py能跑起来但不规范遇到包结构复杂一点就会出问题。4.2 pytest 方式的运行命令pytest 的命令行更简洁直接在根目录pytest testcases -v如果只想跑名字里带 login 的用例用-k做筛选pytest testcases -v -k login-k支持简单的表达式比如-k login and not register可以排除掉不想跑的用例。这个功能在做冒烟测试时特别好用我只想快速验证核心链路就用-k圈一个子集出来几十秒跑完。-s参数用来关闭输出捕获让你能看到代码里的 print 内容调试时很有用--maxfail3表示失败三条就停止避免一个批量问题导致几百条用例全部白跑。pytest 还有一个我特别喜欢的参数--lf全称 last-failed只会重跑上一次失败的用例。这个在修复 bug 的循环里效率极高改完代码直接pytest --lf验证不用等全量用例跑完。4.3 失败重跑机制的配置自动化测试有一类失败非常恶心网络抖动、页面加载慢、动画没结束。这类失败不是你的代码有问题而是环境不稳定。应对办法是给用例加失败重跑。pytest 用pytest-rerunfailures插件装好后加参数pytest testcases -v --reruns 2 --reruns-delay 1意思是失败后重跑两次每次间隔一秒。如果两次都失败才判定为真正失败。这样做能有效过滤掉偶发的环境问题让报告里的红色都是真问题。不过重跑有个陷阱要提醒你它会让执行时间变长。如果你有 200 条用例其中 20 条偶发失败每条重跑两次那总时间会增加不少。所以重跑次数不要设太高两次是经验值三次以上基本没必要因为连续三次都失败通常说明问题不是偶发的。另外重跑的用例在报告里要能区分出来否则你会误以为是两条不同的失败用例。4.4 执行环境的隔离跑用例之前确认你的执行环境是干净的。这里说的干净有两层一是浏览器状态干净没有残留的 cookie 和缓存二是测试数据干净上一次跑留下的数据不会干扰这一次。浏览器这边我通常在初始化时用无痕模式或者在 tearDown 里调driver.delete_all_cookies()。数据这边如果测试会写数据要在前置里准备好、后置里清掉或者每次都用时间戳生成唯一的数据标识。对于团队协作的项目建议把待测环境的地址、账号密码这些配置抽到一个单独的配置文件里不要硬编码在用例中。这样换环境的时候只改一个地方而且敏感信息也不会跟着代码进版本库。配置文件用config.ini或者.env都行python 都有对应的读取方式选一个团队都熟悉的就好。5. 测试报告的生成与结果呈现5.1 unittest 搭配 HTMLTestRunnerunittest 自带的文本输出只能看个大概要做报告得借助第三方库。HTMLTestRunner 是个经典选择它把执行结果渲染成一个 HTML 页面。不过这里有一个大坑网上流传的很多 HTMLTestRunner.py 是 python2 时代的产物直接拿来在 python3 下用会报各种编码和类型错误。你需要找适配 python3 的版本或者用 pip 安装html-testRunner这个包。用适配版本写出来的入口大概长这样import unittest import os from HTMLTestRunner import HTMLTestRunner if __name__ __main__: report_dir reports os.makedirs(report_dir, exist_okTrue) suite unittest.defaultTestLoader.discover(testcases, patterntest_*.py) report_path os.path.join(report_dir, result.html) with open(report_path, w, encodingutf-8) as f: runner HTMLTestRunner( streamf, verbosity2, title自动化测试报告, description每次执行后自动覆盖 ) runner.run(suite)这段代码里有几个细节值得注意。stream参数接收的是文件对象不同版本的 HTMLTestRunner 对文件打开模式要求不一样有的是二进制wb有的是文本w用之前先看一眼包的说明或者直接试一下。title和description是报告页面上显示的标题和描述别小看这两项把项目名和执行环境写进去翻报告的人能省下很多猜测。报告文件名我用固定的result.html每次都覆盖配合日期目录来区分不同批次。5.2 pytest 搭配 pytest-html 和 Allurepytest 的报告生态更丰富装pytest-html后加一个参数就能出报告pytest testcases -v --htmlreports/result.html --self-contained-html--self-contained-html这个参数一定要加它会把 CSS 和 JS 内联进 HTML生成一个单文件。否则报告会依赖一堆外部资源你发给别人看的时候样式全丢对方还以为你报告坏了。pytest-html 的优点是轻量、零配置缺点是样式比较朴素细粒度信息展示有限。如果你需要更专业的报告比如按功能模块分组、带失败截图的步骤详情那就上 Allure。它的流程是分两步的pytest testcases --alluredir./allure-results allure serve ./allure-results第一步执行用例并把原始数据存到目录第二步启动一个本地服务把数据渲染成网页。Allure 的报告信息密度高失败用例能直接展开看到截图和日志非常适合给团队看。代价是环境配置多一步需要装 Allure 的命令行工具团队共享报告时也要统一版本。报告方案依赖成本报告颜值适合场景unittest 文本输出无低本地快速调试HTMLTestRunner一个适配包中unittest 项目、简单分享pytest-html一个插件中pytest 项目、日常输出Allure插件加命令行工具高团队协作、长期维护5.3 失败截图与日志的联动一份只有红色文字的失败报告价值有限一份带失败现场截图的报告价值翻倍。截图的基本写法是driver.get_screenshot_as_file(path)或者driver.save_screenshot(path)前者返回布尔值方便判断成功与否。问题在于怎么让截图和具体的失败用例对应起来。我的做法是在用例的失败处理逻辑里截图文件名带上用例名和时间戳比如test_login_20250101_103000.png。如果是 pytest可以写一个 hook 函数在用例失败时自动抓取当前 driver 的截图。这样报告里每条失败用例都能挂上对应的截图排查时不用去猜当时页面长什么样。日志同理把关键操作和信息用 logging 模块记下来输出到文件。截图负责“看到”日志负责“知道发生了什么”。两个配合起来一个页面加载失败的问题你既能看到白屏的截图又能从日志里看到是卡在了哪个元素等待上。提示截图存的是相对路径时要注意当前工作目录。运行器从哪个目录启动相对路径就相对于哪个目录。用绝对路径最稳妥或者统一在配置里定义一个输出根目录。6. 失败用例排查与稳定性治理6.1 用例跑不起来或者一条都没执行这是最高频的问题表现是命令执行了但输出里显示Ran 0 tests或者收集到 0 个用例。原因通常就那几个文件命名不匹配检查是不是test_开头目录路径传错运行器在错误的目录里搜索测试类没有继承unittest.TestCaseunittest 场景测试方法名没有以test_开头。排查顺序建议从外到内先确认目录对不对再确认文件名最后看类名和方法名。还有一个隐藏原因是__init__.py。如果你的用例放在一个包目录里某些运行方式需要目录下有__init__.py才能被正确导入。这个在不同 python 版本和运行方式下行为有差异遇到了直接加上试试成本很低。6.2 报告生成成功但是内容为空报告文件确实生成了打开一看全是零或者显示错误。这种情况第一要检查报告生成代码是不是在runner.run(suite)之前就把文件关闭了那当然写不进内容。第二要检查stream用的文件模式对不对模式用错会导致写入失败但不报错。第三有些 HTMLTestRunner 版本对报告内容有编码要求如果用例名里有中文可能在渲染阶段出问题。统一用英文命名用例报告本身用中文标题和描述这样最稳。6.3 报告中文乱码HTMLTestRunner 生成的中文乱码是老问题了。根因是页面声明了 UTF-8 但写入时没有按 UTF-8 编码或者反过来。解决办法是在打开文件时显式指定encodingutf-8同时确认 HTMLTestRunner 内部生成 head 时写的 charset 也是 utf-8。如果包本身写死了别的编码那就得改包源码或者换一个维护更活跃的版本。pytest-html 在这块表现好很多基本不会有乱码问题这也是我推荐新手直接用 pytest 的原因之一。6.4 偶发失败怎么定位偶发失败的定位比稳定失败难得多因为复现不了。我的经验是按这个顺序排查先看是不是等待不足很多偶发失败都是页面元素还没出来就去找了加显式等待往往能解决再看是不是测试数据冲突多条用例用了同一个账号或同一批数据并行或串行时互相干扰最后看是不是环境本身不稳比如数据库响应慢、网络抖动。前两类靠改代码解决第三类靠重跑机制过滤。我整理了一份常见问题的速查表遇到时可以直接对号入座现象可能原因处理方向一条用例都没执行命名不符、路径错误、类未继承检查命名与目录结构报告为空文件提前关闭、模式错误检查报告生成代码中文乱码编码未统一显式指定 utf-8截图找不到路径相对目录不对改用绝对路径用例偶发失败等待不足、数据冲突加显式等待、隔离数据执行时间过长浏览器频繁启停提升 fixture 作用域6.5 让报告真正被用起来最后聊一个比技术更重要的事。报告生成出来如果没人看那它就只是一堆文件。我在项目里会让报告固定输出到一个共享目录文件名带上日期和批次再配合一个简单的汇总说明比如今天跑了多少条、通过多少、失败哪几个模块。这样产品、开发、测试都能在同一个页面看到进度而不是每次跑来问“测了吗结果怎么样”。对于长期维护的项目我还会记录每次执行的通过率趋势。如果某个模块的通过率持续下降即使这次还没红也说明代码在变脆值得提前介入修一修等待逻辑或者定位方式。这种“用报告驱动优化”的习惯比单纯会用某个报告工具价值大得多。踩过几次坑之后我越来越觉得自动化测试里真正拉开差距的不是定位写得多花哨而是运行和报告这一层有没有做扎实。用例组织清楚、执行命令固定、报告有人看这套流程跑顺了你后面做数据驱动、做持续集成都会少很多返工。
返回列表