分层测试本质是逻辑分层而非目录结构,需实现职责隔离:页面对象层按业务动作封装、用例层剥离环境与断言细节、fixture仅负责构造销毁且避免耦合。

分层测试不是目录结构,而是职责隔离
很多人把“分层”理解成建几个文件夹,比如 test/、pages/、config/。这顶多算物理分层,真正起作用的是逻辑分层:每层只做一件事,且上层不感知下层实现细节。比如测试用例层写 login_page.input_username("admin"),而不是直接调用 driver.find_element(By.ID, "username").send_keys("admin")——后者一旦页面改ID,所有用例全崩。
四层结构里最容易错的是页面对象层(PO)封装粒度
PO 层不是“把所有元素都封装成方法”,而是按业务动作抽象。常见错误包括:
- 把每个输入框、按钮都单独封装成一个方法(如
get_username_field()、get_submit_btn()),导致用例里堆满定位调用 - 在 PO 方法里混入断言或流程控制(如登录失败后自动重试),把页面逻辑和测试逻辑搅在一起
- 用例直接访问 PO 类的属性(如
login_page.username_input),绕过方法封装,失去统一维护入口
正确做法是:每个 PO 方法对应一个用户可感知的动作,比如 login_with(username, password),内部完成定位、输入、点击、等待跳转,并返回下一个页面对象(如 HomePage()),形成链式调用。
测试用例层必须剥离环境、数据、断言细节
一个可维护的用例函数应该像自然语言句子一样读得通,例如:
立即学习“Python免费学习笔记(深入)”;
def test_user_can_login_and_see_dashboard(login_page, home_page):
home = login_page.login_with("admin", "123456")
assert home.is_welcome_displayed()
这里没有硬编码 URL、没有 YAML 文件路径、没有 time.sleep(2)。所有外部依赖都通过 fixture 注入:login_page 是带环境配置的页面实例,home_page 是其返回值;断言方法 is_welcome_displayed() 封装了显式等待和元素检查逻辑。如果某天要切到 staging 环境,只需改 login_page fixture 的初始化参数,不用动任何用例。
fixture 是分层粘合剂,但滥用会导致耦合
很多人用 fixture 把整个 PO 实例塞进去,结果 fixture 本身变成“上帝对象”,一改全崩。关键原则是:
- fixture 只负责构造和销毁,不处理业务逻辑(比如不调用
.click_login()) - 层级间传递靠返回值,不是靠共享状态(避免用
sessionscope 存 driver 或 page 对象) - 环境配置类(如
Config)应独立于 PO,由 fixture 组合注入,而非让 PO 自己去读settings.ini
例如,@pytest.fixture(scope="function") 返回一个初始化好的 LoginPage,它内部接收一个 driver 和一个 config 实例;而 config 本身又是另一个 fixture,从 settings.ini 或命令行参数加载。这样改环境只需动 config 层,不影响 PO 和用例。


















