做API接口自动化测试绕不开Python,这话说了无数遍,但每次带新人还是会念叨一次。写测试脚本虽然不像开发那样要搞复杂架构,但基础不牢靠,排查问题的时候真的会怀疑人生。今天这篇是接口自动化测试系列里的Python基础第4期,前面已经聊过变量、数据类型、条件循环和函数这些基础,这一期直接把和接口测试强相关的几个Python核心能力一次补齐:面向对象写接口封装、文件操作做数据驱动、异常处理让脚本扛得住网络波动、requests库完成真实HTTP调用。
这些内容不是课本上的语法罗列,而是你在写接口自动化测试脚本时每天都在打交道的搞钱技能。拿文件操作来说,接口测试最典型的方式之一就是数据驱动,测试数据放JSON或Excel里,代码从文件读数据然后批量执行用例,没有这块基础就没法做。再比如异常处理,接口请求遇到超时、断连、各种HTTP错误码,你总不能每次都在脚本里写十层if,try-except一套组合拳下来,脚本健壮性完全不是一个量级。至于requests,那更是接口测试的地基,没有它连“调接口”这个动作都做不了。
废话不多说,直接进正题。
1. 接口测试里的Python基础到底在学什么
1.1 测试脚本和开发代码的定位差异
写接口测试脚本和开发业务系统,虽然在同一个语言体系里,但思路完全不同。开发代码重点在“功能如何正确实现”,要考虑性能、并发、扩展性、可维护性,动不动就上设计模式。而接口测试脚本的核心价值是“把接口调用、数据校验、结果断言这件事高效地自动化”。你写的每一行Python,目标都应该是让测试过程更稳、更快、更好维护,而不是参加代码选美。
所以对做测试的同学来说,Python基础学到一个什么程度才够用?你不需要精通元编程、协程那些花活,但以下四个能力必须过关:第一,看得懂类、对象的写法,知道怎么用类来组织测试代码,因为现在主流接口自动化框架基本都是面向对象封装;第二,能读写文件,尤其是JSON和Excel,因为测试数据绝大多数存这两个格式里;第三,会写异常处理,网络环境远没有你想象的稳定,超时、断连、限流都是家常便饭;第四,能顺畅地写出调接口的请求代码,并知道怎么解析响应。这几个恰好就是今天要讲的内容。
1.2 本系列前几篇讲了什么,这篇补什么
简单回顾一下系列进度。前面几篇里,第一篇做了环境准备和接口测试理论入门,第二篇围绕Python基础第一部分,主要是变量、字符串、列表和字典这些数据类型,第三篇讲条件判断和循环,第四篇重点讲了函数和模块,还稍微提了一下Python的几种常用写法。到这一篇,基础语法已经覆盖得差不多,该往实战方向收了。
这一篇的定位就是“基础收尾+实战衔接”。类、文件、异常、requests这四块学完,你其实已经具备了独立写接口自动化脚本的底层能力。下一篇如果继续更新,就会进入真正的接口自动化框架实战,到时候你会发现这篇打的地基,全是之后要用到的东西。
2. 类和面向对象:把接口封装成能复用的模块
2.1 类和对象,先用大白话说清楚
面向对象这个概念,很多初学者一听到就头大。其实你用个例子三分钟就能搞懂。把类理解成一张“产品设计图纸”,对象就是按照这个图纸造出来的实物。图纸上标了这个产品有哪些属性(比如颜色、尺寸)和哪些功能(比如能开机、能关机),照着图纸做出来的每一台实物,都会有这些属性和功能,但具体数值可以各自不同。
在接口测试里,类的作用是什么?它把你的测试代码从“一段一段复制粘贴”变成“一套可复用的模块”。假设你有登录、注册、用户信息、订单列表四个接口要测,如果不做封装,每个接口都写一遍完整的请求代码,脚本会非常臃肿。更麻烦的是,如果接口地址变了或者基础请求配置改了,你得挨个文件去改。但用类把“发送请求”这个动作抽象出来,所有接口都基于同一个类来调用,改配置的时候只需改一处,这种省心你用了就回不去。
2.2 接口类封装的基础示例
先看一个最基础的类写法,了解语法后我们再逐步深化。
class LoginAPI: """登录接口的封装类""" def __init__(self, base_url): self.base_url = base_url self.session = requests.Session() def login_by_password(self, username, password): """账号密码登录""" url = f"{self.base_url}/api/login" payload = { "username": username, "password": password } resp = self.session.post(url, json=payload) return resp这里面有几点值得拆开说。__init__是构造方法,创建对象时会自动执行。你在初始化时传一个base_url进来,对象自己保存一份,这样后续方法里都能直接通过self.base_url拿到这个地址。self是指对象本身,在类里面写方法时必须作为第一个参数声明,实际调用时不用传这个参数,系统会自动传进去。
我再强调一下requests.Session()的使用意图。Session对象可以理解成一个“带状态的浏览器”,它会自动帮你保存Cookie等会话信息,如果你用同一个Session发多个请求,服务端会认为你是同一个客户端。这个在接口测试里非常重要,因为很多系统需要登录后才能访问其他接口,登录后产生的Cookie和Token都要在后续请求中带上,Session就是把这件事自动化的利器。
使用这个类的方式也很简单:
login_api = LoginAPI("https://api.example.com") resp = login_api.login_by_password("testuser", "123456") print(resp.json())创建对象时以LoginAPI("https://api.example.com")调用,类里的__init__方法会执行,base_url被存到self里。之后调用login_by_password方法时,只传username和password两个参数,因为self已经被系统自动传了。这就是类和对象最基本的使用节奏,后面所有的框架封装都建立在这个模式上。
2.3 类的继承:多接口共用一套逻辑
实际接口自动化测试里,不太可能只测一个接口。如果你每个接口都写一个独立的类,又重复写一堆相同的方法,还不如不封装。这时候就该让继承登场了。
继承就是让子类获得父类的属性和方法,子类再扩展自己的专属能力。用生活例子类比,父类是一台“通用打卡机”,能识别所有员工的工牌;子类是“研发部专用打卡机”,它继承了通用识别能力,还自己增加了“记录研发部加班时长”的功能。
在接口测试场景里,通常的写法是先写一个通用的基类,把所有接口共用的公共逻辑放进去,比如基本的请求方法、超时设置、日志记录、统一异常处理,然后每个业务模块的接口类去继承这个基类,只需要写自己独有的接口方法。
import requests class BaseAPI: """接口测试基类,所有接口类的父类""" def __init__(self, base_url): self.base_url = base_url self.session = requests.Session() self.timeout = 10 def get(self, path, params=None, **kwargs): url = self.base_url + path resp = self.session.get(url, params=params, timeout=self.timeout, **kwargs) return resp def post(self, path, json=None, data=None, **kwargs): url = self.base_url + path resp = self.session.post(url, json=json, data=data, timeout=self.timeout, **kwargs) return resp def close_session(self): self.session.close() class UserAPI(BaseAPI): """用户模块接口类,继承BaseAPI""" def __init__(self, base_url, token): super().__init__(base_url) self.session.headers.update({ "Authorization": f"Bearer {token}" }) def get_user_info(self, user_id): resp = self.get(f"/api/user/{user_id}") return resp def update_profile(self, user_id, nickname): payload = {"nickname": nickname} resp = self.post(f"/api/user/{user_id}/profile", json=payload) return resp这个简化的结构你直接照着写就能跑起来。第一层BaseAPI是通用的,定义了两个基础请求方法,统一管理了超时时间。第二层UserAPI继承基类后,先在自己的__init__里通过super().__init__(base_url)调用父类的构造方法,这样父类初始化好的session和base_url就被子类继承了,然后子类再往session的请求头里塞了一个token。为什么塞token?很多接口要求的登录凭证是通过Header传的,这样写之后,后续UserAPI发起的每个请求都会自动携带这个Header,不用每个方法都手动传。
接口测试时你会发现这种分层的好处:接口数量多的时候,公共逻辑在基类统一管控,比如调整超时设置、增加代理、统一打印日志,只需改一处,所有子类全部生效。而具体接口的差异逻辑各自维护,互不影响,代码结构也一目了然。
3. 文件操作与数据驱动:让测试数据和代码分离
3.1 打开文件的三种姿势和坑
文件操作用来干嘛?往小了说,你要读取一个JSON格式的请求体,或者把接口返回数据保存下来;往大了说,整个数据驱动测试的基础就是文件读写。先看最基础的文件读取。
# 写法一:直接open,记得手动关闭 f = open("test_data.txt", "r", encoding="utf-8") content = f.read() f.close() # 忘记关文件会导致资源泄露 # 写法二:with语句,自动关闭 with open("test_data.txt", "r", encoding="utf-8") as f: content = f.read() # 缩进块结束,文件自动关闭 # 写法三:读取全部行 with open("test_data.txt", "r", encoding="utf-8") as f: lines = f.readlines()三种写法我意见统一:优先用with。open()之后如果忘了close(),小脚本看不出来,跑长任务时会积累文件描述符,最终程序报“Too many open files”。而with语句会在代码块结束时自动帮你去关闭,省心很多。
encoding="utf-8"这个参数你也别忽略。Windows系统默认编码可能是gbk,如果读取的文件本身是utf-8编码,直接open不加encoding参数,遇到中文内容大概率会报UnicodeDecodeError。这个问题在接口测试里出现的频率非常高,因为接口返回的JSON通常都是utf-8编码。养成习惯,凡是open文件,统一加上encoding="utf-8"。
3.2 JSON文件读写,接口测试防身术
JSON是接口测试里出现频率最高的数据格式,没有之一。你请求接口要传JSON,接口返回的也是JSON。所以Python里面处理JSON的json模块,你的熟练度应该高到最后闭着眼睛都能写出来的程度。
先分清四个函数,很多人就在这里栽跟头:
json.load(fp):从文件对象读取JSON内容,解析成Python字典或列表json.dump(obj, fp):把Python对象写入到文件对象里,存成JSON格式json.loads(s):把JSON字符串解析成Python对象json.dumps(obj):把Python对象转换成JSON字符串
多了一个字母“s”含义完全不同。loads和dumps处理的是内存里的字符串,load和dump处理的是文件。写代码时分不清这几个,临时查资料倒没事,但面试一眼穿帮。
实际工作场景里最常用的方式是把测试数据单独存成一个JSON文件,测试代码读取该文件后按字段拿数据:
import json # 测试数据文件 content: test_data.json # { # "login": { # "username": "testuser", # "password": "123456" # }, # "user_id": 10086 # } with open("test_data.json", "r", encoding="utf-8") as f: data = json.load(f) username = data["login"]["username"] password = data["login"]["password"] user_id = data["user_id"] print(username, password, user_id)这样设计的好处是什么?数据和代码分离,以后要改测试数据,只动JSON文件,不用动代码。比如线上环境的账号密码和测试环境不一样,你只需要维护两份环境对应的JSON配置,代码完全复用。如果数据量比较大,还可以把一条条数据放到JSON数组里,循环调用测试用例,这就是数据驱动最朴素的形式。
3.3 接口测试参数化:从JSON批量读数据
既然是数据驱动,自然是让代码循环跑数据,而不是每来一批新数据就改一次代码。这里我用一个最简单的案例,直接演示怎么从一个JSON文件里读取一组用户数据,然后用这些数据批量执行登录接口的测试。
假设JSON文件login_data.json的内容是:
[ {"username": "testuser1", "password": "123456", "expected_code": 200}, {"username": "testuser2", "password": "wrongpwd", "expected_code": 401}, {"username": "", "password": "123456", "expected_code": 400} ]对应的测试代码可以这样写:
import json import requests def load_test_data(file_path): with open(file_path, "r", encoding="utf-8") as f: return json.load(f) def test_login_with_data(file_path): all_cases = load_test_data(file_path) for case in all_cases: resp = requests.post( "https://api.example.com/api/login", json={"username": case["username"], "password": case["password"]} ) actual_code = resp.status_code expected_code = case["expected_code"] if actual_code == expected_code: print(f"PASS: {case['username']} -> {actual_code}") else: print(f"FAIL: {case['username']} -> 期望{expected_code}, 实际{actual_code}")这段代码里体现了一个关键思想:测试用例的“数据”(账号、密码、期望状态码)全部来自外部文件,测试代码的“逻辑”是固定的。以后接口有新的测试组合,只需要往JSON文件里新增一条记录,测试方法一行不用改。这个模式往大了扩,就是pytest参数化的底层思想,理解它再学参数化会顺畅很多。
4. 异常处理:让接口测试脚本不轻易崩溃
4.1 try-except的完整语法结构
接口调用最大的特点就是结果不可预期。网络抖一下可能超时,服务端临时故障可能返回500,接口限流可能返回429。如果你不去捕获这些异常,脚本跑到一半就直接抛错退出,后面的用例全被阻塞,测试报告也拿不全。
所以异常处理对接口自动化测试来说,不是加分项,是必备技能。Python里最基础的异常处理结构是这样的:
try: # 可能会出问题的代码 resp = requests.get("https://api.example.com/api/data", timeout=5) except requests.exceptions.Timeout as e: # 超时时走这里 print(f"请求超时: {e}") except requests.exceptions.ConnectionError as e: # 连接失败时走这里 print(f"连接错误: {e}") except Exception as e: # 其他所有异常都会走到这里 print(f"未知异常: {e}") else: # try里没有异常时执行else块的代码 print("请求成功") finally: # 无论有没有异常,finally块的代码都会执行 print("这里一定会执行")try块里放你猜测可能出错的代码,except块用来捕获并处理指定类型的异常,else块在没有任何异常时执行,finally块保证不离不弃、无论什么情况都会执行。实际工作中使用频率最高的是try和except,一段里通常还会分几个except分别处理不同类型的错误,这样针对每种失败场景都能有对应的处理策略,而不是一把全接到同一个提示里。
4.2 请求异常处理的完整姿势
在接口测试里,requests库最常见到的几个异常就是超时、连接错误、HTTP错误状态、还有JSON解析失败。把这几个异常分开捕获、分别处理,测试脚本才能在遇到问题时优雅收场,而不是留下一堆红通通的Traceback。
import requests import json def request_with_exception_handling(method, url, **kwargs): """带异常处理的请求函数""" try: resp = requests.request(method, url, timeout=10, **kwargs) resp.raise_for_status() # 状态码为4xx/5xx时会抛出HTTPError return resp except requests.exceptions.Timeout: # 超时处理:可以重试一次,也可以将用例标记为失败但继续跑 print(f"[请求超时] {method} {url}") return None except requests.exceptions.ConnectionError: # 连接失败处理:多半是网络不通或服务未启动 print(f"[连接失败] {method} {url}") return None except requests.exceptions.HTTPError as e: # HTTP状态码异常,比如404/500 print(f"[HTTP异常] {method} {url} -> {e}") return None except Exception as e: # 兜底 print(f"[未知异常] {method} {url} -> {e}") return None # 使用示例 resp = request_with_exception_handling( "GET", "https://api.example.com/api/user/10086" ) if resp is not None: try: data = resp.json() print(data) except json.JSONDecodeError: print("响应内容不是合法JSON")这里有一个很多人容易忽略的用法resp.raise_for_status()。它会在状态码为4xx或5xx时主动抛出一个HTTPError异常。这意味着你不用自己判断if resp.status_code != 200再手动处理,异常机制帮你把错误路径统一收拢了,代码更简洁也更规范。另外,像json.JSONDecodeError这种异常也一样要捕获,有时候接口返回的是纯文本或HTML,解析JSON就会报错,捕获以后你能把数据和异常分开处理,测试报告才准确。
4.3 日志记录:出错以后还能复盘
接口测试脚本跑起来,打印一大堆print说实话也够用,但真正遇到问题想复盘时,你会发现自己根本找不到程序跑到哪一步挂的。print输出在终端里一旦滚动过去就没了,而且print本身就是临时的东西,代码里写多了,还要一行行删除。
更靠谱的做法是用Python自带的logging模块。它的好处是不用删,调试级别自己控制,日志既可以在控制台打出,也可以写入文件保存。配置好之后,运行多久、哪一步成功、哪一步失败一目了然。
import logging logging.basicConfig( level=logging.INFO, # 设置日志级别:DEBUG/INFO/WARNING/ERROR format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ logging.FileHandler("api_test.log", encoding="utf-8"), logging.StreamHandler() ] ) logger = logging.getLogger("api_test") logger.info("开始执行登录接口测试") try: resp = requests.post("https://api.example.com/api/login", json=payload, timeout=10) logger.info(f"登录接口状态码: {resp.status_code}") except Exception as e: logger.error(f"登录接口异常: {e}")个人建议在小项目里也尽早切换到logging,养成这个习惯成本很低,收益却很大。尤其是接口自动化测试跑大批量用例时,一个完整的日志文件能帮你精确定位是哪个用例在哪个环节出的问题,而不是每次都靠眼睛盯屏幕。
5. requests库实战:先跑通一个真实请求
5.1 安装和环境验证
requests是Python生态里最常用的HTTP客户端库,它的API设计极其人性化,几乎没有学习成本。接口自动化测试里绝大多数场景,一个requests就够用了。
安装很简单,命令行执行:
pip install requests如果项目用的是虚拟环境,记得先激活虚拟环境再安装。安装完成后,验证一下:
python -c "import requests; print(requests.__version__)"能打印出版本号就说明一切正常。如果提示ModuleNotFoundError,说明requests没装好或者当前Python环境不是同一个。这个问题在多人协作或者本地同时装了多个Python版本时特别常见,建议任何时候都用虚拟环境隔离项目依赖,别把包装到全局环境里。
5.2 GET和POST请求的完整示例
先看GET请求。它的典型场景是查询数据,请求参数拼在URL上。requests里的params参数专门用来传查询参数,库会自动帮你做URL编码,比手工拼字符串靠谱。
import requests # GET请求,查询参数通过params传 get_url = "https://api.example.com/api/posts" params = { "page": 1, "page_size": 20, "keyword": "接口测试" } resp = requests.get(get_url, params=params, timeout=10) print(resp.status_code) # 状态码 print(resp.url) # 实际请求的完整URL print(resp.json()) # 响应内容解析为JSONPOST请求更常用在提交数据、创建资源、登录等场景。它的请求体通常以JSON格式传递:
import requests # POST请求,JSON格式请求体 post_url = "https://api.example.com/api/login" payload = { "username": "testuser", "password": "123456" } headers = { "Content-Type": "application/json", "User-Agent": "Mozilla/5.0" } resp = requests.post(post_url, json=payload, headers=headers, timeout=10) print(resp.status_code) print(resp.json())在这里,json=payload这个参数是requests库特别设计的一个便捷入口,它会自动做三件事:把payload转成JSON字符串、设置Content-Type为application/json、把数据放到请求体里。所以如果传递的是字典格式,直接用json参数最方便。而data参数则更偏向传表单格式的数据,比如application/x-www-form-urlencoded。这两个用法要分清楚,接口对接的时候最怕两边对不上格式,最终表现就是服务端解析不出参数。
5.3 Session会话管理与Token传递
很多业务系统的接口都需要登录后才有权限访问,而登录后服务端返回的凭证,可能是Cookie,也可能是Token。用requests做接口调用时,Session对象能帮你自动维护登录状态。
拿最常见的Token认证来说,登录接口拿到Token后,后续每个请求的Header里都必须带上Authorization字段。手动在每个请求里传会累死,代码还冗余。用Session就不一样了,Session对象可以保存公共请求头和Cookie,后续请求自动携带。
import requests login_url = "https://api.example.com/api/login" payload = {"username": "testuser", "password": "123456"} # 创建Session对象 session = requests.Session() # 登录接口获取token login_resp = session.post(login_url, json=payload, timeout=10) token = login_resp.json().get("token") print(f"登录成功,token: {token}") # 把token写入Session的默认请求头 session.headers.update({ "Authorization": f"Bearer {token}" }) # 后续所有用session发起的请求都会自动带上token user_resp = session.get("https://api.example.com/api/user/10086", timeout=10) print(user_resp.json()) order_resp = session.get("https://api.example.com/api/orders?page=1", timeout=10) print(order_resp.json())这段代码里,登录成功后我只update了一次Session的headers,后面所有的get请求都会自动带上Authorization。这就是为什么前面讲类封装时,特意把session放在__init__里初始化,因为它本来就是用来做这种状态管理的。Cookie同理,如果接口认证方式是Cookie,服务端在登录接口的响应里Set-Cookie,Session会自动保存并在后续带上,不需要你手动处理。
5.4 断言:接口测试的最后一道关卡
接口调用成功了,然后呢?你总得确认结果是不是符合预期。这一步叫断言,也是接口测试的核心产出。Python内置的assert关键字能完成最简单的事,但用起来会出现信息量太少的问题,我直接举个例子对比一下。
import requests resp = requests.get("https://api.example.com/api/user/10086", timeout=10) data = resp.json() # 使用assert直接判断 assert data["code"] == 200 assert data["data"]["nickname"] == "测试用户" # 更推荐:断言失败输出可读信息 expected = "测试用户" actual = data["data"].get("nickname", "") assert actual == expected, f"昵称断言失败: 期望{expected}, 实际{actual}"第一条断言失败时,只显示AssertionError,你完全不知道具体是哪个条件、哪个值出了问题。加了描述信息后,失败原因一眼可见,排查问题的速度完全不一样。在真实项目里,还可以把断言逻辑封装成函数,输出更详细的测试报告。但不管怎么封装,断言的本质是一样的:确认实际结果与预期结果一致,不一致就标记为失败。
6. 常见问题与避坑指南
6.1 编码问题的两大经典现场
接口测试里编码问题出现的频率,高得超出想象。最常见的两种情况,第一种是读取文件时指定编码不对,Python直接报UnicodeDecodeError;第二种是接口返回内容包含中文,直接print出来是一堆乱码。
原因和处理方式都不复杂。读文件报错时,要知道目标文件是什么编码,然后通过encoding参数指定它。如果不知道文件编码,最简单的办法是用文本编辑器打开文件查看右下角编码格式,然后再回代码里对应设置。至于接口返回的中文乱码,很可能是响应头里的charset和实际内容编码不一致,需要手动指定resp.encoding。
import requests resp = requests.get("https://api.example.com/api/user/10086") # 如果接口返回内容是utf-8编码,但响应头没声明,可以手动指定 resp.encoding = "utf-8" data = resp.json() print(data)6.2 requests请求HTTPS接口时的SSL证书问题
很多接口测试环境用的是自签名HTTPS证书,直接用requests访问时,会报SSL证书验证错误。出现这种问题,基本就是verify参数控制的:
import requests import urllib3 # 关闭SSL证书警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # 跳过证书验证 resp = requests.get("https://self-signed.badssl.com/", verify=False, timeout=10) print(resp.status_code)这里有两个要点。第一,verify=False表示不验证证书,生产环境测试时一般不建议全局关闭验证,只在测试环境使用;第二,关闭验证时requests会输出一个警告,提醒你的请求不安全,用urllib3.disable_warnings可以把警告取消掉,让日志干净一些。
6.3 超时设置和重试机制
很多刚接触接口测试的读者会漏掉超时时间设置这一项。如果不设置timeout,requests的请求会一直等下去,万一网络不稳定或服务端响应很慢,你的脚本可能挂在那里十几分钟不回话,大批量跑用例时整个任务都会被拖垮。
超时设置的方式:
# 连接超时5秒,读取超时10秒 resp = requests.get(url, timeout=(5, 10)) # 或者整体超时10秒 resp = requests.get(url, timeout=10)更健壮的方案是再挂一个重试机制,requests本身不带重试,需要结合urllib3来做。用HTTPAdapter为Session配置重试次数,遇到常连接失败或5xx状态码时会自动重试,这个在实际执行大批量接口用例时格外管用。
import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() retry_strategy = Retry( total=2, # 最多重试2次 backoff_factor=1, # 重试间隔: 1s, 2s, 4s status_forcelist=[500, 502, 503, 504] ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount("http://", adapter) session.mount("https://", adapter) resp = session.get("https://api.example.com/api/posts", timeout=10)这个配置在真实项目里几乎是刚需。网络抖动时,重试能自动恢复,不用人工盯着。但也要注意,重试意味着请求会被重复发送,如果是创建订单、支付这类的非幂等接口,重试可能导致重复下单,这种情况下谨慎使用重试,或者先评估接口是否支持幂等。
6.4 接口测试脚本的通用避坑清单
做接口测试脚本时有很多细节问题,单独看都很小,但集合起来就是灾难现场了。我把自己常用的一些避坑项整理成表,你可以直接收藏当检查清单。
| 问题类型 | 典型现象 | 解决建议 |
|---|---|---|
| 编码问题 | 中文乱码、UnicodeDecodeError | 读文件显式指定encoding="utf-8",响应内容设置resp.encoding |
| SSL证书错误 | SSLError: certificate verify failed | 测试环境用verify=False,生产环境校验证书 |
| 超时未设置 | 请求一直挂起不返回 | 所有请求都设置timeout,建议连接5s读取10s |
| 请求体格式错误 | 服务端解析不到参数 | 确认接口要JSON格式还是表单格式,分别用json=和data= |
| 登录状态丢失 | 需要登录的接口返回401 | 用Session对象维护Cookie/Token,不手动逐次传 |
| 断言信息不清晰 | AssertionError看不出原因 | 断言时加描述信息,输出期望值和实际值 |
| 单测脚本耦合度高 | 改测试数据要改代码 | 数据和代码分离,测试数据放JSON/Excel文件里 |
7. 实操总结与下一步学习建议
到这儿,接口自动化测试最常用的Python基础能力已经串成一条线了。学完这一篇,你至少已经具备以下几条实打实的技能:用类封装接口、让请求代码复用,用文件读写和JSON处理做数据分离,用异常处理和logging让脚本在错误面前不崩溃,用requests和Session完成真实接口的请求与状态维护。这套组合拳拿去跑一个中小型项目的接口冒烟测试,已经足够了。
我在实际项目里的体会是,这几点基本知识单看都不难,难的是遇到问题的时候能组合起来用。比如数据驱动测试,表面上是文件读取的问题,实际上涉及JSON解析、循环遍历、请求发送、断言对比、日志记录一条链路。链路里哪个环节薄弱,整套自动化就跑不顺。所以这一篇的内容,建议你是真的把代码敲一遍,不要只是看。照着上面的例子,找一两个公开API或者自己本地启一个服务,把GET、POST、Session、数据驱动、异常处理全部串起来跑一遍,比你看十遍教程都有效。
接口自动化测试这条路上,Python基础只是第一道关卡,后续还有测试框架选型、测试数据管理、报告生成、持续集成这些内容等着踩坑。基础打牢了大楼盖得才高,这一篇的内容消化干净,进入框架实战才不会云里雾里。