ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Python自动化测试入门:用真实测试脚本倒推核心语法

Python自动化测试入门:用真实测试脚本倒推核心语法 我刚开始接触自动化测试那会儿最怕的不是用例写不好而是不知道Python该学到什么程度。网上搜Python核心语法出来的都是动辄几百页的教程从并发编程讲到元类看得人头皮发麻。后来真正把测试脚本写起来才发现测试脚本开发用的语法其实是个很窄的子集会定义变量、存数据、写循环、拆函数、读文件、抓异常再把类和对象的基本玩法搞明白就已经能应付绝大多数自动化场景了。这篇文章就是按这个思路来的用测试脚本的实际需要倒推Python核心语法把每个语法点都放在真实的测试场景里讲清楚。适合准备入门自动化测试的QA、刚转型做测试开发的同学也适合那些学过Python但总觉得和实际工作对不上号的人。读完之后你至少能独立写出一条能跑的接口测试脚本并且知道怎么把它拆成方便维护的小模块。1. 测试脚本到底要学多少Python先划掉不考的语法1.1 测试脚本主要干什么活从自动化测试的日常任务来看脚本要干的活其实很固定准备测试数据从配置、Excel、JSON里读取用户名、密码、URL等参数。发送请求或执行操作接口脚本用HTTP请求UI脚本用元素定位和点击。校验结果把实际返回值和预期值做对比输出通过或失败。记录日志和报告错误信息落盘、生成测试报告。批量重复对一组数据反复执行同一个用例统计结果。这些场景对应的Python语法无非就是变量、数据类型、条件、循环、函数、文件读写、异常处理和类。看起来内容不多但只要把这些吃透日常测试脚本就没问题了。我见过不少同学一上来就研究Python的语法细节比如列表推导式怎么写更优雅、字典合并有几种写法这些确实有趣但对测试脚本开发的初期帮助不大。1.2 先划掉不需要着急学的部分很多人学Python半途而废原因是把范围铺得太大。站在测试脚本开发的角度下面这些可以往后放先不用急着学理由装饰器写框架时经常用但入门阶段用不到等需要给用例加日志、加耗时统计时再补生成器和迭代器处理超大日志时有用普通测试数据量完全不需要多线程和协程并发压测才会用到先把单条用例跑通再说正则表达式只有做复杂字符串提取才需要入门可以先跳过元类、描述符属于语言进阶测试脚本极少涉及网络编程有requests库不需要自己处理socket明确这个范围之后学习路线就清晰了先把数据结构、流程控制、函数、文件、异常、类这六大块做扎实再回到测试场景里反复用。学语法本身不是目的能写出稳定运行的脚本才是目的。所以这篇文章后面每一个章节我都会先讲语法再给测试场景里的例子。2. 环境准备最容易翻车的三个细节版本、解释器、虚拟环境2.1 版本选择与安装新装环境我建议直接选Python 3.10或更新的稳定版。现在很多测试框架和第三方库都已经转向Python 3老掉牙的Python 2早就不维护了想学就直接学3。Windows安装时最关键的步骤是勾选左下角的Add Python to PATH我见过太多人栽在这一步装完在命令行敲python提示不是内部或外部命令其实就是PATH没配上。装完在终端验证一下python --version。如果公司内部有老系统指定了Python 3.6或3.8也别慌语法上对测试脚本开发影响不大个别新特性用不了而已。Linux系统如果自带Python尽量别去动系统自带的版本直接用python3命令调用再把第三方库装到虚拟环境里避免把系统环境搞得一团糟。很多网上的教程会教你怎么把系统Python换成新版本作为测试工程师不建议这么做系统组件依赖旧Python的情况比想象中多真出了问题恢复成本很高。2.2 解释器路径和pip的对应关系在命令行执行Python脚本的时候最容易迷惑的是我明明装了库运行时却说找不到。绝大多数情况是命令对应的解释器不对一个机器上装了多个Pythonpython指向3.8pip却指向3.10结果库装到了另一个版本里。保险的做法是不要直接用pip install而是始终用python -m pip install这样保证装进当前python解释器对应的环境。还有个常见问题是CMD里输入python没反应但开始菜单里的Python能打开。原因在于Windows应用商店或多个版本的环境变量顺序。排查思路很简单在CMD里where python看看解释器路径到底指向哪里。把这个问题搞清楚后面所有库安装问题都能少一半。如果发现命令行里的python指向了Windows应用商店的占位程序去系统设置里调整环境变量路径顺序就行。2.3 虚拟环境为什么必须用测试项目最好每个都建独立虚拟环境共享系统环境短期省事长期必然出问题。一个项目要pytest 6另一个要pytest 8放一起就会冲突。创建虚拟环境很简单python -m venv venv # Windows下激活 venv\Scripts\activate # Linux/Mac下激活 source venv/bin/activate激活后命令行前缀会多一个(venv)这时再python -m pip install requests pytest库就只装在这个项目里。写测试脚本通常还会用到这几个库库用途requests发HTTP请求接口自动化核心pytest测试用例组织与断言框架pytest-html生成HTML测试报告pyyaml读取yaml配置文件openpyxl操作Excel测试数据环境弄得利索后面写脚本会非常省心。大多数入门者遇到脚本在我电脑上跑不通查到最后都是环境问题而不是代码问题。所以我把环境这一章放在最前面值得多花二十分钟一次配好。3. 从变量到数据结构测试数据怎么存才顺手3.1 变量不用声明类型但要小心动态类型Python定义变量不需要写类型url https://api.example.com/login port 8080 is_https True这让入门非常轻松但也带来了一个坑变量在运行时才确定类型。从配置文件或Excel里读出来的数字往往是字符串200而不是整数200直接拿去做比较就会失败。code_from_response 200 if code_from_response 200: print(通过) # 不会执行因为一个是字符串一个是整数所以测试脚本里最常用的操作之一就是类型转换。int(200)转整数str(200)转字符串float(3.14)转小数。写断言前先确认两边类型一致这是排查代码看起来对就是不通过的第一思路。Python有个内置函数type()可以在调试时快速看变量类型拿不准的时候print(type(变量))一下比自己瞎猜强多了。3.2 列表和字典是测试脚本的两大主角列表用来存一组测试数据test_users [alice, bob, carol] for user in test_users: print(user)字典用来表达一条完整的测试用例最贴近接口请求和响应的结构case { name: 登录成功, method: POST, url: /api/login, payload: {username: alice, password: 123456}, expected: 200, }接口返回的JSON解析之后就是字典或嵌套的字典列表所以字典的增删改查必须熟练。取value用case[name]安全一点用case.get(name)避免KeyError。嵌套结构虽然看起来复杂其实就是一层层剥洋葱data[payload][username]。刚开始写测试数据时宁可在字典里多写几个字段也别图省事省略因为后面一旦需要新增参数结构化数据改起来最方便。3.3 元组和集合的特定用途元组和列表很像但创建后不能修改适合保存固定不变的参数比如数据库连接信息。函数返回多个值的时候本质上返回的也是一个元组。集合最大的用途是去重比如统计测试用例的标签去重直接set(tags)就完了。这四个类型在测试脚本里的分工很清楚列表管批量字典管结构化数据元组管固定配置集合管去重。实际写代码时列表和字典占了九成使用场景元组和集合知道大概用途就行。3.4 字符串处理是断言前的必修课接口返回里的多余空格、换行URL参数拼接这些都需要处理字符串。最常用的操作就几个strip()去掉首尾空白split()按分隔符切分replace()替换startswith()判断开头以及f-string格式化。name alice clean_name name.strip() # alice parts usernamealicepassword123456.split() # [usernamealice, password123456] msg f用例执行失败响应码是{code}f-string在拼接测试日志和断言信息时太好用了比一堆加号拼字符串清爽得多。记住一点任何从外部读进来的数据在用于断言之前都要确认它的格式该strip的strip该split的split。字符串处理是那种看起来简单、实际最容易藏Bug的地方多一个空格、多一个换行都可能让断言失败。4. 流程控制就是脚本的指挥棒条件、循环与重试逻辑4.1 用if/elif/else做环境判断和参数校验测试脚本里条件判断最常见的三个场景根据环境切换地址判断参数是否为空根据响应码走不同分支。env test if env test: base_url https://test.api.example.com elif env prod: base_url https://api.example.com else: raise ValueError(f未知环境: {env})这里有个小习惯值得养成所有假设之外的情况用else抛异常而不是默默跳过。不然脚本在错误环境里跑了半天最后结果全错你还不知道是配置问题。参数校验也一样比如接口要求username不能为空如果用例数据里漏了直接在脚本入口抛错比等到发请求后看一堆堆栈更省时间。4.2 for循环写批量用例执行自动化测试的核心就是批量重复for循环是绝对的主角。遍历列表跑用例for case in test_cases: response send_request(case) check_result(case, response)如果同时需要序号用enumeratefor index, case in enumerate(test_cases, start1): print(f开始执行第{index}条用例)range()配合len()用在大批量数据上也很常见。注意一点循环里如果某条用例失败不要在循环里擅自break除非你明确知道后面数据都依赖前面数据。否则一条挂了后面全不跑测试结果就不完整了。更稳妥的做法是记录失败用例循环结束后统一展示这样一次能看到所有问题。4.3 while循环做超时重试接口自动化里经常遇到网络抖动第一次请求超时重试一次就好了。用while实现一个简单的重试逻辑max_retries 3 for attempt in range(1, max_retries 1): try: response requests.post(url, jsonpayload, timeout5) break except requests.exceptions.Timeout: print(f第{attempt}次请求超时重试) else: raise RuntimeError(f重试{max_retries}次仍然失败)这里用了for的else分支循环正常结束没break说明一直失败抛异常。比while少写一个计数器也更不容易出现死循环。重试之间最好加一个短暂停顿time.sleep(1)避免对服务造成过大压力。这个模式在测试脚本里非常实用建议直接复制到自己的工具函数里保存起来以后凡是涉及网络请求都能复用。5. 函数与模块把重复步骤变成可复用能力5.1 函数是脚本可维护性的分水岭很多脚本刚开始只有几十行全是顺序执行的代码跑得通就完事。等用例多了就发现同一个登录步骤在十个用例里复制粘贴了十遍后面接口字段一改要改十个地方。函数就是解决这个问题的。def get_headers(token): return {Authorization: fBearer {token}} def post_json(url, payload, timeout5): response requests.post(url, jsonpayload, timeouttimeout) response.raise_for_status() return response.json()定义函数时可以用默认参数比如timeout5调用时不传就用默认值。这在实际测试里非常实用大部分接口5秒够用个别慢接口调用时单独传timeout10即可。函数命名也值得花心思最好做到看到名字就知道干什么比如get_headers明显是取请求头post_json明显是发送JSON请求半年后再回来看代码也不用猜。5.2 参数传递位置参数、关键字参数、可变参数Python函数调用时可以直接按顺序传位置参数也可以用关键字参数指明传给谁post_json(/api/login, {username: alice}, timeout3)可读性更强的是全部用关键字参数调用。再看一个特殊场景有一段逻辑要适配不同数量的测试数据可以用*args把多余的位置参数收成元组**kwargs把多余的关键字参数收成字典。框架代码里很常见入门阶段知道有这个东西、看代码不懵就行。日常写测试脚本时能用普通参数解决的场景占绝大多数可变参数更多是阅读别人代码时需要理解的概念。5.3 模块和import把公共代码拆出去写测试脚本最大的误区是所有的东西堆在一个文件里。合理的做法是拆分职责公共请求方法放common.py测试数据放配置文件用例分文件存放。引用公共模块用importfrom common import post_json, get_headersPython的import规则不用一次记全记住最常用的几条同一目录下的模块可以直接from xxx import yyy不同目录建议把公共代码做成包或者通过sys.path调整路径但尽量少用。每个测试文件末尾可以加上ifname main:这一行作用是直接运行这个文件时执行里面的调试代码被其他模块导入时不执行非常适合在写用例时快速自测。我自己的习惯是每个py文件都保留这个入口写完一个函数立刻跑一下验证避免攒了一堆代码最后一起调错。6. 文件读写与路径处理读取用例、保存报告6.1 with open和编码问题测试数据往往不在代码里写死而是放在外部文件里所以文件读写是刚需。读文本最标准的写法with open(test_data.txt, r, encodingutf-8) as f: content f.read()with语句会在代码块结束时自动关闭文件不用手动f.close()。编码问题特别坑Windows默认可能用GBK而文件是utf-8打开就乱码或抛UnicodeDecodeError。凡是自己创建的文件写入和读取都明确指定encodingutf-8能省掉一堆麻烦。写完文件后如果再读出来遇到中文乱码先别怀疑代码逻辑看一眼两侧的编码参数是否一致往往就是差这一个参数。6.2 读取JSON、CSV和配置文件接口测试最常见的用例存储格式是JSON读取很直接import json with open(cases.json, r, encodingutf-8) as f: cases json.load(f) for case in cases: print(case[name])CSV文件用csv模块import csv with open(users.csv, r, encodingutf-8) as f: rows list(csv.DictReader(f))配置文件用pyyaml读取得到的同样是字典。这些模块的用法不必死记但要知道数据从文件到Python对象基本都是这个套路打开文件交给对应解析器得到一个列表或字典然后就可以用前面学过的数据结构操作去处理。把测试数据和脚本分离有一个额外好处测试人员修改用例不用碰代码减少误改逻辑的风险。6.3 用pathlib解决路径问题初学者最常见的报错之一是FileNotFoundError: [Errno 2] No such file or directory。不是文件不存在而是运行时当前目录和文件所在目录不一致。命令行里在项目根目录运行和从子目录运行相对路径结果完全不同。解决办法是使用基于当前脚本文件位置的绝对路径from pathlib import Path base_dir Path(__file__).resolve().parent data_path base_dir / data / cases.json cases json.loads(data_path.read_text(encodingutf-8))Path(data) / cases.json就是路径拼接的Python写法比手写字符串拼接多一些跨平台优势在Windows和Linux上都不会错。把这个写成一个工具函数后面所有测试文件都用它定位数据文件和报告输出目录路径问题基本绝迹。我见过太多脚本在本地跑得好好的一放到Jenkins或者别人电脑上就报找不到文件几乎都是因为用了相对路径。7. 异常处理和断言脚本挂了要知道挂在哪7.1 try/except/finally的基本结构测试脚本运行过程中必然遇到异常网络不通、接口返回格式变化、文件不存在。不处理异常脚本会直接中断处理不好又容易把错误吞掉、让人不知道失败原因。标准结构是try: response requests.post(url, jsonpayload, timeout5) except requests.exceptions.Timeout: print(请求超时需要重试) except requests.exceptions.ConnectionError: print(网络连接失败) finally: print(这段无论如何都会执行)finally适合放清理动作比如关闭资源。Python还有一个else子句表示没有异常时执行的代码但不一定每次都用。核心原则捕获异常时要明确类型不要一上来就except Exception把什么都吞掉那样出问题时日志里只有一句出错完全没法排查。写except的时候可以连异常对象一起接住except requests.exceptions.Timeout as e在日志里把实际的异常信息打出来。7.2 让错误信息说人话测试脚本的价值在于失败时能快速定位。围绕这个目标有几个实操建议记录上下文catch到异常后把当前用例名、URL、参数一起打印或写入日志否则你只知道报错了不知道哪条用例在哪里报错。使用logging而不是printprint输出难以分级logging可以分debug、info、error并按时间格式化。import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) logger logging.getLogger(__name__) try: logger.info(开始执行用例: %s, case[name]) run(case) except Exception as e: logger.error(用例 %s 执行失败: %s, case[name], e)我建议从第一天写脚本就用logging哪怕现在只是打印一行信息也比print强。等用例规模上来之后看时间戳、看日志级别、筛错误信息这些能力全都要靠logging。7.3 主动raise和断言把不符合预期变成异常测试里有个概念叫断言本质是如果条件不满足就认为这次测试失败。pytest里直接写assertdef test_login_success(): resp post_json(/api/login, {username: alice, password: 123456}) assert resp[code] 200 assert resp[data][token], token不能为空assert后面加描述信息是个好习惯失败时能看到原因。有些情况下需要主动抛异常比如配置缺失、前置条件不满足直接raise ValueError(缺少必要的配置项)让脚本尽快停下来而不是带着错误状态继续跑导致大量无效用例。有些入门同学怕抛异常总觉得异常是坏事其实在测试脚本里异常恰恰是传递这里有问题信号的标准方式用例失败就是要把问题暴露出来。8. 从脚本到框架用类和对象组织测试代码8.1 类是什么把数据和操作打包在一起当函数越拆越散参数总是来回传的时候就该考虑用类了。类可以把状态和方法放在一起。举个例子一个API测试客户端class ApiClient: def __init__(self, base_url, token): self.base_url base_url self.token token def post(self, path, payload): headers {Authorization: fBearer {self.token}} return requests.post(self.base_url path, jsonpayload, headersheaders, timeout5)用的时候先创建对象client ApiClient(https://api.example.com, tokenabc) resp client.post(/api/login, {username: alice, password: 123456})__init__是初始化方法接受创建对象时的参数。self代表这个对象本身类里的每个方法第一个参数都是self否则无法访问实例的base_url和token。类与函数最大的区别就在这里函数每次调用都要重新传参而对象创建之后base_url和token就一直跟着这个对象走后面的方法调用不需要再重复传。8.2 用类把公共操作收敛起来适合用类组织的测试代码包括请求封装、数据库连接、文件读取工具、UI页面的元素操作。一个典型思路是创建一个BaseApi类所有接口模块继承它复用请求方法子类只写业务相关的接口方法。这样公共逻辑只维护一份修改base_url或加统一鉴权时只改一处。举一个我在实际项目中常用的做法在BaseApi的__init__里把base_url、token、请求会话都准备好子类继承后只用写业务方法class UserApi(BaseApi): def login(self, username, password): return self.post(/api/login, {username: username, password: password})这样测试用例调用的时候一行代码就能发起请求读起来非常清晰。框架能提升效率的原因不是用了多高级的语法而是把重复的东西收拢到了一处。8.3 继承和Page Object雏形UI自动化里的Page Object模式本质上就是类和继承的一种应用。每个页面建一个类把页面上的元素定位和操作封装成方法class LoginPage: def __init__(self, driver): self.driver driver def input_username(self, name): self.driver.find_element(id, username).send_keys(name) def click_login(self): self.driver.find_element(id, login_btn).click()再用继承定义一个和业务相关的用例页面类把公共导航逻辑放在基类。看起来比函数复杂但带来的好处是清晰的对应关系页面和数据模型变成代码里的对象测试用例读起来接近自然语言。入门阶段先掌握class、init、self、继承这四个概念就已经足够把脚本向框架推进一大步。先别急着追设计模式能把代码从一堆散函数整理成几个类对测试脚本来说已经是一次质变。9. 一个完整的登录接口自动化脚本从零到能跑9.1 需求与设计用一个最小但完整的例子把这些语法串起来登录接口自动化脚本。目标是对登录成功、密码错误、用户不存在三条用例跑一遍输出通过或失败。目录结构login_test/ ├── cases.json └── test_login.pycases.json存测试数据test_login.py读取数据、循环执行、断言、输出结果。这个结构虽然简单但已经包含了数据与脚本分离、函数拆分、日志输出这些工程习惯后面往pytest框架迁移也很自然。9.2 完整代码与解读先看测试数据文件[ { name: 登录成功, username: alice, password: 123456, expected: 200 }, { name: 密码错误, username: alice, password: wrong, expected: 401 }, { name: 用户不存在, username: nonexist, password: 123456, expected: 404 } ]然后是脚本import json import logging from pathlib import Path import requests logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) logger logging.getLogger(__name__) BASE_URL https://api.example.com def load_cases(): path Path(__file__).resolve().parent / cases.json with open(path, r, encodingutf-8) as f: return json.load(f) def login(username, password): resp requests.post(f{BASE_URL}/api/login, json{username: username, password: password}, timeout5) return resp.status_code def run_case(case): actual login(case[username], case[password]) if actual case[expected]: logger.info(用例 %s 通过, case[name]) else: logger.error(用例 %s 失败期望 %s实际 %s, case[name], case[expected], actual) raise AssertionError(f断言失败: {case[name]}) def run_all(): cases load_cases() for case in cases: run_case(case) if __name__ __main__: run_all()这段代码用到了前面所有章节的语法Path处理路径、json读取数据、for循环批量执行、函数封装、f-string和日志格式化、断言失败抛异常。直接把同一个BASE_URL改成真实地址就能跑起来尝试。我把断言失败的方式设计成抛异常是为了让脚本在碰到失败用例时立刻停下实际工作中如果希望跑完所有用例再汇总可以把失败结果收集到列表里循环结束后统一打印。9.3 运行与常见问题命令行进入login_test目录激活虚拟环境后执行python test_login.py如果要用pytest组织把run_case改成以test_开头的函数pytest会自动发现python -m pytest test_login.py -v常见问题就三个一是requests没有安装报ModuleNotFoundError在虚拟环境里python -m pip install requests即可二是路径不对确认脚本路径不是从其他目录用相对路径硬拼的三是编码问题打开/写入文件统一用encodingutf-8。把这三个问题记牢脚本跑不通时按顺序排查基本都能解决。如果pytest报告里显示collected 0 items检查一下文件名是不是以test_开头函数名是不是以test_开头以及是否在正确的目录下执行。带过不少刚转自动化的测试同学我发现最容易出现的结果不是学不会而是学太多但用不上。有人花两周啃装饰器、协程回头发现自己连一条接口用例都还没完整跑通过。真正有效的路线是先把这篇文章里的语法用到一个能跑的脚本里哪怕只是把手工点在Postman里的请求变成Python脚本就已经迈出了很大一步。之后再加数据驱动、加报告、加CI集成每一步都是在一个能运行的基础上做增量。遇到报错不要慌先读最后一行再往上看具体是哪一行代码触发的绝大多数语法问题都能在十分钟内解决。祝你的第一条测试脚本早日跑通。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表