自动化测试做了这么多年,我最强烈的体感就是:十个新手有九个把精力花在学框架、写脚本上,结果往往第一步就被环境卡住——今天装个依赖把系统搞乱,明天换台电脑跑不通,后天浏览器一升级脚本就全面崩盘。说句大实话,环境搭建这种事,看起来只是装几个软件,实际它决定了你后续所有脚本能不能稳定复现,是整条自动化测试链路的基座。这篇我就以 Python 为技术栈,把自动化测试环境从零到一怎么搭、为什么这么搭、中间有哪些坑要绕,完完整整过一遍。适合刚入门的测试工程师,也适合打算在团队里统一环境的同学参考,按着做基本能少走大半年的弯路。1. 动手前先想清楚:环境里到底要装什么1.1 把环境拆成四个层次很多教程一上来就让你 pip install 一堆东西,装完也不知道什么是什么。我的建议是,先把自动化测试环境拆成四个层次来看,每一层解决一类问题。第一层是 Python 本身运行环境。自动化脚本本质上是 Python 程序,需要稳定的解释器、正确的版本,以及和它配套的包管理工具。这一层出错,后面所有东西都跟着跑偏。第二层是依赖库层。做 Web 自动化要 selenium、playwright,做接口测试要 requests、pytest,生成报告要 allure-pytest,这些第三方库之间还有版本依赖关系,比如某个版本的 selenium 可能要求 urllib3 不能低于某个版本。这一层最容易出现“装了一堆包,最后互相打架”的状况。第三层是浏览器驱动层。Web UI 自动化脚本要驱动真实浏览器,而浏览器和驱动之间是严格版本匹配的关系。Chrome 一升级,对应版本的 ChromeDriver 没跟上,脚本立刻报废。我在实际项目里见到太多人因为驱动问题排查了好几天。第四层是项目级配置。测试代码要跑起来通常还需要各类环境配置,比如被测系统的地址、账号数据、超时时间,以及 pytest 收集用例的目录规则、报告插件的参数。这一层管的是“脚本在什么条件下执行”。所以在动手装环境之前,脑子里先有这个分层模型。后面每一步操作,你都能知道自己在解决哪一层的事情。1.2 技术选型背后最容易被忽略的原则具体到用什么工具,网上争论特别多。但我建议新手遵循几个原则,比盲目追新重要得多。第一个原则是跟随团队主流技术栈,不要自己另起炉灶。如果团队之前用 Java 那套自动化框架,你要引入 Python 就得想清楚:是并行,还是替换,还是只做新项目的试点?技术选型是一个组织行为,不只是个人喜好。我见过不少同学自己搭了一套很漂亮的环境,结果团队没人会用,最后被搁置。第二个原则是优先选择社区活跃、文档齐全、问题能搜到答案的工具。Python 自动化测试领域,pytest 的地位没什么争议,selenium 虽然近年受到轻量级框架的冲击,但它依然是 Web UI 自动化绕不开的选项。requests 做 HTTP 接口测试也是事实标准。冷门工具往往在搭建环境时就让你翻遍全网找解决方案,性价比太低。第三个原则是环境必须可复现。什么叫可复现?就是你今天花三天搭好的环境,明天换一台新机器,用一个 requirements 文件加几条命令就能一模一样的恢复出来。凡是做不到这一点的环境方案,都会在团队协作和持续集成阶段成为灾难。想清楚这三条原则之后,再进入具体实操,思路会清晰得多。2. 基础运行环境:Python、虚拟环境与依赖管理2.1 Python 版本怎么选Python 版本选择是个看似简单、实际上影响深远的问题。你写代码的时候用的是 3.11 的新语法,结果 CI 机器上还是 3.8,脚本跑起来直接语法报错,这种问题我处理过太多次。我个人的建议是,新项目直接选 3.10 或者 3.11。这两个版本足够稳定,主流自动化测试库的兼容性也都已经跟上。3.12、3.13 虽然更新,但对一些老牌依赖库的支持还存在窗口期,如果不是要尝鲜,没必要冒险。同时,明确一个团队统一的版本,比如大家都用 3.11.9,然后把这个版本写进项目文档和 CI 配置里。还有一个细节,Windows 环境下安装 Python 的时候,记得勾选“Add Python to PATH”。很多人装完 Python 在命令行输 python 提示找不到命令,十有八九是这一步漏掉了。如果你电脑里同时有多个 Python 版本,建议用 python3.11 这种带版本号的命令来区分,不要裸用 python,否则你自己都分不清当前用的是哪个版本。在 macOS 上,我建议优先用官方安装包或者 homebrew 安装,尽量避免系统自带的 Python,因为系统版本通常偏低,而且和系统工具链有耦合。Linux 服务器上则要注意,很多发行版默认自带的 python 是 Python 2 或者版本较低,需要单独安装。2.2 虚拟环境:隔离比想象中更重要虚拟环境这个知识点,我在面试时几乎必问,但真正用得好的人不多。为什么要虚拟环境?举个例子,你做项目 A 的时候用到了 django 5.0,后来做项目 B 需要 django 4.2,如果你不用虚拟环境,直接在全局环境里来回切换版本,很快会发现系统 Python 环境变成了“垃圾场”,装了什么、动过什么都没法追踪。虚拟环境的核心价值就是隔离——每个项目有自己的依赖目录,互不污染。Python 自带的 venv 模块是官方推荐方案,从 Python 3.3 开始就内置了,新项目我基本都用它。用法很简单:# 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Linux / macOS source venv/bin/activate # Windows PowerShell venv\Scripts\Activate.ps1 # Windows CMD venv\Scripts\activate.bat激活之后,命令行提示符前面会多出一个 (venv) 前缀,这时候你执行的 pip install、python 命令都会作用在这个虚拟环境里。conda 也可以做虚拟环境,但它更侧重数据科学场景,对纯自动化测试项目来说有点重。我在 Windows 和 Linux 下都用 venv,实测下来最省心,不需要额外安装,脚本化也方便。重要提示:虚拟环境目录(venv 文件夹)本身不要提交到代码仓库里,它是每台机器各自生成的。正确做法是提交 requirements.txt 这类依赖清单,新机器上用命令重建。2.3 依赖管理与版本锁定依赖管理直接决定你的环境能不能复现。很多人在本地跑通后,把代码丢给同事,同事跑不起来,打开报错一看:模块缺失、版本冲突。问题就出在依赖没有锁定。先看最基础的方案。激活虚拟环境后,安装你需要的库:pip install pytest selenium requests然后把当前环境里的所有依赖导出到文件:pip freeze > requirements.txtpip freeze 输出的每一行都是“包名==精确版本号”,比如 pytest==8.2.0。这样团队里其他人拿到 requirements.txt 之后,在自己的虚拟环境里执行:pip install -r requirements.txt就能装出几乎一模一样的环境。不过 pip freeze 有个缺点:它会列出所有间接依赖,文件冗长且不区分哪些是你直接需要的。所以我更推荐在实际项目里配合 pip-tools 这类工具来管理。简单做法是维护一个 requirements.in 文件,里面只写直接依赖,然后通过 pip-compile 生成锁定的 requirements.txt。这样升级依赖时,只需要改 requirements.in,重新编译即可,间接依赖的锁定由工具自动处理。版本锁定的价值到后期会越来越明显。有一次我把环境的 pytest 从 7.x 升到 8.x,结果某个用于生成测试报告的插件不兼容,全部环境直接失效。后来我老老实实把所有依赖锁定到精确版本,升级之前先在单独的分支里验证,再合并到主环境。这条经验换来的教训是:环境升级要像代码升级一样走流程,不能随手乱来。3. 核心实操:从零开始完整搭建一套可运行的环境3.1 三步创建项目环境拿一个实际场景来走一遍。假设现在要给某个 Web 管理后台做 UI 自动化测试,我要在空目录里把环境搭起来。第一步,创建项目目录和虚拟环境:mkdir ui_auto_test cd ui_auto_test python -m venv venv source venv/bin/activate第二步,安装核心依赖。这里我建议分两次装,先装基础测试框架,再装 Web 自动化相关组件,这样如果出错,容易定位是哪一部分的问题:pip install pytest pip install selenium webdriver-managerwebdriver-manager 这个库强烈推荐,它的作用是由脚本自动检测浏览器版本并下载匹配的驱动程序,解决了手动维护驱动的痛点。后面会细说。第三步,验证环境是否正常。在命令行输入:python -c "import pytest, selenium; print('env ok')" pytest --version如果输出正常,说明基础环境没问题。这一步看起来简单,但很多人跳过它,直接复制网上的教程代码,结果代码跑不起来,又回头怀疑环境有问题。到这里,一套最基础的 Python 自动化测试环境已经跑通了。接下来要把它变成一个能实际写用例、能出报告的项目骨架。3.2 接入 Web UI 自动化:浏览器驱动配置Web UI 自动化里,浏览器驱动是那种“平时感受不到它存在,一旦出问题就让你生不如死”的东西。selenium 通过 WebDriver 协议来操控浏览器,而 WebDriver 是一个独立于浏览器之外的驱动程序,它必须和你本地的浏览器版本严格匹配。比如你用 Chrome 126,那么 ChromeDriver 也必须是匹配 126 的版本。浏览器一旦自动升级到 127,旧驱动立刻失效。传统做法是自己去手动下载对应版本的驱动文件,再配置系统 PATH 或者在代码里指定路径。这做法简直是工程毒瘤——每次浏览器升级都要人肉维护一遍。现在我用的是 webdriver-manager,在代码中直接让程序自动管理驱动:from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service) driver.get("https://example.com") print(driver.title) driver.quit()首次运行时,webdriver-manager 会检测你本机 Chrome 版本,自动下载匹配的 ChromeDriver 并缓存起来,后续运行直接复用缓存。这样团队里其他人拿到代码,不需要手动配置任何路径,跑起来就能用。不过要注意,webdriver-manager 需要能访问驱动的下载站点,有些企业内网环境访问不了,或者下载超时。解决办法有两个:一是从别的机器下载好驱动文件,通过离线安装方式汇总到团队内网;二是如果是企业内部的镜像源,则配置好下载镜像地址。说白了,核心思路是把“网络不可控因素”前置,提前准备好离线包。如果你用的是 Edge 浏览器,也可以用对应的 EdgeChromiumDriverManager,代码写法几乎一样。在团队环境里,我通常建议统一浏览器版本,比如统一用 Windows 10 加固定版本的 Chrome,别让每个人机器上的浏览器版本千差万别,否则驱动版本管理会很痛苦。3.3 配置 pytest 与测试项目骨架环境搭好之后,要让 pytest 真正理解你的项目结构,需要做几件很小但很关键的事情。第一件事,建立目录结构。我习惯的项目结构大概是这样的:ui_auto_test/ ├── venv/ ├── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── test_login.py │ └── test_order_flow.py ├── pages/ │ ├── __init__.py │ ├── login_page.py │ └── order_page.py ├── config/ │ └── config.yaml ├── reports/ ├── requirements.txt └── pytest.initests 目录用来放测试用例,pages 目录放页面封装层,config 放配置数据,reports 放报告输出。这个分层不是随便来的——用例层、页面对象层、数据配置层分开之后,改一个页面结构只需要改 pages 里面对应的类,而不是去几百个用例里翻找定位元素。比如登录页封装成一个类:from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver class LoginPage: def __init__(self, driver: WebDriver): self.driver = driver def input_username(self, username: str): self.driver.find_element(By.ID, "username").send_keys(username) def input_password(self, password: str): self.driver.find_element(By.ID, "password").send_keys(password) def click_login(self): self.driver.find_element(By.ID, "login_btn").click()第二件事,写一个 pytest.ini 配置文件。它让 pytest 按照你定义的规则去收集用例:[pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = -v --tb=short --html=reports/report.html这个配置的意思是:从 tests 目录开始找用例,只找 test_ 开头或者 Test 开头的类和函数,运行时输出详细日志并生成 HTML 报告。addopts 里的 --html 参数需要先安装 pytest-html 插件,项目依赖里加一行即可。第三件事,处理 fixture。conftest.py 是 pytest 的全局配置钩子,我把浏览器初始化和释放放在这里:import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service @pytest.fixture(scope="session") def driver(): service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service) driver.implicitly_wait(10) driver.maximize_window() yield driver driver.quit()使用 fixture 后,每个测试函数里直接声明参数 driver 就能拿到已经初始化的浏览器实例,不需要每个用例都自己 new 一个浏览器。测试结束,pytest 自动执行清理,浏览器进程不会残留。关于 scope 的设置,我建议用 session 级别,因为浏览器在会话里复用可以大幅减少反复启动的开销,代价是有状态,测试之间的数据需要自己注意清理。如果用例之间要强隔离,那就改成 function 级别,牺牲一点速度换稳定。到这一步,Web UI 自动化环境的搭建就算闭环了:能启动浏览器,能写用例,能出报告。4. 接口测试环境的快速搭建与数据管理4.1 requests 加 pytest 的组合很多团队的自动化测试入口其实是接口测试,相比 UI 测试,它更稳定、执行更快、维护成本更低。接口测试环境的搭建比 UI 简单不少,但有一个核心难点击穿:怎么优雅地处理测试数据。我推荐用 requests 加 pytest 的组合。安装依赖:pip install requests pytest然后写一个最简单的接口用例:import requests def test_get_user_info(): resp = requests.get("https://example.com/api/user/1", timeout=10) assert resp.status_code == 200 assert resp.json()["code"] == 0这里看起来简单,但实际工程中没那么容易。被测接口通常需要登录态,也就是你要先调用登录接口拿 token,然后后续所有请求都带上它。这时候我用一个 session 级别的 fixture 来管理:import pytest import requests @pytest.fixture(scope="session") def token(): resp = requests.post( "https://example.com/api/login", json={"username": "test_user", "password": "test_pass"}, timeout=10, ) return resp.json()["data"]["token"] @pytest.fixture(scope="session") def client(token): s = requests.Session() s.headers.update({"Authorization": f"Bearer {token}"}) return s def test_get_user_info(client): resp = client.get("https://example.com/api/user/1", timeout=10) assert resp.status_code == 200requests.Session 会自动维持 cookie,配合请求头统一附加 token,让每条用例不需要重复写鉴权逻辑。这里我想特意说明一下:接口测试用例的高可维护性来源于“鉴权、基础 URL、公共参数”这类的公共逻辑都收敛到了 fixture 和配置层,而不是散落在每个用例里。4.2 测试数据与配置文件分离接口测试和 UI 测试都会面对同一个问题:测试环境地址、账号密码、超时时间这类配置,随时可能变化,写死在代码里会让环境切换变成噩梦。比如你今天连测试环境,明天连预发环境,代码里写死 https://test.example.com,那就只能全局搜索替换,麻烦不说,还容易漏。我的做法是使用 yaml 或 ini 配置文件,结合 pytest 的 conftest 加载配置,然后通过 fixture 注入。安装依赖:pip install pyyamlconfig/config.yaml 内容:base_url: "https://test.example.com" timeout: 10 users: admin: username: "admin_test" password: "admin_passwd" normal: username: "user_test" password: "user_passwd"conftest.py 里加载配置:import pytest import yaml @pytest.fixture(scope="session") def config(): with open("config/config.yaml", "r", encoding="utf-8") as f: return yaml.safe_load(f) @pytest.fixture(scope="session") def client(config): import requests s = requests.Session() s.base_url = config["base_url"] return s这样换环境时,只需要改配置文件,一行代码都不用动。我在团队里推广这个模式之后,测试人员切换环境的时间从半小时缩短到三分钟。环境变量的引入是更进一步的方案。比如敏感数据放外部的 .env 文件,代码里通过 os.environ 读取,这样配置文件本身也能提交到代码仓库而不会泄密。这里建议用 python-dotenv 来管理,项目依赖里加 python-dotenv,conftest 顶部加载一下即可。5. 高频故障排查手册5.1 依赖装不上、装错版本依赖相关的问题占据了自动化测试环境排障的半壁江山。典型场景一:pip install 极慢或者直接卡死。大部分情况下,把 pip 默认源切换到国内常用的 PyPI 镜像就能解决。全局配置:pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple如果公司内网有自己的 pip 源,优先配置内网源,速度一般比公共镜像还快。注意一点,不要因为这个原因就在代码里随便写死某个源地址,最好把源配置放在本机的 pip 配置文件中,让 requirements.txt 保持干净。典型场景二:根据 requirements.txt 安装时报依赖冲突。解决思路是先检查报错中提到的包冲突双方,打开它们的依赖声明看有没有明确版本上限,比如某些库只支持 urllib3<2.0。遇到这种问题不要硬调版本,要往上层找:是不是某个包版本太新,导致引用了不兼容的底层库。最直接的解法是暂时固定那个底层库到你已知能正常工作的大版本区间。典型场景三:同一个包,本地跑得好好的,换台机器就装不上。这种基本都是系统层面的差异,比如某个依赖有编译安装环节,Windows 上缺 MSVC 编译器,Linux 上缺 build-essential。遇到这种包,优先找有没有预编译的 wheel 包,不要试图在每台机器上编译。5.2 浏览器驱动起不来驱动相关的故障,典型特征就是报错信息里带着 session not created 或者 Unable to find chromedriver。第一步要排查的是驱动和浏览器是否匹配。新版 Chrome 可以在地址栏输入 chrome://version 查完整版本号,然后用这个版本号去对驱动的版本支持范围。用了 webdriver-manager 也不能完全高枕无忧——有少数情况是它自动匹配失败,这时你可以手动指定驱动版本。另一个隐藏坑是路径问题。旧代码里如果用了 exe 路径写死的方式,比如 webdriver.Chrome(executable_path="d:/driver/chromedriver.exe"),换台电脑没有这个路径,脚本必挂。所以不管本地怎么方便,最终提交的代码一定要优先考虑驱动自动管理,或者至少用相对路径加 PATH 配置。还有一个低级但常见的坑:浏览器进程残留。测试异常退出时,浏览器进程没被关干净,导致下一次启动时浏览器实例数超限,在 Linux 服务器上特别常见。排查方法很简单:任务管理器或者 ps 命令看看有没有残留的 chrome 进程,有就先杀掉再跑。长期解法是代码里用 try/finally 或者 pytest 的 fixture teardown 确保退出时清理。5.3 换台机器跑不通这个问题最打击团队士气,也是很多环境搭建失败的最后一道坎。换机器跑不通,十有八九是环境没有做到可复现。具体表现为:依赖清单不完整,requirements.txt 丢了或者没有更新;虚拟环境没重建,直接拷贝了旧机器的 venv 目录,结果因为路径、Python 版本不匹配导致各种诡异报错。正确的做法是:新机器上连虚拟环境都不需要拷贝,直接按流程重建。先确认 Python 版本一致,再创建虚拟环境,再安装 requirements.txt。我建议团队里把这几条命令写进一个 setup.sh 或者 setup.bat 脚本,新同学克隆代码后执行一键脚本就能把环境拉起来。这比任何口头指导都可靠。如果你在 Windows 上搭好了环境,而 CI 服务器是 Linux,那么你测出来的路径分隔符、文件编码、字符大小写问题都会冒出来。最好从一开始就让环境贴近生产形态,或者在代码中使用 pathlib 统一处理路径,尽量避免写死分隔符。5.4 pytest 没有收集到任何用例pytest 跑完显示 no tests ran,很多人第一反应是代码出问题了,其实大概率是命名规则和目录配置不匹配。pytest 默认收集规则是:文件以 test_ 开头且以 .py 结尾,类名以 Test 开头,函数名以 test_ 开头。如果你把用例函数命名为 check_login,pytest 是看不到它的。处理办法有两种:要么改命名,要么在 pytest.ini 里显式配置 python_functions = test_、check_。还有一种情况:你的用例文件放在某个子目录里,但 testpaths 配置没有包含它。pytest 会只从 testpaths 指定的路径去找用例,你放外面当然收集不到。排查时先看 pytest 运行的目录和配置的 testpaths,再用 pytest --collect-only 查看它到底找到了什么。最后一种常见情况是 conftest.py 里 import 报错。pytest 收集用例时会先加载 conftest,如果这个文件里 import 了某个不存在的模块,整个收集过程直接失败,但报错信息可能比较隐晦。这时候先在命令行手动执行 python -c "import conftest" 看看是否报错,能快速定位是不是 conftest 本身的问题。我用一个表格来总结这些高频问题和对应的排查路线:现象最可能原因快速排查与解决依赖安装极慢或超时pip 源不稳定配置国内镜像源或公司内网源装完依赖运行报模块版本错误间接依赖冲突pip check 查看冲突,锁定底层库版本浏览器打不开或报 session not created驱动版本不匹配查看浏览器版本,用 webdriver-manager 自动匹配代码在本机能跑,别人机器不行环境不可复现重建虚拟环境,按 requirements.txt 安装pytest 显示 no tests ran文件命名或收集规则不匹配检查 pytest.ini 的 testpaths 和命名规则浏览器进程越来越多,内存吃满测试异常后未清理用 fixture teardown 统一清理,及时杀残留进程6. 收尾经验:环境维护与团队协作6.1 环境文档化环境搭好不算完,把搭建过程写成文档才是对团队真正的贡献。我见过太多“人走环境散”的项目:搭建环境的同学离职之后,新同学对着一个跑不起来的项目束手无策。环境文档不需要写论文,但必须包含三个内容:第一步,Python 版本从哪下载、装哪个版本;第二步,依赖清单怎么生成、怎么安装;第三步,项目目录结构和配置项含义。最好再附一个从零开始执行的命令清单,别人照着敲就能跑通。维护这份文档时要注意,文档内容和实际环境要保持同步,每次依赖发生变化,就顺手改文档。我个人的习惯是把这个文档放在团队的代码仓库里,名字就叫 setup.md,和 requirements.txt 放在同一层级。遇到新同事入职,直接甩他两条命令:一条是文档地址,一条是一键环境脚本。6.2 几条吃了大亏才换来的铁律最后整理几条我在这几年自动化测试环境搭建里,用真金白银换来的经验。说是铁律,其实就是每天被现实教育之后的妥协。第一,永远不要在系统全局环境里装测试依赖。全局环境里既有项目依赖又有系统工具依赖,装多了以后,你不敢动任何东西,因为一动就可能把之前能跑的环境搞挂。虚拟环境成本极低,永远是第一选择。第二,环境升级必须走流程。不管你是把 Python 从 3.10 升到 3.11,还是把一个依赖包从旧版本升到新版本,先在一个独立分支里验证全部用例通过,再合并主分支。直接在生产环境上升级依赖,等于拿全团队的效率赌你的一次手速,赌输的概率远比你想象的大。第三,不要迷信别人给的“大而全”安装命令。网上很多教程让你一次 pip install 十几个包,实际上里面一半你可能用不上,而且会引入不必要的依赖冲突。依赖这种东西,按需安装、按需导出,保持最小化。第四,环境问题和代码问题要分开排查。很多时候脚本报错了,我们第一反应是环境没弄好,然后重装一遍环境,浪费时间又没有效果。我的排查习惯是先看报错堆栈:如果报错发生在导入阶段,多半是环境问题;如果报错发生在运行某个方法的时候,基本能断定是代码逻辑问题,别一上来就动环境。根据我自己的体会,自动化测试环境搭建这个过程,本质上是在为你后续所有的用例执行打地基,地基打不好,上层写多少用例都容易塌。这套“分层理解、虚拟隔离、依赖锁定、驱动自动管理、配置外部化”的搭法,是我在很多项目里反复验证过的最稳路径。环境问题看似琐碎,但把它理顺了,切换到任何新项目都能快速上手,而这也恰恰是自动化测试领域最容易被低估的一项基本功。
自动化测试做了这么多年,我最强烈的体感就是:十个新手有九个把精力花在学框架、写脚本上,结果往往第一步就被环境卡住——今天装个依赖把系统搞乱,明天换台电脑跑不通,后天浏览器一升级脚本就全面崩盘。说句大实话,环境搭建这种事,看起来只是装几个软件,实际它决定了你后续所有脚本能不能稳定复现,是整条自动化测试链路的基座。这篇我就以 Python 为技术栈,把自动化测试环境从零到一怎么搭、为什么这么搭、中间有哪些坑要绕,完完整整过一遍。适合刚入门的测试工程师,也适合打算在团队里统一环境的同学参考,按着做基本能少走大半年的弯路。1. 动手前先想清楚:环境里到底要装什么1.1 把环境拆成四个层次很多教程一上来就让你 pip install 一堆东西,装完也不知道什么是什么。我的建议是,先把自动化测试环境拆成四个层次来看,每一层解决一类问题。第一层是 Python 本身运行环境。自动化脚本本质上是 Python 程序,需要稳定的解释器、正确的版本,以及和它配套的包管理工具。这一层出错,后面所有东西都跟着跑偏。第二层是依赖库层。做 Web 自动化要 selenium、playwright,做接口测试要 requests、pytest,生成报告要 allure-pytest,这些第三方库之间还有版本依赖关系,比如某个版本的 selenium 可能要求 urllib3 不能低于某个版本。这一层最容易出现“装了一堆包,最后互相打架”的状况。第三层是浏览器驱动层。Web UI 自动化脚本要驱动真实浏览器,而浏览器和驱动之间是严格版本匹配的关系。Chrome 一升级,对应版本的 ChromeDriver 没跟上,脚本立刻报废。我在实际项目里见到太多人因为驱动问题排查了好几天。第四层是项目级配置。测试代码要跑起来通常还需要各类环境配置,比如被测系统的地址、账号数据、超时时间,以及 pytest 收集用例的目录规则、报告插件的参数。这一层管的是“脚本在什么条件下执行”。所以在动手装环境之前,脑子里先有这个分层模型。后面每一步操作,你都能知道自己在解决哪一层的事情。1.2 技术选型背后最容易被忽略的原则具体到用什么工具,网上争论特别多。但我建议新手遵循几个原则,比盲目追新重要得多。第一个原则是跟随团队主流技术栈,不要自己另起炉灶。如果团队之前用 Java 那套自动化框架,你要引入 Python 就得想清楚:是并行,还是替换,还是只做新项目的试点?技术选型是一个组织行为,不只是个人喜好。我见过不少同学自己搭了一套很漂亮的环境,结果团队没人会用,最后被搁置。第二个原则是优先选择社区活跃、文档齐全、问题能搜到答案的工具。Python 自动化测试领域,pytest 的地位没什么争议,selenium 虽然近年受到轻量级框架的冲击,但它依然是 Web UI 自动化绕不开的选项。requests 做 HTTP 接口测试也是事实标准。冷门工具往往在搭建环境时就让你翻遍全网找解决方案,性价比太低。第三个原则是环境必须可复现。什么叫可复现?就是你今天花三天搭好的环境,明天换一台新机器,用一个 requirements 文件加几条命令就能一模一样的恢复出来。凡是做不到这一点的环境方案,都会在团队协作和持续集成阶段成为灾难。想清楚这三条原则之后,再进入具体实操,思路会清晰得多。2. 基础运行环境:Python、虚拟环境与依赖管理2.1 Python 版本怎么选Python 版本选择是个看似简单、实际上影响深远的问题。你写代码的时候用的是 3.11 的新语法,结果 CI 机器上还是 3.8,脚本跑起来直接语法报错,这种问题我处理过太多次。我个人的建议是,新项目直接选 3.10 或者 3.11。这两个版本足够稳定,主流自动化测试库的兼容性也都已经跟上。3.12、3.13 虽然更新,但对一些老牌依赖库的支持还存在窗口期,如果不是要尝鲜,没必要冒险。同时,明确一个团队统一的版本,比如大家都用 3.11.9,然后把这个版本写进项目文档和 CI 配置里。还有一个细节,Windows 环境下安装 Python 的时候,记得勾选“Add Python to PATH”。很多人装完 Python 在命令行输 python 提示找不到命令,十有八九是这一步漏掉了。如果你电脑里同时有多个 Python 版本,建议用 python3.11 这种带版本号的命令来区分,不要裸用 python,否则你自己都分不清当前用的是哪个版本。在 macOS 上,我建议优先用官方安装包或者 homebrew 安装,尽量避免系统自带的 Python,因为系统版本通常偏低,而且和系统工具链有耦合。Linux 服务器上则要注意,很多发行版默认自带的 python 是 Python 2 或者版本较低,需要单独安装。2.2 虚拟环境:隔离比想象中更重要虚拟环境这个知识点,我在面试时几乎必问,但真正用得好的人不多。为什么要虚拟环境?举个例子,你做项目 A 的时候用到了 django 5.0,后来做项目 B 需要 django 4.2,如果你不用虚拟环境,直接在全局环境里来回切换版本,很快会发现系统 Python 环境变成了“垃圾场”,装了什么、动过什么都没法追踪。虚拟环境的核心价值就是隔离——每个项目有自己的依赖目录,互不污染。Python 自带的 venv 模块是官方推荐方案,从 Python 3.3 开始就内置了,新项目我基本都用它。用法很简单:# 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Linux / macOS source venv/bin/activate # Windows PowerShell venv\Scripts\Activate.ps1 # Windows CMD venv\Scripts\activate.bat激活之后,命令行提示符前面会多出一个 (venv) 前缀,这时候你执行的 pip install、python 命令都会作用在这个虚拟环境里。conda 也可以做虚拟环境,但它更侧重数据科学场景,对纯自动化测试项目来说有点重。我在 Windows 和 Linux 下都用 venv,实测下来最省心,不需要额外安装,脚本化也方便。重要提示:虚拟环境目录(venv 文件夹)本身不要提交到代码仓库里,它是每台机器各自生成的。正确做法是提交 requirements.txt 这类依赖清单,新机器上用命令重建。2.3 依赖管理与版本锁定依赖管理直接决定你的环境能不能复现。很多人在本地跑通后,把代码丢给同事,同事跑不起来,打开报错一看:模块缺失、版本冲突。问题就出在依赖没有锁定。先看最基础的方案。激活虚拟环境后,安装你需要的库:pip install pytest selenium requests然后把当前环境里的所有依赖导出到文件:pip freeze > requirements.txtpip freeze 输出的每一行都是“包名==精确版本号”,比如 pytest==8.2.0。这样团队里其他人拿到 requirements.txt 之后,在自己的虚拟环境里执行:pip install -r requirements.txt就能装出几乎一模一样的环境。不过 pip freeze 有个缺点:它会列出所有间接依赖,文件冗长且不区分哪些是你直接需要的。所以我更推荐在实际项目里配合 pip-tools 这类工具来管理。简单做法是维护一个 requirements.in 文件,里面只写直接依赖,然后通过 pip-compile 生成锁定的 requirements.txt。这样升级依赖时,只需要改 requirements.in,重新编译即可,间接依赖的锁定由工具自动处理。版本锁定的价值到后期会越来越明显。有一次我把环境的 pytest 从 7.x 升到 8.x,结果某个用于生成测试报告的插件不兼容,全部环境直接失效。后来我老老实实把所有依赖锁定到精确版本,升级之前先在单独的分支里验证,再合并到主环境。这条经验换来的教训是:环境升级要像代码升级一样走流程,不能随手乱来。3. 核心实操:从零开始完整搭建一套可运行的环境3.1 三步创建项目环境拿一个实际场景来走一遍。假设现在要给某个 Web 管理后台做 UI 自动化测试,我要在空目录里把环境搭起来。第一步,创建项目目录和虚拟环境:mkdir ui_auto_test cd ui_auto_test python -m venv venv source venv/bin/activate第二步,安装核心依赖。这里我建议分两次装,先装基础测试框架,再装 Web 自动化相关组件,这样如果出错,容易定位是哪一部分的问题:pip install pytest pip install selenium webdriver-managerwebdriver-manager 这个库强烈推荐,它的作用是由脚本自动检测浏览器版本并下载匹配的驱动程序,解决了手动维护驱动的痛点。后面会细说。第三步,验证环境是否正常。在命令行输入:python -c "import pytest, selenium; print('env ok')" pytest --version如果输出正常,说明基础环境没问题。这一步看起来简单,但很多人跳过它,直接复制网上的教程代码,结果代码跑不起来,又回头怀疑环境有问题。到这里,一套最基础的 Python 自动化测试环境已经跑通了。接下来要把它变成一个能实际写用例、能出报告的项目骨架。3.2 接入 Web UI 自动化:浏览器驱动配置Web UI 自动化里,浏览器驱动是那种“平时感受不到它存在,一旦出问题就让你生不如死”的东西。selenium 通过 WebDriver 协议来操控浏览器,而 WebDriver 是一个独立于浏览器之外的驱动程序,它必须和你本地的浏览器版本严格匹配。比如你用 Chrome 126,那么 ChromeDriver 也必须是匹配 126 的版本。浏览器一旦自动升级到 127,旧驱动立刻失效。传统做法是自己去手动下载对应版本的驱动文件,再配置系统 PATH 或者在代码里指定路径。这做法简直是工程毒瘤——每次浏览器升级都要人肉维护一遍。现在我用的是 webdriver-manager,在代码中直接让程序自动管理驱动:from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service) driver.get("https://example.com") print(driver.title) driver.quit()首次运行时,webdriver-manager 会检测你本机 Chrome 版本,自动下载匹配的 ChromeDriver 并缓存起来,后续运行直接复用缓存。这样团队里其他人拿到代码,不需要手动配置任何路径,跑起来就能用。不过要注意,webdriver-manager 需要能访问驱动的下载站点,有些企业内网环境访问不了,或者下载超时。解决办法有两个:一是从别的机器下载好驱动文件,通过离线安装方式汇总到团队内网;二是如果是企业内部的镜像源,则配置好下载镜像地址。说白了,核心思路是把“网络不可控因素”前置,提前准备好离线包。如果你用的是 Edge 浏览器,也可以用对应的 EdgeChromiumDriverManager,代码写法几乎一样。在团队环境里,我通常建议统一浏览器版本,比如统一用 Windows 10 加固定版本的 Chrome,别让每个人机器上的浏览器版本千差万别,否则驱动版本管理会很痛苦。3.3 配置 pytest 与测试项目骨架环境搭好之后,要让 pytest 真正理解你的项目结构,需要做几件很小但很关键的事情。第一件事,建立目录结构。我习惯的项目结构大概是这样的:ui_auto_test/ ├── venv/ ├── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── test_login.py │ └── test_order_flow.py ├── pages/ │ ├── __init__.py │ ├── login_page.py │ └── order_page.py ├── config/ │ └── config.yaml ├── reports/ ├── requirements.txt └── pytest.initests 目录用来放测试用例,pages 目录放页面封装层,config 放配置数据,reports 放报告输出。这个分层不是随便来的——用例层、页面对象层、数据配置层分开之后,改一个页面结构只需要改 pages 里面对应的类,而不是去几百个用例里翻找定位元素。比如登录页封装成一个类:from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver class LoginPage: def __init__(self, driver: WebDriver): self.driver = driver def input_username(self, username: str): self.driver.find_element(By.ID, "username").send_keys(username) def input_password(self, password: str): self.driver.find_element(By.ID, "password").send_keys(password) def click_login(self): self.driver.find_element(By.ID, "login_btn").click()第二件事,写一个 pytest.ini 配置文件。它让 pytest 按照你定义的规则去收集用例:[pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = -v --tb=short --html=reports/report.html这个配置的意思是:从 tests 目录开始找用例,只找 test_ 开头或者 Test 开头的类和函数,运行时输出详细日志并生成 HTML 报告。addopts 里的 --html 参数需要先安装 pytest-html 插件,项目依赖里加一行即可。第三件事,处理 fixture。conftest.py 是 pytest 的全局配置钩子,我把浏览器初始化和释放放在这里:import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service @pytest.fixture(scope="session") def driver(): service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service) driver.implicitly_wait(10) driver.maximize_window() yield driver driver.quit()使用 fixture 后,每个测试函数里直接声明参数 driver 就能拿到已经初始化的浏览器实例,不需要每个用例都自己 new 一个浏览器。测试结束,pytest 自动执行清理,浏览器进程不会残留。关于 scope 的设置,我建议用 session 级别,因为浏览器在会话里复用可以大幅减少反复启动的开销,代价是有状态,测试之间的数据需要自己注意清理。如果用例之间要强隔离,那就改成 function 级别,牺牲一点速度换稳定。到这一步,Web UI 自动化环境的搭建就算闭环了:能启动浏览器,能写用例,能出报告。4. 接口测试环境的快速搭建与数据管理4.1 requests 加 pytest 的组合很多团队的自动化测试入口其实是接口测试,相比 UI 测试,它更稳定、执行更快、维护成本更低。接口测试环境的搭建比 UI 简单不少,但有一个核心难点击穿:怎么优雅地处理测试数据。我推荐用 requests 加 pytest 的组合。安装依赖:pip install requests pytest然后写一个最简单的接口用例:import requests def test_get_user_info(): resp = requests.get("https://example.com/api/user/1", timeout=10) assert resp.status_code == 200 assert resp.json()["code"] == 0这里看起来简单,但实际工程中没那么容易。被测接口通常需要登录态,也就是你要先调用登录接口拿 token,然后后续所有请求都带上它。这时候我用一个 session 级别的 fixture 来管理:import pytest import requests @pytest.fixture(scope="session") def token(): resp = requests.post( "https://example.com/api/login", json={"username": "test_user", "password": "test_pass"}, timeout=10, ) return resp.json()["data"]["token"] @pytest.fixture(scope="session") def client(token): s = requests.Session() s.headers.update({"Authorization": f"Bearer {token}"}) return s def test_get_user_info(client): resp = client.get("https://example.com/api/user/1", timeout=10) assert resp.status_code == 200requests.Session 会自动维持 cookie,配合请求头统一附加 token,让每条用例不需要重复写鉴权逻辑。这里我想特意说明一下:接口测试用例的高可维护性来源于“鉴权、基础 URL、公共参数”这类的公共逻辑都收敛到了 fixture 和配置层,而不是散落在每个用例里。4.2 测试数据与配置文件分离接口测试和 UI 测试都会面对同一个问题:测试环境地址、账号密码、超时时间这类配置,随时可能变化,写死在代码里会让环境切换变成噩梦。比如你今天连测试环境,明天连预发环境,代码里写死 https://test.example.com,那就只能全局搜索替换,麻烦不说,还容易漏。我的做法是使用 yaml 或 ini 配置文件,结合 pytest 的 conftest 加载配置,然后通过 fixture 注入。安装依赖:pip install pyyamlconfig/config.yaml 内容:base_url: "https://test.example.com" timeout: 10 users: admin: username: "admin_test" password: "admin_passwd" normal: username: "user_test" password: "user_passwd"conftest.py 里加载配置:import pytest import yaml @pytest.fixture(scope="session") def config(): with open("config/config.yaml", "r", encoding="utf-8") as f: return yaml.safe_load(f) @pytest.fixture(scope="session") def client(config): import requests s = requests.Session() s.base_url = config["base_url"] return s这样换环境时,只需要改配置文件,一行代码都不用动。我在团队里推广这个模式之后,测试人员切换环境的时间从半小时缩短到三分钟。环境变量的引入是更进一步的方案。比如敏感数据放外部的 .env 文件,代码里通过 os.environ 读取,这样配置文件本身也能提交到代码仓库而不会泄密。这里建议用 python-dotenv 来管理,项目依赖里加 python-dotenv,conftest 顶部加载一下即可。5. 高频故障排查手册5.1 依赖装不上、装错版本依赖相关的问题占据了自动化测试环境排障的半壁江山。典型场景一:pip install 极慢或者直接卡死。大部分情况下,把 pip 默认源切换到国内常用的 PyPI 镜像就能解决。全局配置:pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple如果公司内网有自己的 pip 源,优先配置内网源,速度一般比公共镜像还快。注意一点,不要因为这个原因就在代码里随便写死某个源地址,最好把源配置放在本机的 pip 配置文件中,让 requirements.txt 保持干净。典型场景二:根据 requirements.txt 安装时报依赖冲突。解决思路是先检查报错中提到的包冲突双方,打开它们的依赖声明看有没有明确版本上限,比如某些库只支持 urllib3<2.0。遇到这种问题不要硬调版本,要往上层找:是不是某个包版本太新,导致引用了不兼容的底层库。最直接的解法是暂时固定那个底层库到你已知能正常工作的大版本区间。典型场景三:同一个包,本地跑得好好的,换台机器就装不上。这种基本都是系统层面的差异,比如某个依赖有编译安装环节,Windows 上缺 MSVC 编译器,Linux 上缺 build-essential。遇到这种包,优先找有没有预编译的 wheel 包,不要试图在每台机器上编译。5.2 浏览器驱动起不来驱动相关的故障,典型特征就是报错信息里带着 session not created 或者 Unable to find chromedriver。第一步要排查的是驱动和浏览器是否匹配。新版 Chrome 可以在地址栏输入 chrome://version 查完整版本号,然后用这个版本号去对驱动的版本支持范围。用了 webdriver-manager 也不能完全高枕无忧——有少数情况是它自动匹配失败,这时你可以手动指定驱动版本。另一个隐藏坑是路径问题。旧代码里如果用了 exe 路径写死的方式,比如 webdriver.Chrome(executable_path="d:/driver/chromedriver.exe"),换台电脑没有这个路径,脚本必挂。所以不管本地怎么方便,最终提交的代码一定要优先考虑驱动自动管理,或者至少用相对路径加 PATH 配置。还有一个低级但常见的坑:浏览器进程残留。测试异常退出时,浏览器进程没被关干净,导致下一次启动时浏览器实例数超限,在 Linux 服务器上特别常见。排查方法很简单:任务管理器或者 ps 命令看看有没有残留的 chrome 进程,有就先杀掉再跑。长期解法是代码里用 try/finally 或者 pytest 的 fixture teardown 确保退出时清理。5.3 换台机器跑不通这个问题最打击团队士气,也是很多环境搭建失败的最后一道坎。换机器跑不通,十有八九是环境没有做到可复现。具体表现为:依赖清单不完整,requirements.txt 丢了或者没有更新;虚拟环境没重建,直接拷贝了旧机器的 venv 目录,结果因为路径、Python 版本不匹配导致各种诡异报错。正确的做法是:新机器上连虚拟环境都不需要拷贝,直接按流程重建。先确认 Python 版本一致,再创建虚拟环境,再安装 requirements.txt。我建议团队里把这几条命令写进一个 setup.sh 或者 setup.bat 脚本,新同学克隆代码后执行一键脚本就能把环境拉起来。这比任何口头指导都可靠。如果你在 Windows 上搭好了环境,而 CI 服务器是 Linux,那么你测出来的路径分隔符、文件编码、字符大小写问题都会冒出来。最好从一开始就让环境贴近生产形态,或者在代码中使用 pathlib 统一处理路径,尽量避免写死分隔符。5.4 pytest 没有收集到任何用例pytest 跑完显示 no tests ran,很多人第一反应是代码出问题了,其实大概率是命名规则和目录配置不匹配。pytest 默认收集规则是:文件以 test_ 开头且以 .py 结尾,类名以 Test 开头,函数名以 test_ 开头。如果你把用例函数命名为 check_login,pytest 是看不到它的。处理办法有两种:要么改命名,要么在 pytest.ini 里显式配置 python_functions = test_、check_。还有一种情况:你的用例文件放在某个子目录里,但 testpaths 配置没有包含它。pytest 会只从 testpaths 指定的路径去找用例,你放外面当然收集不到。排查时先看 pytest 运行的目录和配置的 testpaths,再用 pytest --collect-only 查看它到底找到了什么。最后一种常见情况是 conftest.py 里 import 报错。pytest 收集用例时会先加载 conftest,如果这个文件里 import 了某个不存在的模块,整个收集过程直接失败,但报错信息可能比较隐晦。这时候先在命令行手动执行 python -c "import conftest" 看看是否报错,能快速定位是不是 conftest 本身的问题。我用一个表格来总结这些高频问题和对应的排查路线:现象最可能原因快速排查与解决依赖安装极慢或超时pip 源不稳定配置国内镜像源或公司内网源装完依赖运行报模块版本错误间接依赖冲突pip check 查看冲突,锁定底层库版本浏览器打不开或报 session not created驱动版本不匹配查看浏览器版本,用 webdriver-manager 自动匹配代码在本机能跑,别人机器不行环境不可复现重建虚拟环境,按 requirements.txt 安装pytest 显示 no tests ran文件命名或收集规则不匹配检查 pytest.ini 的 testpaths 和命名规则浏览器进程越来越多,内存吃满测试异常后未清理用 fixture teardown 统一清理,及时杀残留进程6. 收尾经验:环境维护与团队协作6.1 环境文档化环境搭好不算完,把搭建过程写成文档才是对团队真正的贡献。我见过太多“人走环境散”的项目:搭建环境的同学离职之后,新同学对着一个跑不起来的项目束手无策。环境文档不需要写论文,但必须包含三个内容:第一步,Python 版本从哪下载、装哪个版本;第二步,依赖清单怎么生成、怎么安装;第三步,项目目录结构和配置项含义。最好再附一个从零开始执行的命令清单,别人照着敲就能跑通。维护这份文档时要注意,文档内容和实际环境要保持同步,每次依赖发生变化,就顺手改文档。我个人的习惯是把这个文档放在团队的代码仓库里,名字就叫 setup.md,和 requirements.txt 放在同一层级。遇到新同事入职,直接甩他两条命令:一条是文档地址,一条是一键环境脚本。6.2 几条吃了大亏才换来的铁律最后整理几条我在这几年自动化测试环境搭建里,用真金白银换来的经验。说是铁律,其实就是每天被现实教育之后的妥协。第一,永远不要在系统全局环境里装测试依赖。全局环境里既有项目依赖又有系统工具依赖,装多了以后,你不敢动任何东西,因为一动就可能把之前能跑的环境搞挂。虚拟环境成本极低,永远是第一选择。第二,环境升级必须走流程。不管你是把 Python 从 3.10 升到 3.11,还是把一个依赖包从旧版本升到新版本,先在一个独立分支里验证全部用例通过,再合并主分支。直接在生产环境上升级依赖,等于拿全团队的效率赌你的一次手速,赌输的概率远比你想象的大。第三,不要迷信别人给的“大而全”安装命令。网上很多教程让你一次 pip install 十几个包,实际上里面一半你可能用不上,而且会引入不必要的依赖冲突。依赖这种东西,按需安装、按需导出,保持最小化。第四,环境问题和代码问题要分开排查。很多时候脚本报错了,我们第一反应是环境没弄好,然后重装一遍环境,浪费时间又没有效果。我的排查习惯是先看报错堆栈:如果报错发生在导入阶段,多半是环境问题;如果报错发生在运行某个方法的时候,基本能断定是代码逻辑问题,别一上来就动环境。根据我自己的体会,自动化测试环境搭建这个过程,本质上是在为你后续所有的用例执行打地基,地基打不好,上层写多少用例都容易塌。这套“分层理解、虚拟隔离、依赖锁定、驱动自动管理、配置外部化”的搭法,是我在很多项目里反复验证过的最稳路径。环境问题看似琐碎,但把它理顺了,切换到任何新项目都能快速上手,而这也恰恰是自动化测试领域最容易被低估的一项基本功。