资讯动态

从while循环看软件测试入门:边界思维与Python自动化实践

发布时间:2026/9/4 21:44:24 来源:尧图企业网站定制
如果你打算在 2026 年进入软件测试行业先别急着囤课。很多人买完一堆视频后会发现一个尴尬现象看别人写 while 循环轻轻松松自己动手就死循环背了一堆测试理论面试官问“这个输入框怎么设计用例”脑子还是空白。这不是学习能力的问题而是学习路径出了问题。绝大多数新人把“编程”和“测试”当成两件事来学先花两个月啃语法再花两个月找测试项目做。结果前面学的代码根本用不上后面做的项目又只是照着别人点一遍根本没有真正理解测试用例从哪里来。这篇文章想说的核心观点是while 循环不只是编程基础里的一个语法点它恰恰是理解测试思维的第一个放大镜。一个循环条件写错就会出现死循环对应到测试中就是边界值没测、退出条件没考虑、异常分支没覆盖。你如果能把 while 循环从“会写”练到“会测”软件测试的很多基本功其实已经打了一半。下面我会把这套软件测试入门到项目实战的路径拆开来讲。全程使用 Python 语言示例不需要你额外准备昂贵环境用一套本地环境就能把测试基础、自动化用例和接口测试项目全部串起来。文末也会给你一条适合 2026 年的学习路线帮你少走很多弯路。1. 为什么 2026 年会把“while 循环”和“软件测试”放在一起学先做一个判断2026 年的测试岗位早就不是“纯手工点点点”就能混下去的阶段了。从近年招聘趋势来看测试岗位对候选人的要求正在发生明显变化会写自动化用例至少会用 Python 或 Java。能看懂接口请求和响应不是只操作 UI。能分析日志定位问题是前端还是后端。具备编程思维可以写出清晰的循环、分支和异常处理逻辑。换句话说测试新人缺的不是“记住 while 循环的语法”而是缺一种能力面对一个有规则、有条件、有边界的业务逻辑你能判断它哪里最容易出错。举个例子。面试官给你一个函数最多重试 3 次请求第 1 次失败后继续重试超过 3 次就报“重试次数耗尽”。这个功能怎么测试你要考虑的第一个问题不是“用 pytest 怎么写”而是为什么循环条件要写成 attempt max_attempts而不是 attempt max_attempts如果写成当max_attempts 3时实际会尝试 4 次。这在线上就是一个非常典型的边界 bug。再往深一层想如果请求失败后代码忘记给attempt加 1会发生什么死循环。重试永远不会停止系统资源被耗尽。这就是 while 循环和软件测试的关系。测试的核心是检查“系统是否在任何条件下都符合预期”而 while 循环的核心是“在满足什么条件时继续、在满足什么条件时停止”。这两件事都必须对边界条件高度敏感。把 while 循环作为测试入门的第一课不是为了成为编程高手而是为了形成一种职业直觉看到任何条件判断先问三个问题。正常情况走什么分支边界临界值怎么处理异常情况下是否还能正常退出有了这个直觉后面再学测试理论基础就不会觉得那些概念是空中楼阁了。下面先回顾软件测试的基础知识再一步步进入实操。别嫌这些基础啰嗦很多人在项目实战里“执行完不知道算不算通过”就是因为基础概念没有建立起来。2. 软件测试基础先分清测试对象、测试层次和测试设计方法软件测试基础包含的内容很多但入门阶段真正需要反复理解的是一个闭环测试计划、测试设计、测试执行、缺陷管理、回归验证。你现在不需要把 CMMI、TMMi 这些体系全部背下来但至少要能回答以下三类问题。2.1 测试到底在测什么软件测试不是“找到所有 bug”而是“在有限资源下尽可能暴露风险”。所以评估测试效果时不要只看报告里写了多少个 bug还要看你是否覆盖了高风险的功能点。测试对象可以是代码函数、接口、功能模块、整个业务系统。不同层级对应不同测试阶段测试层次测试对象典型关注点常见执行人单元测试函数、方法、类输入输出、分支条件、异常处理开发工程师或测试开发集成测试模块之间的接口请求参数、响应数据、交互逻辑测试工程师系统测试完整系统业务流程、权限、兼容性、性能测试工程师验收测试面向用户的交付系统是否满足业务验收标准业务方、测试工程师自动化测试通常先落在单元测试和接口测试因为它们比 UI 测试稳定运行速度快容易排查失败原因。2.2 测试执行方式怎么选入门阶段会被各种名词绕晕比如手工测试、自动化测试、黑盒测试、白盒测试、灰盒测试。其实这些分类角度不一样不要放在一个维度里比较。手工测试和自动化测试区别是“人执行”还是“工具和代码执行”。黑盒测试和白盒测试区别是“是否关注内部实现”。所以完全存在“手工黑盒测试”的说法也存在“自动化的接口测试”。没有哪一种组合是绝对最好的关键看项目阶段。阶段推荐方式原因需求刚提测手工流程穿越提前验证业务流程是否完整新功能第一轮手工 少量自动化快速发现严重问题回归阶段自动化为主避免重复成本上线前冒烟 关键链路检查快速判断能否发布2.3 测试用例设计方法所有测试设计的核心可以简化为一句话把业务规则翻译成可执行的输入条件和预期结果。新手最常犯的错误是“想到什么测什么”这样既没有依据也没有办法证明覆盖是否充分。入门必须先掌握下面这三种方法等价类划分。把所有输入数据分成若干类每一类里取一个代表值进行测试。例如登录用户名合法格式是一类非合法格式是一类空值又是一类。这样设计用例可以减少重复但不代表不用测边界。边界值分析。bug 往往发生在输入范围的临界附近。比如年龄规则是 1 到 120 岁那么 0、1、120、121 都是重点测试数据。错误推测法。根据经验猜测系统哪些地方容易出错。比如用户输入的字符串包含空格、接口返回超时、数据表为空、网络断开。更进一步的因果图、判定表、正交试验在测试基础和面试中都可能碰到。但建议先别死记硬背后面我会用一个登录年龄校验的例子把等价类和边界值真正用起来。2.4 测试基础里最重要的执行习惯面试官经常问“你提测之后先干什么”这不是让你背流程而是看你会不会控制风险。比较稳妥的回答顺序是看开发提测说明确认这次改了什么。跑冒烟测试判断主流程是否能通。把高风险模块放在前面测试。发现问题后先保留现场证据再提单。回归时关注旧功能是否被新代码影响。这套习惯在真实项目里比背一百个概念都有用。因为测试工作本质是风险管理不是“把用例全部执行一遍就算结束”。3. while 循环从语法到调试先理解退出条件和死循环编程基础里有很多内容为什么单独把 while 循环拎出来讲因为 while 循环能把“边界条件”这个词变得非常具体。你可以用它做一个最简单的重试逻辑也可以用它实现复杂的轮询等待逻辑。无论多复杂都逃不过三个要素初始条件。循环继续或终止的判断条件。循环体内让条件状态发生改变的语句。下面先用一个最简单的例子说明。# demo_while.py # 模拟最多重试 3 次的任务每次失败打印提示 def retry_task(max_attempts: int 3): attempt 0 while attempt max_attempts: print(f第 {attempt 1} 次尝试开始) # 真实项目这里通常放网络请求或业务操作 # 此处用普通打印模拟执行后才让 attempt 自增 attempt 1 print(重试结束)这里的关键点在于attempt 1必须写在循环体内。如果把attempt 1删除attempt会一直是 0条件0 3永远成立程序就会一直打印直到你手动终止进程。这也是最常见的新手死循环问题。再来看一个稍微贴近业务场景的 while 轮询。假设你正在查询订单处理状态服务端先返回 PROCESSING几秒后返回 COMPLETED。客户端不能无限等下去所以要设置最大尝试次数。# poll_order.py import time def wait_order_completed(max_retries: int 5, interval: float 0.5): current 0 while current max_retries: current 1 # 模拟每次拉取到的状态 print(f第 {current} 次轮询查询订单状态) time.sleep(interval) # 实际项目中这里应该调用接口获取最新状态 # 如果状态等于 COMPLETED再 return print(达到最大尝试次数仍没有等到最终状态)这里有个容易被忽视的点current 1放在循环体开头所以第一次循环时current已经被更新为 1。如果业务上要求第 1 次查询之前要等待 0.5 秒写法应该调整。这种“先等待还是先查询”的时序差异就是典型的功能性 bug也是测试要关注的时序边界。3.1 while 与 for 的区别实际项目里 for 循环和 while 循环都能完成重复动作但选择标准可以很简单知道循环次数时优先用 for。不知道循环次数只知道终止条件时使用 while 更直观。重试、轮询、超时等待这类场景都是“次数不确定但必须有限”因此更适合 while同时要在代码里加上最大次数保护。等你进入接口测试项目后会看到很多超时重试机制的底层逻辑就是 while 循环。如果面试官问“while 循环死循环怎么排查”回答思路不应该是“把代码删了重启”而是看以下三处循环条件是否可能永远为真。循环体内是否修改了关键变量。修改变量的路径是否被 continue、return 或异常跳过了。比如下面这段代码就会出问题attempt 0 while attempt 3: try: # 模拟请求 pass except Exception: print(请求失败准备重试) continue attempt 1当请求每次都会抛异常时continue会让程序跳过后面的attempt 1导致 attempt 永远不增加从而形成死循环。正确写法是在 except 中或 continue 之前完成自增。你能一眼找出这个问题说明你已经有测试思维了。4. 环境准备与前置条件用 Python pytest 搭建最小练习环境本篇文章的实操会围绕 Python 展开。不建议为了测试入门专门安装很重的商业工具先用轻量工具把自动化思想打通更重要。下面是推荐的准备工作。Python 3.10 或更高版本版本请以本机可安装版本为准。pytest用于编写自动化测试用例并收集执行结果。requests用于调用本地演示接口。Flask用于临时搭建一个极简接口服务。不一定需要最新版本但建议使用虚拟环境避免污染系统 Python。# 进入项目目录 mkdir test_learning cd test_learning # 创建虚拟环境不同操作系统中 python 命令可能是 python3 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS / Linux: source venv/bin/activate激活成功后命令行提示符前面会出现(venv)。然后安装依赖。pip install requests pytest flask安装完成后可以先执行一个小命令验证环境。python -c import pytest; print(pytest.__version__)如果能打印出版本号说明环境正常。这里不要求你死记硬背具体版本只要安装成功即可。后面的示例中我会尽量让项目在当前目录下保持简单只包含几个.py文件。5. 测试基础实战为一个年龄输入规则设计测试用例在软件测试入门阶段最忌讳一上来就搭自动化框架因为自动化只是执行手段前提是“用例设计本身是有效的”。这里给出一个非常常见的业务规则用户注册时输入年龄要求是 1 到 120 之间的整数。先写一个用来被测试的函数。注意这个函数本身不需要很复杂重点是你能围绕它的输入条件设计用例。# age_checker.py def is_valid_age(age_str: str) - bool: if age_str is None: return False if not isinstance(age_str, str): return False age_text age_str.strip() if age_text : return False try: age int(age_text) except ValueError: return False if age 1 or age 120: return False return True这里把输入值统一理解为字符串方便演示界面输入框传参。真实项目中函数的入参类型可能更复杂也可能直接从 JSON 里读取字段。测试时需要根据接口定义来调整。再来写 pytest 测试代码。设计用例时先把输入分成等价类合法整数18、60。非法数字范围0、121、负数。非整数类型18.5、abc、空字符串。特殊输入None、两边带空格的字符串。边界值重点考虑1 和 120 应该通过0 和 121 应该拦截。# test_age_checker.py import pytest from age_checker import is_valid_age class TestUserAgeRule: pytest.mark.parametrize( input_value, [1, 120, 18, 60 ] ) def test_valid_age_input(self, input_value): assert is_valid_age(input_value) is True pytest.mark.parametrize( input_value, [0, 121, -1, 18.5, abc, , None] ) def test_invalid_age_input(self, input_value): assert is_valid_age(input_value) is False运行测试pytest test_age_checker.py -v预期会看到 4 个合法输入用例和 7 个非法输入用例全部通过。如果其中有没通过的就说明age_checker.py和测试代码之间对规则的预期不一致。这里想特别强调一点不要写“单独一个用例覆盖所有输入”的测试那是把多个结果混在一起失败时定位成本很高。像上面这样把每个输入值拆开并给测试方法取一个能表达业务含义的名字后续维护会更轻松。等你能熟练写出这种测试再去看“软件测试面试必背 100 例”这类资料会发现很多题目答案不再需要死记。比如面试官问等价类和边界值的区别你就可以直接讲上面这个例子的设计过程。6. 项目实战用本地 Flask 接口 while 超时轮询跑通自动化测试只测一个纯函数还远不够真实项目里的测试往往发生在接口层。为了不把你的机器环境搞乱我们本地搭建一个极简接口服务来模拟“查询订单状态”。这个项目会包括三部分一个本地的 Flask 接口服务。一个订单状态查询客户端其中包含 while 轮询逻辑。一组 pytest 自动化用例。先创建mock_server.py# mock_server.py import time from flask import Flask, jsonify app Flask(__name__) start_time time.time() app.route(/api/order/status) def order_status(): elapsed time.time() - start_time if elapsed 1.2: return jsonify({ order_id: DEMO001, status: PROCESSING }) return jsonify({ order_id: DEMO001, status: COMPLETED }) if __name__ __main__: app.run(host127.0.0.1, port5050)这个接口的含义是服务启动后 1.2 秒内返回 PROCESSING之后返回 COMPLETED。为什么做这个处理因为在测试“状态轮询”时客户端必须能应对“第一次调用还不满足目标条件”的情况。然后创建order_client.py封装一个带 while 轮询的查询函数# order_client.py import time import requests def fetch_order_status(base_url: str, order_id: str) - dict: url f{base_url}/api/order/status resp requests.get(url, params{order_id: order_id}, timeout2) resp.raise_for_status() return resp.json() def wait_order_completed( base_url: str, order_id: str, max_attempts: int 5, interval: float 0.3 ) - dict: current 0 while current max_attempts: data fetch_order_status(base_url, order_id) print(f第 {current 1} 次轮询当前状态{data.get(status)}) if data.get(status) COMPLETED: return data current 1 time.sleep(interval) raise TimeoutError(f订单 {order_id} 在 {max_attempts} 次尝试内未完成)再创建一个启动测试的test_order_client.py。由于测试需要调用真实接口运行测试前需要先启动本地的 Flask 服务。实际工程里可以使用 pytest fixture 自动开启测试服务但入门阶段先手动启动服务理解请求响应链路更重要。# test_order_client.py import pytest from order_client import fetch_order_status, wait_order_completed BASE_URL http://127.0.0.1:5050 def test_fetch_order_status_returns_expected_fields(): data fetch_order_status(BASE_URL, DEMO001) assert data[order_id] DEMO001 # PROCESSING 或 COMPLETED 都可能是当前返回值 # 因为测试取决于服务已经运行了多久。 assert data[status] in {PROCESSING, COMPLETED} def test_wait_order_completed_returns_final_status(): # 只要本地服务还处于启动早期 # 这个用例大概率会先看到 PROCESSING # 再经过多轮轮询后等到 COMPLETED。 result wait_order_completed( BASE_URL, DEMO001, max_attempts10, interval0.3 ) assert result[status] COMPLETED def test_wait_order_timeout_when_max_attempts_too_small(): # 如果最大尝试次数为 1且当前服务还没有返回 COMPLETED # 应当直接抛超时异常。 with pytest.raises(TimeoutError): wait_order_completed(BASE_URL, DEMO001, max_attempts1, interval0.1)运行时要开两个终端。第一个终端启动接口服务python mock_server.py看到下面类似输出说明服务启动成功* Running on http://127.0.0.1:5050第二个终端执行测试pytest test_order_client.py -v预期能看到前面两个用例通过第三个用例也会通过因为它期望抛超时异常。但有一个现实问题需要你注意如果服务已经启动超过 1.2 秒后再执行第三个用例服务端返回的就已经是 COMPLETED此时 max_attempts1 也能第一次成功不会抛超时第三个用例反而会失败。这属于测试设计时不稳定的外部状态问题。怎么解决真实工程里会通过 mock 接口返回值、控制时间戳等方式让测试结果稳定。在这里你只需要理解两个结论while 轮询在现代接口测试中非常常见。测试用例必须保证前置条件可控否则结果会不稳定。给一个更稳定的实现方式是在测试中使用 monkeypatch 替换fetch_order_status这样不再依赖时间和服务状态。# test_order_client_mock.py import pytest from order_client import wait_order_completed def fake_fetch_always_completed(base_url, order_id): return {order_id: order_id, status: COMPLETED} def test_wait_order_completed_with_mock(monkeypatch): monkeypatch.setattr( order_client.fetch_order_status, fake_fetch_always_completed ) result wait_order_completed( http://fake, DEMO002, max_attempts3, interval0.1 ) assert result[status] COMPLETED这个测试文件的好处是不启动 Flask 服务也能跑因为根本不发起真实 HTTP 请求。执行pytest test_order_client_mock.py -v从这里可以看到测试进阶的一个重要思想先保证测试用例本身稳定再去处理真实环境的复杂性。7. 常见问题与排查思路无论你是刚刚搭环境还是已经写完了用例下面这些问题几乎都会遇到。建议收藏后按表排查。问题现象可能原因排查方式解决方案pytest 提示 no tests ran文件名或函数名不符合 pytest 收集规则确认文件是否以 test_ 开头函数是否以 test_ 开头把文件命名为 test_.py函数或类方法命名为 test_运行 pytest 时 import 报错 ModuleNotFoundError当前目录没有被加入 Python 搜索路径先看是否在项目根目录执行命令在项目根目录执行 pytest必要时创建init.py 或调整 PYTHONPATHwhile 循环一直不退出循环体内没有修改关键变量在循环内打印关键变量观察是否为固定值在循环条件依赖的变量变化处加入更新逻辑while 循环使用了 continue 后出现死循环continue 跳过了变量自增代码检查 continue 后面的代码是否无法执行在 continue 之前完成变量更新或改用 for 循环接口请求超时本地服务没有启动或端口号不一致在浏览器访问 http://127.0.0.1:5050/api/order/status先启动 mock_server.py再执行测试测试结果不稳定测试依赖外部当前时间或服务状态查看失败用例是否受前置条件影响使用 mock 固定返回值保证测试可重复Flask 启动提示端口被占用5050 端口被其他程序占用命令行查看占用进程改用其他端口并同步修改 base_url测试代码通过但浏览器手工访问失败测试里请求的 host 或 port 不一致对比测试代码与手工访问地址统一使用 127.0.0.1 和相同端口排查问题有一个总原则不要看表象猜原因先看报错输出和最新日志。命令行工具里pytest -v已经给你打印了每个用例的执行结果几乎每个失败都能定位到具体断言行。你只需要跟着报错去检查业务函数或测试前置条件即可。8. 最佳实践与工程建议做完上面的项目后你已经具备了把“测试基础 while 循环 接口测试”串起来的能力。但要进入真实项目还需要注意下面这些工程问题。8.1 给 while 循环加保护只要代码里出现 while就要警惕无限循环。真实项目里重试和轮询不能无限进行必须做到以下几点。给循环加最大尝试次数或最大超时时间。每次重试之间加退避时间避免高频请求压垮服务。把关键变量的变化过程打印到日志中。当达到最大尝试次数后不要静默失败而是抛出明确异常。举个例子重试外部接口时最好使用指数退避。第一次失败后等 0.5 秒第二次等 1 秒第三次等 2 秒。这样可以降低下游服务压力。8.2 测试用例要反映业务规则测试用例最容易犯的错是只测“正常路径”不测“边界和异常”。我在前面年龄校验的例子里把 0、121、空值都做了断言这就是业务规则驱动测试。你要学会在动手写用例前把业务规则整理成一张输入条件表再让用例覆盖每一条。如果开发改了需求比如年龄上限从 120 改成 150你只需要修改测试数据中关于 121 的预期并且考虑 150、151。测试用例忠实记录需求变化本身就是一种文档价值。8.3 配置不要硬编码本地示例里直接写BASE_URL http://127.0.0.1:5050是可以的但真实项目里环境有测试环境、预发布环境和生产环境。建议把环境地址放在环境变量或配置文件中不要写死在代码里。# config.py import os BASE_URL os.getenv(TEST_BASE_URL, http://127.0.0.1:5050) MAX_RETRIES int(os.getenv(MAX_RETRIES, 5))这样当测试环境变化时你不需要改代码只要在启动或 CI 里设置环境变量即可。8.4 数据隔离与安全边界做接口测试时必须明确一件事只能在你被授权的测试环境中操作。不要用生产环境数据去跑“删除”“批量修改”之类的测试用例。如果实在需要真实业务数据脱敏也要先申请权限并在测试结束后清理数据。另一个常见风险是测试账号权限过高。建议使用最小权限账号避免误操作影响核心数据。这类规范不是空话而是在很多生产事故中总结出来的真实教训。8.5 测试代码也要写清楚很多人误以为测试代码是“一次性脚本”写得很随意。实际上测试代码的生命周期比功能代码还长。你这次写完下个迭代还要继续跑。所以建议给每个测试类加注释说明这条业务规则来自哪里为什么选择这些边界值。函数命名尽量体现行为比如test_age_upper_boundary_150_should_pass这样失败时能快速判断是哪个场景出了问题。8.6 缺陷报告是测试的交付物你在项目中发现问题后需要填写缺陷报告。一份能帮助开发快速定位的缺陷报告应该包括标题、前置条件、复现步骤、实际结果、预期结果、截图或日志、测试环境。不要只写“页面报错了”这种描述对解决 bug 没有价值。写清楚“输入 18 位身份证号后点击提交接口返回 500后端日志显示字段超长”开发才能定位。9. 项目实战之后的下一步不要急着跳着学性能测试很多初学者学完一个接口测试项目就想去学性能测试、安全测试、测试开发这种跳跃容易让基础不牢。我更建议你按这条路线继续把 while 循环相关的练习题再刷一轮特别是死循环排查和重试逻辑。补充软件测试基础中的用例设计方法把等价类、边界值、判定表用到实际需求中。多写几个接口测试项目不局限于登录和订单。可以尝试用户查询、文件上传、数据导出。在团队中主动承担回归测试任务观察旧用例为什么会失败代码变更对测试结果产生了什么影响。再接触测试平台、CI/CD、容器化测试环境。与其把“软件测试面试必背 100 例”全部背完不如先把几个经典功能用例写通透。面试官问“你如何测试一个 while 重试方法”你能把等价类、边界值、死循环排查、超时保护、mock 返回值讲清楚已经能证明你不是只会背概念的测试新手了。复盘一下这篇文章的内容先从代码思维切入讲清楚 while 循环与软件测试基础之间的关系再带你设计了一个年龄校验输入框的测试用例覆盖等价类和边界值最后用一个本地 Flask 接口项目演示了如何用 while 轮询处理接口状态并用 pytest 执行自动化断言。你不需要继续纠结“学多全”先把这个最小闭环跑通再基于自己的项目和岗位需求做深度的测试设计。如果后面遇到具体问题比如 pytest 的 fixture 怎么写、接口返回字段不确定怎么处理、本地测试服务如何自动化启停可以按这里提供的思路继续拆解也可以在我后面的文章中找到对应扩展。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价