
1. 为什么接口自动化测试工程也要用PO设计模式接口自动化测试做到一定规模后很多人会明显感觉到一个痛点用例代码越来越长、越来越像改一个接口地址或者字段名得跑遍十几个文件去改。我最早搭接口测试框架时也踩过这个坑一开始图省事直接在测试用例里写HTTP请求、拼参数、解析响应几十条用例的时候还挺顺手等项目功能多起来、用例过了几百条维护成本就直接爆炸了。这个问题的本质是测试用例同时承担了“业务逻辑”和“接口实现”两件事。用例既要懂业务比如下单要先登录、要先建订单又要懂技术细节URL是什么、Header怎么传、参数叫什么名字两层耦合在一起改动自然牵一发动全身。PO设计模式Page Object Model页面对象模型在UI自动化测试里已经被验证了十几年核心思想就一句话把页面元素和操作封装成对象测试用例只跟对象打交道不直接碰页面细节。把这个思想迁移到接口自动化测试工程里思路是完全一样的——把接口封装成对象把请求细节藏在对象内部测试用例只关心“我要调什么接口、传什么业务参数、期望什么结果”不再关心HTTP层面的事情。有人可能会说接口测试不是比UI测试简单吗有必要搞这么重吗我的观点是单接口调试确实没必要上PO但凡是工程化的接口自动化测试尤其是要做回归、要接CI、要多人协作的PO模式的收益会非常明显。它可以解决接口测试工程里三个最头疼的问题第一是复用问题。一个登录接口可能被几十条用例间接依赖把登录封装成对象后任何用例只要调一行login()就行不用重复写请求逻辑。第二是维护成本问题。接口地址、参数名、鉴权方式一旦变化只需要改接口对象一处用例层完全不用动。这一点在项目快速迭代期尤其救命。第三是可读性问题。纯用例层的代码读起来是业务语言“注册一个新用户-用该用户登录-创建订单-查询订单”而不是一坨request.setParam()和JSONPath解析新同事接手代码的理解成本会低非常多。说白了PO模式不是UI自动化的专利它是解决“实现细节”与“业务逻辑”耦合的通用设计思想。接口测试工程里把PO模式用好代码结构会清晰一大截。2. 把“页面对象”翻译成“接口对象”核心思路拆解2.1 UI测试的PO与接口测试的PO映射关系怎么对应很多教程讲PO模式都用UI自动化举例第一次尝试把它迁移到接口测试的人最容易卡在“找不到页面对象”上——接口测试哪来的页面和元素其实这里只需要做一个思维转换映射关系如下UI自动化中的PO概念接口自动化中的对应概念页面对象Page接口对象Api按业务模块划分页面元素定位By.id、FindBy接口定义URL、请求方法、请求头、参数名页面操作方法点击、输入接口请求方法封装好HTTP调用的函数页面跳转page.navigate()接口依赖调用先登录再下单元素等待、显式等待响应断言、超时重试说白了UI层的“页面”对应接口层的“业务模块”UI层的“元素操作”对应接口层的“请求发送”UI层的“页面流转”对应接口层的“接口调用链”。举个例子UI测试里你会在LoginPage里封装inputUsername()、inputPassword()、clickLogin()接口测试里你就在LoginApi里封装login(username, password)内部完成URL拼接、参数组装、请求发送、响应返回。用例层不会关心LoginApi内部用了什么HTTP客户端、参数是form格式还是JSON格式就像用例层不会关心LoginPage里的input标签长什么样。这个映射想清楚以后整个工程的分层就顺理成章了。2.2 PO模式在接口测试中的三层经典分层按照PO模式的思想接口自动化测试工程一般会拆成三层每一层的职责边界要划清楚第一层基础封装层Base层。这一层跟具体业务无关提供所有接口对象都要用的基础能力比如HTTP客户端初始化、公共请求头拼接、GET/POST/PUT/DELETE的通用方法、统一日志打印、统一响应解析。这一层可以理解为接口测试里最底层的“基础设施”。第二层接口对象层PO层。按业务模块拆分的接口对象比如UserApi、OrderApi、PayApi每个对象负责自己模块下所有接口的封装。对象内部的方法对应具体的接口操作方法入参是业务参数方法返回值是解析后的结果对象。这一层是PO模式的核心也是整个工程维护成本最集中的地方。第三层测试用例层TestCase层。这层只写业务场景把接口对象按业务逻辑串起来配合断言验证结果。用例层不出现HTTP相关的代码不出现URL、Header、请求参数细节。我见过一些团队把PO模式搞歪了在用例层里又套了一层“业务关键字层”搞到最后用例层和PO层职责重叠反而更乱。接口测试的PO模式没那么玄乎三层足够关键是把每层的边界守住不要越层。三层分完之后代码里的依赖关系是单向的用例层依赖接口对象层接口对象层依赖基础封装层。这个依赖方向一定不能反一旦出现用例层直接操作HTTP客户端或者PO层直接写业务断言分层就名存实亡了。3. 接口对象层怎么设计才能既灵活又好维护3.1 从零搭建一个基础的接口测试工程骨架先聊工程骨架。无论用Java还是PythonPO模式的工程落地都遵循类似结构api-test-project/ ├── src/ │ ├── main/java/ │ │ ├── base/ # 基础封装层 │ │ │ ├── BaseApi.java # 通用HTTP方法封装 │ │ │ ├── ApiClient.java # HTTP客户端封装 │ │ │ └── ApiConfig.java # 环境配置读取 │ │ ├── po/ # 接口对象层 │ │ │ ├── UserApi.java # 用户模块接口 │ │ │ ├── OrderApi.java # 订单模块接口 │ │ │ └── PayApi.java # 支付模块接口 │ │ ├── model/ # 数据模型 │ │ │ ├── User.java │ │ │ └── Order.java │ │ └── util/ # 工具类 │ │ ├── AssertUtil.java │ │ └── RandomUtil.java │ └── test/java/ │ ├── cases/ # 测试用例层 │ │ ├── UserTest.java │ │ └── OrderTest.java │ └── suite/ # 测试套件 │ └── TestNGSuite.xml └── resources/ ├── config/ │ ├── dev.properties # 开发环境配置 │ └── staging.properties # 预发环境配置 └── data/ ├── user_data.json # 测试数据 └── order_data.json这个骨架是Java TestNG OkHttp的技术栈其实换成Python Pytest requests也只是语法差异思想完全一样。基础封装层里最核心的就是BaseApi它提供所有PO对象共用的请求方法public class BaseApi { protected OkHttpClient client; protected String baseUrl; protected MapString, String commonHeaders; public BaseApi() { this.client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); this.baseUrl ApiConfig.getBaseUrl(); this.commonHeaders new HashMap(); this.commonHeaders.put(Content-Type, application/json); this.commonHeaders.put(Accept, application/json); } protected Response execute(Request request) { try { Response response client.newCall(request).execute(); return response; } catch (IOException e) { throw new RuntimeException(接口请求失败: request.url(), e); } } protected Response get(String path, MapString, String queryParams) { HttpUrl.Builder urlBuilder Objects.requireNonNull(HttpUrl.parse(baseUrl path)).newBuilder(); if (queryParams ! null) { queryParams.forEach(urlBuilder::addQueryParameter); } Request request new Request.Builder() .url(urlBuilder.build()) .headers(Headers.of(commonHeaders)) .get() .build(); return execute(request); } protected Response post(String path, Object requestBody) { String jsonBody JSON.toJSONString(requestBody); RequestBody body RequestBody.create(jsonBody, MediaType.parse(application/json)); Request request new Request.Builder() .url(baseUrl path) .headers(Headers.of(commonHeaders)) .post(body) .build(); return execute(request); } // PUT、DELETE等方法同理这里不一一展开 }为什么要在BaseApi里统一封装这些方法因为接口测试工程里90%的请求都是重复的套路拼URL、加Header、序列化参数、发请求、拿响应。如果每个PO对象都自己写一遍等于把重复代码复制了几十份后续要调整超时时间、加日志、加鉴权拦截器那就得全局搜索替换了非常痛苦。3.2 一个标准接口对象该怎么写接下来看接口对象层这是PO模式的核心。以用户模块为例常规写法是这样的public class UserApi extends BaseApi { /** * 用户登录 * * param username 用户名 * param password 密码 * return LoginResponse 包含token和用户信息 */ public LoginResponse login(String username, String password) { LoginRequest requestBody new LoginRequest(username, password); Response response post(/api/user/login, requestBody); String body getResponseBody(response); LoginResponse result JSON.parseObject(body, LoginResponse.class); if (!result.isSuccess()) { throw new BusinessException(登录失败: result.getMessage()); } return result; } /** * 用户注册 */ public RegisterResponse register(String username, String password, String email) { RegisterRequest requestBody new RegisterRequest(username, password, email); Response response post(/api/user/register, requestBody); return JSON.parseObject(getResponseBody(response), RegisterResponse.class); } /** * 获取用户信息 */ public UserInfo getUserInfo(String token) { MapString, String headers new HashMap(); headers.put(Authorization, Bearer token); Response response getWithHeaders(/api/user/info, null, headers); return JSON.parseObject(getResponseBody(response), UserInfo.class); } }这个对象里有几个点值得细说第一方法入参是业务参数不是HTTP参数。login(String username, String password)而不是login(MapString, String params)。为什么因为业务参数是有语义的调用方一眼就知道要传什么而Map传参等于把参数结构的控制权交给了调用方PO封装的意义就打了折扣。第二方法内部完成请求发送和响应解析返回的是结果对象。调用方拿到的就是一个干净的Java对象不需要关心JSON是怎么解析的也不需要手动去body里取字段。这就像UI测试里LoginPage的clickLogin方法返回一个HomePage对象而不是返回一个布尔值——这样调用方可以直接用返回值继续做下一步操作。第三PO方法内部可以处理简单的业务异常。比如登录失败直接抛业务异常而不是等用例层去解析响应码。这样做的好处是多个用例调用同一个登录方法时只要登录失败统一的异常逻辑都会生效。我在实际工程里还见过一种写法PO方法直接返回Response原始对象让用例层自己解析。这种写法不是不行但PO模式的“封装”价值就少了一大半一旦响应结构变化所有用例层解析代码都得跟着改。除非团队里有非常强的一致性约束否则我不推荐这个做法。3.3 参数对象和返回对象的封装决定了PO层好不好用PO模式里最容易被忽略、但实际最影响体验的就是参数对象和返回对象统称数据模型。很多团队一开始图省事PO方法的参数用Map返回值用JSONObject写几条用例没问题用例一多就开始乱了调用方不知道这个接口到底要哪些字段、哪些字段必填、返回结果里有哪些字段。用强类型的数据模型可以解决这些问题。每个接口的请求参数和响应结果都定义一个专门的类// 登录请求参数 public class LoginRequest { private String username; private String password; public LoginRequest(String username, String password) { this.username username; this.password password; } // getter/setter 省略 } // 登录响应结果 public class LoginResponse { private String token; private UserInfo userInfo; private Integer code; private String message; public boolean isSuccess() { return code ! null code 200; } // getter/setter 省略 }这样做有四个好处编译期就能发现字段名写错的问题不用等运行时IDE自动补全写用例的时候效率高很多字段类型明确不会再出现String转Integer报错的事情响应的业务状态可以封装成方法比如isSuccess()用例层读起来更自然我一个做Python接口测试的朋友用dataclass实现了类似的效果思路是一样的——用类型约束替代字符串散装操作让代码更健壮。3.4 接口之间的依赖关系怎么在PO层优雅处理接口测试里最麻烦的场景是接口依赖B接口需要A接口的返回值作为入参比如创建订单需要先拿到用户token查询订单需要订单ID。很多初学者会把这种依赖关系写死在用例里// 不好的写法用例里手动管理token传递 String token userApi.login(test, 123456).getToken(); OrderApi orderApi new OrderApi(token); orderApi.createOrder(...);这个写法有个问题每个用例都得先登录、拿token、再创建OrderApi对象光这一行前置代码就重复了无数次。更别提token还有可能过期需要刷新。PO模式里更优雅的做法是把这种依赖关系封装进接口对象内部。比如OrderApi的构造函数接收一个token创建订单时自动带上鉴权头或者更进一步把登录逻辑封装成一个统一的Session管理类public class OrderApi extends BaseApi { private String token; public OrderApi(String token) { this.token token; } public CreateOrderResponse createOrder(CreateOrderRequest request) { MapString, String headers new HashMap(); headers.put(Authorization, Bearer token); Response response postWithHeaders(/api/order/create, request, headers); return JSON.parseObject(getResponseBody(response), CreateOrderResponse.class); } }然后用例层可以通过一个工厂方法获取已认证的接口对象public class ApiFactory { public static OrderApi getOrderApi() { String token SessionManager.getToken(); return new OrderApi(token); } }SessionManager内部维护了登录态比如token过期就自动重新登录。这样用例层写起来就非常干净想测订单接口直接ApiFactory.getOrderApi()就能用了完全不用关心token怎么来的。这个设计的核心思路是把“接口之间的依赖”从用例层下沉到PO层让用例层只描述业务不处理技术链条。4. 用例层怎么写才能最大化PO模式的价值4.1 用户中心接口的完整用例示例接口对象层封装好之后用例层会变得非常清爽。以用户中心为例几个典型用例可以这么组织public class UserTest extends BaseTest { private UserApi userApi; BeforeMethod public void setUp() { userApi new UserApi(); } Test(description 正确的用户名密码可以登录成功) public void testLoginSuccess() { LoginResponse response userApi.login(testuser, 123456); AssertUtil.assertSuccess(response.isSuccess(), 登录应该成功); AssertUtil.assertNotNull(response.getToken(), 登录成功应返回token); } Test(description 错误的密码登录失败) public void testLoginWrongPassword() { try { userApi.login(testuser, wrongpassword); AssertUtil.fail(使用错误密码登录应该抛出异常); } catch (BusinessException e) { // 预期的业务异常测试通过 } } Test(description 登录成功后可以获取到用户信息) public void testGetUserInfoAfterLogin() { LoginResponse loginResponse userApi.login(testuser, 123456); UserInfo userInfo userApi.getUserInfo(loginResponse.getToken()); AssertUtil.assertNotNull(userInfo.getUserId(), 用户信息应包含userId); AssertUtil.assertEqual(testuser, userInfo.getUsername(), 用户名应一致); } }注意看整个用例层里看不到任何URL、HTTP方法、JSON解析的代码全部都是“业务操作结果断言”。这就是PO模式追求的终极效果——用例的可读性接近自然语言。新同事接手这个用例类哪怕完全不了解HTTP细节也能看懂在测什么。4.2 业务场景的串联多接口组合用例怎么编排单接口用例只是基础PO模式更大的价值体现在多接口串联的业务场景用例上。比如“用户下单支付全流程”这样一条用例涉及注册、登录、创建订单、支付、查询订单五个接口。用PO模式串起来的代码是长这样的Test(description 用户下单支付全流程) public void testOrderPayFlow() { // 1. 生成一个随机用户 String username RandomUtil.randomUsername(); String password Test123456; userApi.register(username, password, username example.com); // 2. 登录拿到token String token userApi.login(username, password).getToken(); // 3. 创建订单 OrderApi orderApi ApiFactory.getOrderApi(token); CreateOrderResponse orderResponse orderApi.createOrder( new CreateOrderRequest(iPhone 15, 1, 7999.00)); // 4. 支付订单 PayApi payApi ApiFactory.getPayApi(token); payApi.pay(orderResponse.getOrderId(), PayMethod.ALIPAY); // 5. 查询订单确认状态 OrderInfo orderInfo orderApi.queryOrder(orderResponse.getOrderId()); AssertUtil.assertEqual(PAID, orderInfo.getStatus(), 支付后订单状态应为PAID); }这段用例完全看不出HTTP的痕迹读起来就跟业务操作手册一模一样。更重要的是如果哪天接口的URL变了、参数结构变了我只需要改对应的PO类这条用例一行都不用动。4.3 断言层的封装断言结果比断言过程更重要接口测试的断言和UI测试不太一样UI测试断言的往往是“这个元素是不是显示了”这种直观结果接口测试要断言的通常是“响应码、业务码、字段值、数据内容”。我建议把断言也做一层薄封装而不是在用例里散落一堆assertEquals。原因很简单接口测试的断言逻辑经常是复用的比如“创建资源成功后返回202并且带resourceId”“查询接口返回结构包含分页信息”这些断言逻辑可以在多个用例里重复出现。public class AssertUtil { public static void assertSuccess(boolean condition, String message) { Assert.assertTrue(condition, message); } public static void assertBusinessCode(int expected, int actual, String message) { Assert.assertEqual(expected, actual, 业务码不一致: message); } public static void assertNotNull(Object obj, String message) { Assert.assertNotNull(obj, message); } public static void assertEqual(Object expected, Object actual, String message) { Assert.assertEqual(expected, actual, message); } /** * 断言接口响应结果的指定字段列表都存在 */ public static void assertFieldsPresent(Object obj, String... fieldNames) { for (String field : fieldNames) { assertNotNull(ReflectionUtils.getFieldValue(obj, field), 字段 field 不应为空); } } }断言的封装要点是底层用你熟悉断言库TestNG的Assert、JUnit的Assert、AssertJ都行上层统一走自己的工具类。以后要切换断言库只需要改一个类不用全局替换。5. 工程治理数据、配置、日志一个都不能少5.1 测试数据管理数据与逻辑分离PO模式解决了代码结构问题但接口自动化测试工程还有一个同样重要的问题——测试数据管理。理想状态下测试数据和测试逻辑应该是分离的换一套数据就能跑不同场景。常见做法是把测试数据外置到文件或表格里。比如用户模块的测试数据放在user_data.json{ validUser: { username: testuser, password: 123456, email: testuserexample.com }, invalidUser: { username: testuser, password: wrongpassword }, randomUserTemplate: { username: auto_${random}, password: Test123456, email: auto_${random}example.com } }用例层通过数据加载工具读取这些数据public class TestDataLoader { public static T T load(String key, ClassT clazz) { // 从JSON配置文件中读取并反序列化 } } Test public void testLoginWithValidUser() { LoginRequest request TestDataLoader.load(validUser, LoginRequest.class); LoginResponse response userApi.login(request.getUsername(), request.getPassword()); AssertUtil.assertSuccess(response.isSuccess(), 有效用户登录应该成功); }数据外置的好处有两个一是测试数据和测试逻辑解耦修改测试数据不用动代码二是同一个用例可以配合不同数据集跑多轮做数据驱动测试。TestNG的DataProvider配合这个思路写数据驱动用例非常顺手。5.2 多环境切换PO层里的配置管理接口测试通常要跑多套环境开发环境、测试环境、预发环境。这些环境的域名不同有的环境可能还要走不同的鉴权方式。PO模式工程的配置管理一般集中在BaseApi层解决。常见的做法是加载一个环境配置文件通过环境变量或启动参数指定当前环境。比如dev.properties和staging.properties# dev.properties api.base.urlhttp://dev.example.com api.auth.modenone # staging.properties api.base.urlhttp://staging.example.com api.auth.modetoken然后在BaseApi的构造阶段统一读取public class ApiConfig { private static Properties properties; static { String env System.getProperty(env, dev); // 读取对应环境的properties文件 } public static String getBaseUrl() { return properties.getProperty(api.base.url); } public static String getAuthMode() { return properties.getProperty(api.auth.mode); } }跑测试的时候只需要-Denvstaging就能切换整套环境PO层和用例层完全不用感知环境差异。这个设计让我在平时回归和发布前验证时省了大量时间。5.3 公共请求头、日志与其他横切关注点PO模式工程还有一个细节值得注意公共请求头的注入。很多接口都需要带traceId、appVersion、platform等公共参数如果每个PO方法手动加那就太啰嗦了。正确的做法是在BaseApi里加一个拦截器或者统一处理逻辑public class ApiInterceptor implements Interceptor { Override public Response intercept(Chain chain) throws IOException { Request originalRequest chain.request(); Request requestWithHeaders originalRequest.newBuilder() .header(X-Trace-Id, UUID.randomUUID().toString()) .header(X-App-Version, 1.0.0) .header(X-Platform, api-test) .build(); return chain.proceed(requestWithHeaders); } }加上这个拦截器之后不用改PO和用例层任何代码所有请求自动带上公共头。这就是分层结构的红利横切关注点只需要在底层处理一次整个工程都能受益。日志也是同理。为了排查问题方便我在BaseApi的execute方法里加了统一的请求/响应日志protected Response execute(Request request) { long startTime System.currentTimeMillis(); try { Response response client.newCall(request).execute(); long duration System.currentTimeMillis() - startTime; LogUtil.logRequest(request); LogUtil.logResponse(response, duration); return response; } catch (IOException e) { throw new RuntimeException(接口请求失败: request.url(), e); } }这样每个请求的URL、参数、耗时、响应状态都会自动落日志排查问题的时候不用去猜用例执行到哪一步挂了直接看日志就行。6. 常见问题与排查技巧实录6.1 接口对象越写越臃肿怎么办PO模式落地一段时间后最容易出现的问题是接口对象类越写越大。一个UserApi可能积累了上百个方法几千行代码毕竟一个用户模块的接口可能有几十个每个接口还可能有正常、异常、边界等多个场景。我自己踩过这个坑之后总结出两个解法一是按照接口的子功能继续拆分对象比如UserAuthApi登录、注册、登出、UserProfileApi资料查询、修改、UserAdminApi后台管理类接口让每个PO类的职责更聚焦二是在PO类内部不要堆积用例逻辑一个方法只做一件事——接口调用基础解析不要把一个场景的多步操作也塞进PO方法那应该是用例层编排的事。另外如果发现PO方法里出现了大量重复的“解析响应、判断业务码、抛异常”代码可以考虑写一个统一的模板方法public abstract class BaseApi { protected T T executeRequest(Request request, ClassT responseType) { Response response execute(request); String body getResponseBody(response); T result JSON.parseObject(body, responseType); handleBusinessError(result); return result; } }6.2 用例失败后的排查链路接口测试用例失败时最怕的就是只知道“失败了”不知道为什失败。PO模式工程里排查链路由底层到顶层通常是这样的日志 → 请求参数是否正确 → PO封装逻辑是否出错 → 数据问题 → 业务代码缺陷。定位问题第一步永远是看统一日志确认请求URL、参数、响应码和响应体是否正常。如果日志显示请求本身是成功的比如HTTP 200那问题大概率出在PO层的解析或断言上如果请求本身失败比如404、500要继续往上看是不是URL配置错误或环境带宽问题。为了让排查效率更高我在工程里给每个PO方法都加了方法名级别的日志标记。比如登录方法的日志会输出[UserApi.login] request body... response...这样一条用例跑下来日志链路能够清晰还原整个调用过程定位问题快很多。6.3 常见问题速查表根据这几年的接口自动化测试工程实践经验我把几个典型问题整理成了表格方便遇到时快速对照问题现象可能原因排查思路所有用例突然大面积失败baseUrl配置错误、环境挂了、公共参数变了先看BaseApi日志确认请求是否到达预期环境单个模块的用例失败该模块接口定义变化检查对应PO对象的URL、参数名、返回值解析偶发性失败重跑又通过超时设置过短、接口响应抖动调整BaseApi超时时间考虑加重试机制登录成功但后续接口全部401token过期或鉴权头未正确传递检查SessionManager的token刷新逻辑PO层报JSON解析异常响应结构变了或者字段类型不匹配对比响应实际JSON与返回模型类的字段定义用例执行慢每用例都重新登录、没有连接复用用SessionManager缓存token、启用HTTP连接池6.4 PO模式落地过程中的一些“反模式”提醒最后提醒几个我亲眼见过的PO模式错误用法这些“反模式”比不用PO模式更坑反模式一PO方法返回原始Response。这等于把封装直接撕开了一个口子用不了几次用例层就会慢慢出现解析代码PO模式名存实亡。反模式二在PO方法里隐藏关键参数。比如把一个接口的必填参数在PO方法里写死了默认值用例层无法覆盖不同入参场景。这种封装太“重”反而限制了用例的灵活性。反模式三用例层直接new PO对象并传一堆构造参数。每次调用都要先初始化一堆依赖明显是PO对象设计粒度不对应该把通用的参数比如token交给工厂或Session管理而不是让用例层手动组装。7. 最后聊点我的个人体会接口自动化测试工程应用PO设计模式本质上是把UI自动化测试沉淀了多年的优秀实践迁移到了接口测试领域目的就是让测试代码具备更好的可复用性、可维护性和可读性。我个人在实际应用中的体会是PO模式的价值不是一开始就显现的而是当工程规模超过100条用例、迭代超过3个月后优势才会越来越明显——这时候你会发现自己改用例的时间省下来了一大截排查问题也更有条理。最后再分享一个小技巧如果你刚开始给接口测试工程引入PO模式不要追求一步到位。先按模块拆出两三个核心PO对象跑通流程比如把最常用的登录和用户信息接口封装好跑一段时间感受一下分层的好处再逐步把其他接口收拢进PO层。一个几百个接口的历史工程不太可能一口气全改造完但你可以从最高频的接口开始让改造的ROI最大化。另外建议把PO类的方法命名和业务术语严格对齐比如register、login、createOrder、pay不要用doPost、sendRequest这种跟业务无关的名字——PO模式的价值就藏在“代码即业务文档”这件事里。