尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

自动化测试进阶实战:显式等待、数据驱动与页面对象封装解析

自动化测试进阶实战:显式等待、数据驱动与页面对象封装解析 1. 项目概述与目标拆解接到“3.1完成进阶13、14、15”这个任务时我先愣了一下因为这看起来就是一条非常简单的课程进度记录。但我把它当做一个“小项目”来对待而不是随手划掉三道题。只有把任务拆开看透才知道进阶13、14、15这三道题到底在考什么、怎么练才不白练、做完之后能沉淀下什么。先交代一下背景我所在的团队每周会安排一批自动化测试的进阶练习编号从1到20穿插在章节3.1的课程模块里。进阶13、14、15刚好处于一个“理论讲完、开始上手综合场景”的过渡位置。换句话说前面的练习都在打单点技能比如定位元素、等待加载、断言文本而13、14、15开始要求你把多个能力串起来。这也是我把它们当成一个整体项目来做的核心原因分开做是三道题合起来就是一个微型自动化测试项目的闭环。这一篇就围绕“完成进阶13、14、15”的完整过程来写包含题目拆解、技术选型、实现细节、踩坑记录和最终检验。整个过程的代码量不大但思路含量不小对正在学自动化测试、准备从入门跨向进阶的读者而言参考价值比较直接。我刚拿到题目时做的第一件事不是写代码而是把三道题分别看了一遍确认每个题目的考查点。把题目分类之后思路立刻清晰了进阶13重点考显式等待和元素状态判断场景是一个列表页的异步加载。进阶14重点考多个表单控件的组合操作以及断言时的边界条件。进阶15重点考数据驱动也就是把测试数据和测试逻辑拆开用一套逻辑跑多组数据。这三个点正好分别对应自动化测试里的“稳”、“全”、“省”。稳是指脚本不要靠sleep硬等全是指用例要覆盖正常和异常路径省是指用数据驱动减少重复代码。把一个任务想成这样的三层结构之后执行起来就非常顺。2. 三道进阶题的设计逻辑与核心思路2.1 进阶13异步加载场景下的显式等待这道题给出了一个模拟的订单列表页面页面加载后需要1到3秒才会把订单数据渲染出来。题目的要求是编写自动化脚本打开页面后等待“订单列表加载完成”这个状态出现然后完成对订单条数和特定订单号的断言。很多新手看到这种题目第一反应是用time.sleep(3)。但在一线测试框架里sleep是纪律性错误因为它的等待时间不可控。网络快的时候白白等3秒网络慢的时候3秒可能还不够。正确的姿势是使用显式等待让脚本轮询某个条件直到条件满足或者超时。我在实现时用的是WebDriverWait配合expected_conditionsfrom selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait WebDriverWait(driver, 10) order_items wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, .order-item)) )这里有一个细节值得展开presence_of_element_located只检查元素是否出现在DOM里不检查元素是否可见。如果列表项是一个带骨架屏的页面DOM里可能有占位节点此时用visibility_of_all_elements_located更稳妥。进阶题里没有明确说明页面是否有骨架屏但从断言稳定性的角度出发我直接选择了可见性判断宁可严格一点也不让脚本出现假阳性。等待条件选好之后还有一个隐藏考点等待超时时间设多少合适。我按常见的3G网络下前端接口响应时间做了估算取10秒作为兜底超时。这里没有魔法数字纯粹是实际场景的常见值一个列表接口在非极端网络下2到3秒内返回算正常10秒已经覆盖了大多数慢速情况。如果10秒还没出现脚本报错也比继续往下执行要好。2.2 进阶14多表单控件组合操作与断言进阶14给的是一个商品筛选页面筛选条件分别是价格区间输入框、品牌下拉框、销量排序单选按钮以及一个“查询”按钮。题目要求组合这些控件完成一次筛选然后验证结果列表里的商品价格都落在指定区间内。这个题目的难点不在操作控件本身而在两个容易被忽略的地方第一下拉框选项的加载可能是异步的第二筛选后的结果列表需要在断言前完成刷新。对于下拉框我使用了Select类这一步本身没有悬念from selenium.webdriver.support.ui import Select brand_select Select(driver.find_element(By.ID, brand)) brand_select.select_by_visible_text(品牌A)这里要说一下为什么用select_by_visible_text而不是select_by_value。在真实项目里下拉框的value属性经常是后端定义的ID可读性很差而且不同环境可能不一致。而页面展示的文本是产品经理确认过的用文本选择更贴近用户视角脚本维护起来也更直观。价格区间输入框操作就更直接了两个send_keys就能搞定。但提交之后的结果校验需要仔细设计断言因为价格是浮点数直接比较会碰到精度问题。我的做法是把价格文本转成整数“分”再比较def parse_price(price_text): return int(float(price_text.replace(¥, )) * 100) assert all(low parse_price(item.text) high for item in price_results)用“分”做单位既避开了浮点误差又符合电商系统里价格计算的基本规则算是一个实践中比较顺手的小技巧。2.3 进阶15数据驱动的参数化改造进阶15的题目看起来很短用同一个登录测试流程验证三组账号密码的组合分别是正确账号正确密码、正确账号错误密码、不存在账号任意密码。要求代码不复制三份。这题本质上就是在考数据驱动。我先写了一版最直接的实现把三组数据放进一个列表然后通过pytest的参数化装饰器跑同一套逻辑import pytest pytest.mark.parametrize( username,password,expected, [ (valid_user, valid_pass, 登录成功), (valid_user, wrong_pass, 密码错误), (ghost_user, any_pass, 账号不存在), ], ) def test_login(username, password, expected): login_page.login(username, password) assert expected in login_page.get_message()这是数据驱动最基础也最清晰的一种形态数据与逻辑分离新增一条验证场景只需要往列表里加一行不用改动测试函数本体。对于一个小型测试项目来说这种朴素方案已经足够好不需要一上来就上Excel或YAML。不过如果数据量变大直接把数据堆在装饰器里会让代码文件变得臃肿。进阶15虽然没有要求我还是顺手把数据抽到了独立的测试数据模块里这样更符合“数据驱动”的本意数据和测试逻辑分离开测试脚本只负责流程和断言。后面如果产品说要加10组异常场景我只需要改数据文件谁碰代码的风险都更小。3. 实操过程与核心环节实现3.1 环境确认与项目结构整理动工之前我先锁定了环境版本避免不同版本的库带来兼容性问题。这次的运行环境是Python 3.10、pytest 7.4、Selenium 4.15浏览器用的是Chrome 120对应版本的chromedriver。Selenium 4相比老版本最大的变化是内置了相对定位和更完善的等待API用起来顺手不少。项目目录我按下面这个结构来组织测试代码、页面对象、测试数据分开存放advanced_practice/ ├── pages/ │ ├── order_list_page.py │ ├── filter_page.py │ └── login_page.py ├── test_data/ │ └── login_data.py ├── tests/ │ ├── test_advanced_13.py │ ├── test_advanced_14.py │ └── test_advanced_15.py └── conftest.py这个结构看起来简单但实战中特别有用。把页面操作封装到pages目录测试用例里就不会出现一大串driver.find_element连招断言的可读性也会好很多。conftest.py里放的是driver的fixture每个测试模块直接复用不会重复启动浏览器。fixture这块我额外说一句因为很多人写着写着就忽略掉了。我用的是函数级fixture每个用例跑完就退出浏览器保证用例之间互不干扰。这种做法的代价是执行慢但换来的是稳定性和隔离性。在小规模练习项目里稳定大于速度。3.2 页面对象封装与等待策略落地页面对象模式是这个项目的骨架。我拿登录页举例把元素定位和操作动作封装成方法class LoginPage: def __init__(self, driver): self.driver driver self.username_input (By.ID, username) self.password_input (By.ID, password) self.submit_button (By.ID, submit) self.message (By.CLASS_NAME, message) def login(self, username, password): self.driver.find_element(*self.username_input).send_keys(username) self.driver.find_element(*self.password_input).send_keys(password) self.driver.find_element(*self.submit_button).click() message_el WebDriverWait(self.driver, 5).until( EC.visibility_of_element_located(self.message) ) return message_el.text这里值得注意的点是我在login方法内部做了显式等待等待提示消息可见之后才返回文本。这样做的好处是调用方不需要再关心页面什么时候加载完成拿到返回值就可以直接断言。封装的意义就在于此把复杂的等待逻辑留在内部对外暴露一个简单的动作接口。三个页面对象做完之后测试用例本身的代码量就很少了。这也是一个经验之谈页面对象如果没有把等待逻辑封装进去测试用例就会到处散落WebDriverWait维护成本很高。宁可把等待写在页面对象里让它变成一种默认行为也不要指望每个写用例的人都记得加等待。3.3 三组用例的执行结果与稳定性观察代码写完之后我连续跑了三次测试套件确认结果稳定。三次执行全部通过平均耗时在18秒左右主要时间花在浏览器启动和页面加载上。这个执行速度对10个用例左右的小规模练习项目来说是正常的。我对结果做了个简单统计用例执行时间结果test_advanced_134.2秒通过test_advanced_146.8秒通过test_advanced_157.1秒通过三次跑完没有出现偶发失败说明等待策略和数据驱动设计在稳定性方面是合格的。不过我也清楚一次通过不代表就没有问题。自动化测试脚本最害怕的不是“跑不过”而是“这次过、下次挂”的脆性。所以我又多跑了几轮并且在其中一轮里手动加入了延迟模拟确保显式等待不是靠运气过关。3.4 待办事项与扩展空间做完三道题之后我给自己列了一个简单的后续清单。第一个待办是给进阶14的筛选场景增加一个“无结果”的测试数据验证空状态页面是否也能正常断言。第二个待办是把测试报告接入pytest-html方便把结果分享给团队。第三个待办是重新审视一下CSS选择器把稳定性不足的class依赖改成更健壮的定位方式。这些不是题目要求的内容但对于一个想持续精进的测试工程师来说做完题只是起点把练习当成小项目去延伸才是真正的进阶。这也是我在这一节里想传达的核心观点练习题永远是一个最小可行样本真正的能力来自你对这个样本的延伸思考。4. 常见问题与排查技巧实录4.1 等待时间到了但元素仍然没出现这是我第一次跑进阶13时碰到的问题。当时我把等待条件设为presence_of_element_located结果在列表项渲染前页面已经有一个空的占位DOM节点脚本直接判定元素存在后续取订单号时自然就报了空指针。排查方法很简单打开浏览器开发者工具手动刷新页面观察DOM结构的变化。如果发现页面先插入空的div再用异步请求填充数据就不能用presence必须改成visibility或者自定义等待条件。这个排查思路在大多数异步渲染场景里都适用。4.2 下拉框选项加载比预期慢进阶14的下拉框也给我上了一课。我以为Select对象创建之后就能直接操作结果在跑的时候偶尔会报“选项不可见”的错误。仔细看才发现下拉框的选项是页面加载后通过接口动态插入的Select初始化时选项还没填充进来。解决办法是在操作下拉框之前先用显式等待确保选项数量大于1wait.until( lambda d: len(Select(d.find_element(By.ID, brand)).options) 1 )这个lambda表达式看起来有点绕但它解决的问题很实际把“下拉框选项已经加载完毕”变成一个可轮询的条件等条件成立再往下执行。类似这种动态加载在真实项目里比比皆是养成“操作前先确认状态”的习惯能省下大量排查时间。4.3 用例之间互相干扰导致偶发失败跑进阶15时出现过一次让我困惑的现象单独跑登录用例全部通过但把三个用例放在一起跑第二个用例偶尔失败。后来发现是浏览器缓存引起的。第一个用例登录成功后第二个用例打开登录页时页面自动带出了上次输入的账号。解决这个问题我用了两个层面的手段第一在fixture中每次启动浏览器时用一个干净的user data directory第二在页面对象里增加了一个清空输入框的操作。两个手段叠加之后用例之间的隔离性明显提升。这个坑在真实项目中尤其常见尤其是涉及到登录态、缓存、本地存储的场景用例隔离做得不好脚本就是一颗定时炸弹。5. 从三道题到一套测试习惯的沉淀把进阶13、14、15做完回头看这三道题我最大的体会是它们不是三个孤立的小练习而是一条“稳定性、覆盖面、可维护性”的进阶路径。进阶13让你学会等待真实的状态而不是盲等进阶14让你意识到组合操作的每一步都不能假设页面是静止的进阶15则是用一个简单的参数化技巧把代码量砍掉三分之二。在实际项目中我见过太多返回“测试通过”但实际什么也没验证的脚本也见过满屏sleep(5)的“稳定”脚本——它们看起来稳实际上慢且脆弱。真正有价值的自动化测试应该是又快又稳又有覆盖面的而这三个维度恰好就是这三道进阶题在训练的东西。最后分享一个我在这个过程中养成的小习惯每道题做完之后不管多简单都顺手写一段“如果这里是真实项目我还会补充什么验证”的备注。这种微小的练习做多了你会发现自己看问题的视角不再局限于“让脚本跑通”而是慢慢转向“让脚本真的有用”。这种思维转变比多会几个API要重要得多。
返回列表