最近几年接到不少朋友和团队同行问同一个问题:自动化测试框架到底怎么落地?很多人不是不会写脚本,不是没用过Selenium或者Requests,而是卡在“从0到1怎么搭”“搭起来之后怎么让团队真用起来”这两步上。正好借这篇博客,把我这些年从零搭建、持续迭代自动化测试框架的完整思路和实操过程整理出来,一篇打通从选型、目录设计、核心封装到CI落地的所有环节。
这篇内容主要面向:刚接手团队自动化建设、准备从手工测试向自动化转型的测试开发工程师,以及已经写了不少脚本但觉得维护成本高、想重构框架的同学。里面没有纸上谈兵的理论,全是我自己踩过坑之后沉淀下来的可复制方案,Java技术栈为主,接口自动化和UI自动化两条线都会讲到。
1. 先想清楚:自动化测试框架到底在解决什么问题
聊落地之前,必须先回答一个问题:一个团队已经能用手工点点点完成测试,为什么还要花人力去搭自动化框架?这个问题想不清楚,框架搭到一半就会搁浅。
1.1 框架的本质不是“写脚本”,而是“降低维护成本”
很多初学者以为自动化测试框架就是写一堆脚本来代替人工操作。这个理解对了一半。真正成熟的框架,核心目标是让测试脚本的创建成本、执行成本、维护成本三者同时可控。
举个例子:你有一个登录功能,手工测试10分钟能测完主要场景。如果写一个登录的自动化脚本要花1小时,脚本跑一次要5分钟,而每改一次需求脚本就要跟着改30分钟,那这个自动化的ROI(投入产出比)就是负的。框架的意义就在于,通过合理的封装和分层,把“1小时写脚本”压缩到“10分钟配置一条用例”,把“每次改需求都要改脚本”变成“只改一个配置文件”。
这里有一个非常关键的理念:数据驱动。把测试数据从脚本代码中剥离出来,用外部文件(Excel、YAML、JSON)维护。业务逻辑变了,优先改数据;代码逻辑变了,才去动脚本。后面我会专门讲这个。
1.2 接口自动化和UI自动化,落地难度完全不同
选型之前要区分两个大方向。接口自动化测试框架(通常基于HTTP协议层做验证)和UI自动化测试框架(基于浏览器页面元素做验证)逻辑相似,但落地难度天差地别。
接口自动化的优点是对环境依赖小、执行速度快、稳定性高、维护成本相对低。它适合在CI流水线里频繁回归,适合作为质量保障的第一道防线。
UI自动化的优点是更贴近用户真实操作路径,能验证前后端联调后的完整功能。但缺点是执行慢、对环境依赖大(浏览器版本、分辨率、网络延迟都可能影响结果)、元素定位容易因为前端改版而失效。它不适合大规模频繁回归,更适合做冒烟测试、关键路径验证。
我的建议非常简单粗暴:能接口自动化覆盖的,就不要用UI自动化重复覆盖。UI自动化只用来覆盖那些接口层验证不了的端到端关键场景。框架设计上,两个方向可以共用一个基础底座(报告、日志、配置、数据驱动封装),但用例层和驱动层必须分开。
1.3 框架落地的验收标准不是“写了很多脚本”
还有一个常见的认知误区:团队成员提交的自动化用例数量越多,说明自动化建设越好。这个不对。真正有效的验收标准是:自动化测试有没有在持续发现回归缺陷、有没有真的节省人工回归时间、有没有被研发团队主动纳入提交流程。
如果一个框架搭了半年,脚本有几千条,但每天都在报一堆没人看的失败结果,或者执行一次要跑几个小时导致CI流程形同虚设,那这个框架本质上是失败的。框架落地成功的标志,是它成为研发流程中的一环,而不是测试团队自娱自乐的工具。带着这个标准去设计框架,你会发现很多决策会不一样——比如你会优先保证用例稳定性,而不是追求数量;你会重点做失败重跑和结果通知,而不是执着于炫酷的测试报告。
2. 框架选型:别盲目追新,适合团队的就是最好的
技术选型是框架落地过程中第一个分岔路。选错了,后面返工成本极高。我见过不少团队在选型阶段纠结了几个月,Java还是Python、TestNG还是JUnit5、Selenium还是Playwright、要不要上BDD(行为驱动开发)……讨论得热火朝天,代码一行没写。
2.1 语言和基础库:Java技术栈的成熟组合
如果你的团队以Java为主,测试开发工程师日常写代码主要用Java,那就没必要为了自动化测试单独引入Python技术栈。虽然Python写脚本确实快,但维护两套语言体系的学习成本和沟通成本,在中小团队里是实实在在的负担。
我推荐的基础组合是这套:
| 层级 | 推荐选型 | 说明 |
|---|---|---|
| 构建工具 | Maven | 生态成熟、依赖管理清晰,比Gradle更“标准” |
| 测试框架 | TestNG | 支持分组、依赖、并行执行,比JUnit4更灵活,比JUnit5更稳(社区资料多) |
| HTTP客户端 | RestAssured | 接口自动化首选,链式API写起来非常优雅 |
| UI驱动 | Selenium Java | 生态最全、遇到问题搜得到答案;备选Playwright(后文细说) |
| 断言库 | AssertJ | 流式断言,可读性强,失败信息友好 |
| 报告 | Allure2 | 测试报告颜值高、信息全,团队推广时接受度极高 |
| 数据驱动 | TestNG DataProvider + YAML | 灵活且易维护,比Excel更轻量 |
| 日志 | SLF4J + Logback | 标准组合,排查失败用例时必不可少 |
这套组合的最大优势是“稳”。每个组件都是经过市场验证的方案,网上资料多、踩坑记录全,团队里任何一个人遇到问题,搜一下基本都能找到答案。这里面最核心的是TestNG和RestAssured的组合——TestNG的分组和依赖能力让你可以灵活控制用例执行范围,RestAssured让接口请求和响应验证代码量压缩一半以上。
2.2 UI自动化:Selenium和Playwright怎么选
Selenium作为老牌UI自动化框架,胜在生态完整和兼容性强。无论什么浏览器、什么语言、什么CI平台,Selenium Grid都能覆盖。缺点是API相对底层,一些常见操作(比如等待元素出现)需要封装,写起来有一定样板代码。
Playwright是后起之秀,API设计更现代,有自动等待机制(不需要手动写显式等待),还有比较实用的trace viewer(追踪查看器)功能,定位元素失败时能直接看截图和DOM快照。它内置的浏览器安装机制也比Selenium的driver配置省心不少。
我的建议是:老团队已经基于Selenium沉淀了大量封装代码,没必要为了追新重写一遍;新团队从零起步,没有历史包袱,可以考虑Playwright,但前提是团队中至少有一两个人熟悉JS的调试方式(Playwright虽然支持Java,但文档和社区案例以JavaScript为主)。框架的核心是分层思想,工具是随时可以替换的。
2.3 接口自动化框架:RestAssured vs HttpClient vs Feign
接口自动化选型看起来选择很多,其实思路很简单。HttpClient太底层,需要自己封装URL拼装、请求头处理、JSON序列化反序列化,写起来代码量大、维护成本高。Feign是服务间调用的客户端,设计目标是微服务通信,在测试场景下反而显得笨重。RestAssured则是专门为接口测试设计的,提供了类似Given-When-Then的BDD风格API,内置了JsonPath和XmlPath,响应断言写起来非常顺手。
这是RestAssured和HttpClient的直观对比:
// 用HttpClient写一个GET请求+断言状态码 CloseableHttpClient client = HttpClients.createDefault(); HttpGet request = new HttpGet("http://api.example.com/users/1"); request.addHeader("Authorization", "Bearer " + token); CloseableHttpResponse response = client.execute(request); Assert.assertEquals(response.getStatusLine().getStatusCode(), 200); // 用RestAssured写同样的逻辑 given() .header("Authorization", "Bearer " + token) .when() .get("http://api.example.com/users/1") .then() .statusCode(200);后者简洁的阅读体验,恰好是团队协作时最在意的——别人看你的测试代码时,能一眼看懂你在测什么。这也是框架设计中一个常被忽略的原则:测试代码本身就是测试用例的可执行文档,可读性和可维护性比“炫技”重要得多。
3. 目录结构和分层设计:框架的地基工程
选型完成了,下一步是搭工程骨架。很多团队栽在这一步:目录结构乱七八糟,工具类散落在各个包,测试数据和脚本混在一起,配置文件和代码路径盘根错节。等脚本多了,想改个配置要全局搜索,想找一条用例要翻十几个目录,维护成本直接爆炸。
3.1 一套能长期维护的Maven工程目录
下面是我实践多年、调整过很多版之后相对稳定的标准目录结构,按照Maven标准布局展开:
auto-test-framework/ ├── pom.xml ├── src/main/java │ └── com/company/autotest │ ├── core/ # 框架核心源码,如HttpClient封装、DriverFactory │ │ ├── http/ # RestAssured封装层 │ │ ├── ui/ # WebDriver封装层 │ │ ├── config/ # 配置读取 │ │ ├── report/ # 报告扩展 │ │ └── utils/ # 通用工具 │ └── model/ # 实体对象 ├── src/test/java │ └── com/company/autotest │ ├── cases/ # 测试用例(按模块分包) │ ├── datas/ # DataProvider数据源 │ └── suite/ # 测试套件XML ├── src/test/resources │ ├── config/ # 环境配置 │ │ ├── env-dev.yaml │ │ ├── env-test.yaml │ │ └── env-prod.yaml │ ├── data/ # 测试数据文件 │ └── driver/ # WebDriver执行文件 ├── logs/ # 运行日志 └── output/ # 测试报告输出核心思路是:主代码只放框架逻辑(基础设施),测试代码只放用例和场景,资源文件只放配置和数据。三层职责分离,谁坏了改谁,互不干扰。
3.2 分层设计:每一步都有明确职责
框架内部我习惯分成四层,从下往上依次是:
- 基础层:工具类、配置读取、日志初始化、数据库连接等。这层不涉及任何业务,只提供通用能力。
- 服务层:对业务接口或页面操作的封装。比如UserApi这个类封装了查询用户、创建用户、删除用户等所有用户相关的接口调用;LoginPage封装了打开登录页、输入用户名、输入密码、点击登录等页面操作。
- 用例层:把服务层的封装组织成具体的测试场景,描述“我要测什么”。这层只关心业务规则,不关心HTTP细节或页面元素。
- 数据层:为用例层提供参数化输入,包括正常数据、边界数据、异常数据。
这个分层的核心好处是:如果接口的URL变了,你只需要改服务层;如果页面的登录按钮id变了,你只需要改LoginPage;如果要加一条新用例,你只需要在用例层新增一个方法。变更被限制在局部,不会像多米诺骨牌一样满盘皆输。
3.3 包名和命名规范:细节决定协作效率
框架一旦多人协作,命名不统一的代价会被放大。我自己在用的一套规范是:
- 测试类统一命名为
XxxTest,接口测试类加ApiTest后缀,UI测试类加PageTest后缀。比如UserApiTest、LoginPageTest。 - 用例方法名直接用中文场景描述(Java方法名支持中文):
testLoginSuccessWithCorrectPassword太长,登陆成功_账号密码正确反而一目了然。前提是团队达成一致,且CI环境无编码问题。 - 每个用例类顶部必须写
@Feature和@Story注解(Allure报告会读取),描述这个类对应哪个功能模块、哪个用户故事。报告生成后,产品经理也能看懂你在测什么。
命名规范看起来是小事,实际影响非常大。好的命名让报告变成团队都能读懂的文档,而不是只有写脚本的人自己知道的“黑盒”。
4. 核心代码实现:怎么把框架跑起来
目录结构和分层设计搞清楚了,接下来就是动手写核心代码。这一步是框架落地中最容易“半途而废”的环节,因为代码一多,写着写着就会陷入细节。我的建议是:先搭一个最小可运行的骨架(登录接口+一条用例跑通),再逐步丰富功能。
4.1 配置管理:环境切换要像换衣服一样简单
测试环境、预发环境、生产环境的地址和账号各不相同,配置文件必须和环境解耦。我的做法是:为每个环境准备一个独立的YAML文件,运行时通过系统参数指定激活哪个环境。
先定义一个配置加载工具类,核心逻辑是读环境变量env,默认走test环境:
public class ConfigManager { private static final Map<String, Object> CONFIG = new HashMap<>(); static { String env = System.getProperty("env", "test"); InputStream is = ConfigManager.class.getClassLoader() .getResourceAsStream("config/env-" + env + ".yaml"); CONFIG.putAll(YamlUtils.load(is)); } public static String get(String key) { return String.valueOf(CONFIG.get(key)); } public static String getBaseUrl() { return get("baseUrl"); } public static String getToken() { return get("token"); } }使用方式:
mvn test -Denv=dev # 开发环境 mvn test -Denv=test # 测试环境 mvn test -Denv=prod # 预发环境所有和业务相关的地址、账号、超时时间都写在环境文件里,代码里看不到任何硬编码的URL或密码。这样做的直接好处是:环境切换不用改代码、不用重新打包、不用提交新分支,一条命令解决。
4.2 API层封装:把HTTP请求封装成业务动作
接口自动化框架的核心封装,是把RestAssured的“请求-响应”过程收敛到业务语义层面。设计一个BaseApi类,封装统一的请求头、日志和报告记录:
public class BaseApi { protected static RequestSpecification given() { return RestAssured .given() .baseUri(ConfigManager.getBaseUrl()) .header("Authorization", "Bearer " + ConfigManager.getToken()) .header("Content-Type", "application/json") .log().ifValidationFails() .filters(new AllureRestAssured()); // 自动把请求信息写入Allure报告 } protected Response post(String path, Object body) { return given() .body(JsonUtils.toJson(body)) .when() .post(path) .then() .extract() .response(); } protected Response get(String path) { return given() .when() .get(path) .then() .extract() .response(); } }然后封装业务接口:
public class UserApi extends BaseApi { public static User createUser(String name, String email) { CreateUserRequest request = new CreateUserRequest(); request.setName(name); request.setEmail(email); return post("/api/users", request).as(User.class); } public static User getUser(Long userId) { return get("/api/users/" + userId).as(User.class); } public static int deleteUser(Long userId) { return delete("/api/users/" + userId).statusCode(); } }这样测试用例层写起来就非常干净:
@Test public void 创建用户后可以通过id查询到该用户() { User created = UserApi.createUser("张三", "zhangsan@example.com"); assertThat(created.getId()).isNotNull(); User queried = UserApi.getUser(created.getId()); assertThat(queried.getName()).isEqualTo("张三"); }这个封装的精髓在于:用例层不需要关心URL、鉴权、请求头、JSON序列化这些细节,只需要关心“创建用户”这个业务动作。哪怕后端接口路径整体加了版本前缀,你只需要改BaseApi一处,所有用例都跟着调整。
4.3 UI层封装:让页面操作不再脆弱
UI自动化的核心痛点是元素定位不稳。Selenium原生写法要求每个用例都写driver.findElement(By.id("xxx")).click(),一旦页面改版,定位器失效,用例批量报红。解决思路是引入Page Object(页面对象)模式。
Page Object的本质:把页面的元素定位和操作方法封装在独立的类中,测试用例不直接接触WebDriver。
public class LoginPage extends BasePage { // 定义元素定位器 private final By usernameInput = By.id("username"); private final By passwordInput = By.id("password"); private final By loginButton = By.id("login-btn"); private final By errorMsg = By.className("error-tip"); public void login(String username, String password) { waitForElementVisible(usernameInput).sendKeys(username); waitForElementVisible(passwordInput).sendKeys(password); clickWhenReady(loginButton); } public String getErrorMessage() { return waitForElementVisible(errorMsg).getText(); } public boolean isLoginSuccess() { return waitForUrlContains("/dashboard"); } }BasePage里封装了等待机制、点击保护和日志记录:
public class BasePage { protected WebDriver driver; protected WebDriverWait wait; public BasePage(WebDriver driver) { this.driver = driver; this.wait = new WebDriverWait(driver, Duration.ofSeconds(10)); } protected WebElement waitForElementVisible(By locator) { return wait.withMessage("等待元素可见超时: " + locator) .until(ExpectedConditions.visibilityOfElementLocated(locator)); } protected void clickWhenReady(By locator) { WebElement element = wait.withMessage("等待元素可点击超时: " + locator) .until(ExpectedConditions.elementToBeClickable(locator)); try { element.click(); } catch (ElementClickInterceptedException e) { // 元素被遮挡时,尝试用JS点击绕过 JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript("arguments[0].click();", element); } } }这里有两个非常实用的细节:一是所有定位元素的等待都有withMessage提示,一旦等待超时,报告里能直接看到是哪个元素出了问题,而不是泛泛的"NoSuchElementException";二是点击被拦截时自动降级用JS点击,能绕过常见的弹窗遮挡、固定定位元素遮挡问题。实测这两个小设计,能让UI用例的稳定性提升30%以上。
4.4 数据驱动:把测试数据从代码中解放出来
框架落地的核心难点不在技术实现,而在维护成本。数据驱动是控制维护成本最有效的手段。我用TestNG的@DataProvider配合YAML文件实现。
定义一个数据源注解和读取工具:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DataFile { String value(); String sheet() default ""; }然后写一个通用数据加载器:
public class DataProviderUtils { public static Object[][] loadYamlData(String filePath) { List<Map<String, Object>> dataList = YamlUtils.loadList(filePath); Object[][] result = new Object[dataList.size()][1]; for (int i = 0; i < dataList.size(); i++) { result[i][0] = dataList.get(i); } return result; } }测试用例这样写:
@Test(dataProvider = "loginData") public void 登录接口校验(Map<String, Object> data) { String username = String.valueOf(data.get("username")); String password = String.valueOf(data.get("password")); int expectedCode = (int) data.get("expectedCode"); Response response = AuthApi.login(username, password); assertThat(response.getStatusCode()).isEqualTo(expectedCode); }对应的YAML测试数据:
- username: "admin" password: "correct_password" expectedCode: 200 - username: "admin" password: "wrong_password" expectedCode: 401 - username: "" password: "correct_password" expectedCode: 400 - username: "admin" password: "" expectedCode: 400新增一条测试用例数据,只需要在YAML文件里加几行,不需要动任何Java代码。这就是数据驱动的核心价值——把“新增测试场景”变成“新增测试数据”,让非测试开发岗位的同学也能参与用例维护。
4.5 测试报告:所有人都能看懂的“成绩单”
框架跑起来之后,有一个环节会直接影响团队推广的成功率:测试报告。技术团队在乎失败日志,但领导层只在乎一个数字:通过率。Allure2是我用过最适合做这件事的报告框架。
接入方式很简单,三步:pom.xml引入依赖、写一个监听器类:
public class AllureListener implements ITestListener { @Override public void onTestFailure(ITestResult result) { // 接口测试:保存请求响应日志 // UI测试:截图并附到报告中 if (result.getInstance() instanceof BasePage) { WebDriver driver = ((BasePage) result.getInstance()).getDriver(); byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES); Allure.addAttachment("失败截图", "image/png", new ByteArrayInputStream(screenshot), ".png"); } // 保存异常堆栈 Allure.addAttachment("失败原因", "text/plain", result.getThrowable().getMessage()); } }配置监听器启用,执行后生成报告:
mvn clean test allure generate target/allure-results --clean -o target/allure-report allure open target/allure-report一套配置下来,团队每个人都能在浏览器里查看测试结果,包括:测试通过率趋势、每个功能模块的用例分布、失败用例的具体请求参数和响应内容(接口测试)、失败时页面的截图(UI测试)。这比任何口头宣导都有说服力——数据摆在那里,研发团队自然会主动关注失败用例。
5. CI集成:让自动化测试跑在每一次代码提交里
框架本地能跑通只是第一步,真正的落地是把它接入CI流水线,让自动化测试成为研发流程中自动运行的环节。没有CI集成的自动化框架,价值和手工执行脚本差别不大。
5.1 Jenkins流水线配置:经典但可靠
如果你的团队还在用Jenkins,配置思路如下。新建一个Pipeline任务,Jenkinsfile内容大致如下:
pipeline { agent any stages { stage('Checkout') { steps { checkout scm } } stage('Run AutoTest') { steps { sh 'mvn clean test -Denv=test' } post { always { allure includeProperties: true, jdk: 'default', report: 'target/allure-report', results: 'target/allure-results' } } } stage('Notification') { steps { // 发送结果通知到钉钉/邮件 sh 'bash scripts/notify.sh' } } } post { failure { // 构建失败时发送企业微信通知 emailext(recipients: 'qa@company.com', subject: "自动化测试失败: ${env.JOB_NAME}", body: "请查看报告: ${env.BUILD_URL}allure") } } }关键配置点有三个:环境变量通过-Denv传入,确保测试跑在指定环境;报告插件AboutAllure自动收集结果并发布,打开链接即可查看;失败时自动发企业微信/邮件通知,不需要人工盯构建结果。
5.2 更轻量的CI集成:Gitea Actions和GitLab CI
现在很多团队用Gitea(开源轻量Git服务)或GitLab,这时用平台自带的CI功能更轻量。
GitLab CI的.gitlab-ci.yml示例:
stages: - test auto-test: stage: test image: maven:3.8-jdk-11 script: - mvn clean test -Denv=test artifacts: paths: - target/allure-results expire_in: 7 days rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'配置要点:只在MR(合并请求)事件触发时执行——这样每一次代码评审都伴随着自动化回归,研发提交前就能发现接口被改坏了。artifacts保留测试结果,开发同学在MR页面上就能下载查看报告。
5.3 定时执行策略:回归任务放到深夜跑
除了MR触发执行外,还要配置一套定时任务。推荐策略是:
- 每个工作日凌晨2点跑全量回归(此时业务低峰,数据稳定),早上团队上班前报告已生成。
- 每次MR触发只跑受影响模块的用例(利用TestNG的分组功能,
-Dgroups=userApi,orderApi),把执行时间控制在5分钟以内。 - 核心冒烟用例在每次部署后自动执行,验证环境可用性。
分组执行是TestNG非常实用但常常被忽略的功能。在用例类上加@Test(groups = {"smoke", "userApi"}),然后通过命令行参数灵活选择要跑的组,不用修改任何代码就能控制测试范围。
6. 框架落地过程中的常见问题和避坑指南
最后分享一些我这些年遇到的高频问题和踩坑经验,每一条都是真金白银攒出来的。
6.1 用例不稳定怎么办
UI自动化最常见的失败原因:元素定位超时、弹窗遮挡、网络延迟。接口自动化最常见的失败原因:测试数据被污染(比如重复执行时创建了相同的数据)、环境依赖(依赖的第三方服务挂了)。
解决思路:
- UI方面,优先使用显式等待替代固定sleep,移动端额外考虑分辨率适配。
- 接口方面,用例自带数据清理逻辑(执行前清理历史测试数据,执行后标记清理),尽量使用独立的测试账号和数据构造器,避免用例之间相互依赖。
- 引入失败重试机制。自定义一个RetryListener,对于UI用例重试2次、接口用例重试1次,重试成功不算失败,但要在报告中标记。实测这个机制能把UI测试的通过率从85%拉到95%以上。
6.2 团队抗拒用自动化框架怎么办
技术选型再先进,如果团队不愿意用,框架就是摆设。我的经验是:不要一上来就要求团队所有人都写自动化脚本。先把框架封装到“不需要懂代码也能执行用例”的程度,然后从团队中挑一两个写代码意愿强、学得快的同学做“种子用户”,输出一个标准模板用例,其他同学照着模板套。
后续降低使用门槛,可以配置一个简单的Web操作页面(或者用现成的开源API管理平台),让测试同学在线选择用例、填写参数、触发执行、查看报告。当大家发现“点点鼠标就能完成回归、自动出报告”比纯手工舒服时,自动化的推广就水到渠成了。
6.3 测试数据和环境的隔离问题
一个经常翻车的场景:自动化测试在执行过程中创建的数据污染了测试环境,导致研发自测时看到一堆“垃圾数据”。解决这个问题要在框架设计时就考虑:
- 所有自动化造数都带统一前缀或专属标记(比如用户名统一加
at_前缀),便于识别和清理。 - 写一个全局的
@AfterSuite清理方法,执行完毕后删除本套件产生的所有测试数据。 - 如果测试环境可以随时重建,优先使用Docker容器做环境隔离。测试环境报废了直接重建,成本最低。
6.4 框架文档和知识沉淀
框架代码再健壮,没有文档就是一座孤岛。我在项目里强制维护两份文档:
README.md:快速上手文档。包含环境准备、框架结构说明、一条用例从编写到执行的完整示例、遇到问题联系谁。docs/框架设计说明.md:记录核心设计决策和取舍原因。比如“为什么用TestNG而不是JUnit5”“为什么数据文件用YAML而不是Excel”。写清楚当时基于什么背景做了这个选择、有哪些备选方案、未来什么条件下可以考虑迁移。
这类文档最大的价值不是给新人看,而是给几个月之后的自己看——框架改动后,如果没有设计决策记录,很多“当时为什么这么做”的问题会反复拷问后来维护的人。
根据我个人经验,自动化测试框架的落地很少是技术瓶颈,更多是工程化思维和团队协同的问题。技术方案再成熟,如果在选型、分层、CI集成、团队推广任何一环上掉链子,都可能功亏一篑。把这个框架跑起来并不难,难的是坚持按照“低维护成本”的原则持续优化迭代。哪怕你现在从零开始,也可以先把最小骨架搭起来,让一条接口用例跑通,再按照这篇博客的思路逐步加厚。每加一块能力,都问自己一句:这个改动是让框架更容易维护,还是更复杂了?答案如果是后者,就要警惕过度设计了。