Java与Selenium构建Web自动化测试:从环境搭建到工程化实践
1. 项目概述:为什么选择Java与Selenium构建Web自动化测试
如果你是一名Java开发者,或者你的团队技术栈以Java为主,当需要为Web应用引入自动化测试时,Selenium几乎是绕不开的选择。这不仅仅是因为Selenium是Web自动化测试领域事实上的标准,更因为Java与Selenium的结合,提供了一种在企业级开发中极为可靠、稳定且可维护性高的解决方案。我见过太多项目,初期为了快速验证,用Python或JavaScript写了一些测试脚本,但随着项目迭代、团队扩大,这些脚本很快就因为缺乏结构、难以维护而变成“一次性用品”,最终被废弃。而基于Java的Selenium测试框架,从设计之初就考虑了工程化的需求,无论是依赖管理、测试组织、报告生成,还是与持续集成(CI/CD)流水线的集成,都能提供坚实的支撑。
这个项目的核心,就是搭建一个基于Java的Selenium Web自动化测试环境。听起来简单,不就是装个库、下个驱动吗?但实际操作中,从环境变量配置、依赖版本对齐,到浏览器驱动的动态管理,每一步都可能藏着让你调试半天的“坑”。特别是对于刚接触自动化测试的Java开发者,很容易在“环境”这一步就卡住,感觉明明照着教程做了,浏览器就是打不开,或者各种奇怪的版本冲突错误。接下来,我会以一个从业超过十年的测试开发视角,带你从零开始,不仅把环境搭起来,更要理解每一步背后的逻辑,分享那些官方文档里不会写的实操细节和避坑指南,让你搭建的环境既稳固又易于团队协作。
2. 环境准备与核心组件解析
2.1 Java开发环境:不止是安装JDK
很多人认为Java环境就是装个JDK,设置一下 JAVA_HOME 就完事了。但对于自动化测试项目,我们需要考虑得更周全。
首先, JDK版本的选择 。我强烈建议使用 JDK 8或JDK 11 这两个LTS(长期支持)版本。目前(以当前技术环境为参考),JDK 8依然拥有最广泛的生态兼容性,而JDK 11则是更多新项目的起点。避免使用过于前沿的版本(如JDK 17+的某些早期小版本),以免遇到Selenium或第三方库的兼容性问题。你可以通过命令 java -version 来验证安装。
其次,关于 构建工具 。这是Java项目工程化的基石。你有两个主流选择:Maven或Gradle。对于自动化测试项目,我个人的偏好是 Maven ,原因在于其约定大于配置的理念,使得项目结构非常清晰,并且中央仓库的依赖管理对于测试所需的各类库(如Selenium、TestNG、日志组件)支持得非常好。在项目根目录的 pom.xml 文件中,我们将定义所有依赖。
注意:确保你的IDE(如IntelliJ IDEA或Eclipse)正确识别了构建工具。在IDEA中,导入项目后,务必检查右下角是否弹出“Maven项目需要导入”的提示,点击
Import Changes。很多“找不到类”的错误,都是因为依赖没有正确加载。
2.2 Selenium Java Client:沟通的桥梁
Selenium的核心是一个用于控制浏览器的协议——WebDriver协议。而我们写的Java代码并不能直接和浏览器对话,需要一个“翻译官”,这就是 Selenium Java Client Library 。它是一组Java库,将我们的Java指令(如 findElement , click )翻译成WebDriver协议规定的HTTP请求,发送给浏览器驱动。
在Maven的 pom.xml 中,添加Selenium依赖非常简单:
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.11.0</version> <!-- 请使用当时最新的稳定版本 -->
</dependency>
添加这一个依赖,Maven会自动帮你引入一系列相关的子模块,如 selenium-api , selenium-chrome-driver , selenium-support 等。这里有一个关键点: 保持版本统一 。整个项目,包括所有团队成员,都应该使用相同版本的 selenium-java 。版本不一致是导致“在我机器上能跑”这类问题的常见元凶。
2.3 浏览器与浏览器驱动:真正的执行者
这是最容易出错的环节。你需要理解三者之间的关系:
- 浏览器 :如Chrome, Firefox, Edge。用户肉眼可见的窗口。
- 浏览器驱动 :如ChromeDriver, geckodriver (Firefox), msedgedriver。这是一个独立的可执行文件,由浏览器厂商提供。它的作用是充当本地HTTP服务器,接收来自Selenium Java Client的请求,并将其转化为对浏览器内核的实际操作。
- Selenium Java Client :我们写的测试代码。
它们的工作流程是 :你的Java代码(Client) -> HTTP请求 -> 浏览器驱动(Driver) -> 操作系统级指令 -> 浏览器(Browser)。
因此,安装的关键在于 驱动 。并且有一个黄金法则: 浏览器驱动的版本必须与已安装的浏览器主版本号完全匹配 。Chrome 115就需要ChromeDriver 115,用114或116都可能失败。
驱动的管理策略 :
- 手动下载放置 :从官方站点(如ChromeDriver: https://chromedriver.chromium.org/)下载对应版本的驱动,将其所在目录添加到系统的
PATH环境变量中。这是最传统的方法,但管理多个版本很麻烦。 - 使用
WebDriverManager库(强烈推荐) :这是一个开源库,能自动检测你本地安装的浏览器版本,并下载匹配的驱动。它能极大简化环境配置。只需在pom.xml中添加依赖:
然后在代码中,一行代码即可搞定驱动设置:<dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>5.5.3</version> </dependency>WebDriverManager.chromedriver().setup();
3. 项目搭建与基础框架构建
3.1 创建Maven项目与基础结构
使用IDE或命令行创建一个标准的Maven项目。我建议的目录结构如下:
your-autotest-project
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ │ └── com
│ │ └── yourcompany
│ │ └── test
│ │ ├── base
│ │ │ └── BaseTest.java
│ │ ├── pages
│ │ │ └── LoginPage.java
│ │ └── tests
│ │ └── LoginTest.java
│ └── resources
│ ├── config.properties
│ └── log4j2.xml
├── pom.xml
└── README.md
src/main/java:通常放一些工具类、辅助类。src/test/java:测试代码的家。base包放基础类(如初始化驱动),pages包放页面对象类,tests包放具体的测试用例。src/test/resources:放配置文件、测试数据等。
3.2 编写基础测试类与驱动初始化
在 BaseTest.java 中,我们封装WebDriver的初始化和销毁逻辑。这里以Chrome为例,展示结合 WebDriverManager 的最佳实践。
package com.yourcompany.test.base;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import java.time.Duration;
public class BaseTest {
protected WebDriver driver;
@BeforeMethod
public void setUp() {
// 1. 自动管理ChromeDriver
WebDriverManager.chromedriver().setup();
// 2. 配置浏览器选项
ChromeOptions options = new ChromeOptions();
// 添加常用选项
options.addArguments("--start-maximized"); // 最大化窗口
options.addArguments("--disable-infobars"); // 禁用“Chrome正在受自动化软件控制”提示
options.addArguments("--disable-notifications"); // 禁用通知
// options.addArguments("--headless"); // 无头模式,用于CI环境,平时调试可注释掉
// 3. 实例化Driver
driver = new ChromeDriver(options);
// 4. 设置全局等待(隐式等待)
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));
// 5. 打开初始页面(例如公司测试环境首页)
driver.get("https://your-test-environment.com");
}
@AfterMethod
public void tearDown() {
if (driver != null) {
driver.quit(); // 使用quit()而非close(),quit会关闭所有窗口并终止驱动进程
}
}
}
关键点解析 :
WebDriverManager.chromedriver().setup():这行代码是神器。它会在用户主目录下(如~/.cache/selenium)维护一个驱动缓存,避免重复下载。ChromeOptions:用于精细控制浏览器行为。--disable-infobars可以避免那个显眼的自动化提示,让测试环境更“干净”。- 隐式等待 :
implicitlyWait设置了一个全局的等待时间,在查找元素时,如果元素没有立即出现,Selenium会轮询查找直到超时。这比写死Thread.sleep()要高效和优雅得多。 driver.quit()vsdriver.close():务必用quit()。close()只关闭当前标签页,而quit()会关闭所有窗口并安全地终止后台的驱动进程,防止内存泄漏。
3.3 引入测试框架:TestNG vs JUnit
Java世界主要有两大测试框架:JUnit和TestNG。对于Selenium自动化测试,我 更推荐TestNG ,原因如下:
- 更丰富的注解 :支持更灵活的测试生命周期管理(如
@BeforeSuite,@AfterTest)。 - 依赖测试 :可以通过
dependsOnMethods设置测试方法间的依赖关系。 - 参数化测试 :支持从
@DataProvider传入多组测试数据,非常适合用不同数据驱动同一个测试流程。 - 并行测试 :原生支持在套件或测试级别进行并行执行,能大幅缩短测试集运行时间。
- 更强大的报告 :默认生成的HTML报告比JUnit更详细。
在 pom.xml 中添加TestNG依赖:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.8.0</version>
<scope>test</scope>
</dependency>
创建一个简单的测试类 LoginTest.java 来继承 BaseTest :
package com.yourcompany.test.tests;
import com.yourcompany.test.base.BaseTest;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
public class LoginTest extends BaseTest {
@Test
public void testSuccessfulLogin() {
// 这里暂时直接操作,后续会引入Page Object模式
driver.findElement(By.id("username")).sendKeys("validUser");
driver.findElement(By.id("password")).sendKeys("validPass");
driver.findElement(By.tagName("button")).click();
// 验证登录成功,例如检查是否跳转到首页或出现欢迎语
String welcomeText = driver.findElement(By.cssSelector(".welcome-msg")).getText();
assertTrue(welcomeText.contains("欢迎回来"), "登录成功后未找到欢迎信息");
}
}
现在,在IDE中右键运行这个测试类,你应该能看到一个Chrome浏览器自动打开,并执行登录操作。恭喜,你的第一个基于Java的Selenium自动化测试已经跑通了!
4. 进阶实践:设计模式与最佳实践
4.1 Page Object Model (POM):让代码可维护
直接在测试类里写 findElement 和 sendKeys 是“脚本”,而不是“框架”。当页面元素变更时,你需要修改所有相关的测试用例,维护成本极高。 Page Object Model (页面对象模式) 是解决这一问题的标准设计模式。
其核心思想是: 将一个页面的元素定位和操作封装在一个单独的类中 。测试用例只关心业务逻辑(做什么),不关心具体实现(怎么做)。
我们重构上面的登录测试。首先创建 LoginPage.java :
package com.yourcompany.test.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class LoginPage {
private WebDriver driver;
// 使用@FindBy注解定位元素,这是PageFactory模式的一部分
@FindBy(id = "username")
private WebElement usernameInput;
@FindBy(id = "password")
private WebElement passwordInput;
@FindBy(tagName = "button")
private WebElement loginButton;
@FindBy(cssSelector ".welcome-msg")
private WebElement welcomeMessage;
// 构造函数,初始化元素
public LoginPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this); // 关键!初始化所有@FindBy注解的元素
}
// 页面操作方法
public void enterUsername(String username) {
usernameInput.clear();
usernameInput.sendKeys(username);
}
public void enterPassword(String password) {
passwordInput.clear();
passwordInput.sendKeys(password);
}
public void clickLogin() {
loginButton.click();
}
public String getWelcomeMessage() {
return welcomeMessage.getText();
}
// 一个完整的业务流方法
public void login(String username, String password) {
enterUsername(username);
enterPassword(password);
clickLogin();
}
}
然后,修改 LoginTest.java :
package com.yourcompany.test.tests;
import com.yourcompany.test.base.BaseTest;
import com.yourcompany.test.pages.LoginPage;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
public class LoginTest extends BaseTest {
@Test
public void testSuccessfulLoginWithPOM() {
LoginPage loginPage = new LoginPage(driver);
loginPage.login("validUser", "validPass");
String actualMessage = loginPage.getWelcomeMessage();
assertTrue(actualMessage.contains("欢迎回来"), "登录失败,欢迎信息为: " + actualMessage);
}
}
看,测试用例变得多么清晰!所有关于登录页的细节都被隐藏在 LoginPage 类中。如果登录按钮的标签从 <button> 变成了 <input type="submit"> ,你只需要修改 LoginPage 类中的一处定位即可,所有测试用例都不受影响。
4.2 等待策略:从隐式等待到显式等待
前面我们设置了隐式等待,但它并非万能。隐式等待适用于 findElement 这类查找操作。但对于一些复杂的条件,如“元素可点击”、“元素包含特定文本”、“某个弹窗消失”,就需要 显式等待 。
显式等待允许你为某个特定条件设置等待,更加灵活和精确。Selenium提供了 WebDriverWait 和 ExpectedConditions 类来实现。
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public void waitForElementToBeClickable(WebElement element) {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
wait.until(ExpectedConditions.elementToBeClickable(element));
}
// 在页面对象中使用
public void clickLoginWithExplicitWait() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
wait.until(ExpectedConditions.elementToBeClickable(loginButton)).click();
}
最佳实践 : 混合使用,但以显式等待为主 。在 BaseTest 的 setUp 中设置一个较短的全局隐式等待(如5-10秒),作为兜底策略。在具体的页面操作中,针对关键交互使用更精确的显式等待。避免过度依赖隐式等待,因为它会对所有 findElement 生效,可能在不必要的地方浪费时间。
4.3 数据驱动测试
硬编码的测试数据(如 "validUser" )不利于测试覆盖。TestNG的 @DataProvider 可以轻松实现数据驱动。
package com.yourcompany.test.tests;
import com.yourcompany.test.base.BaseTest;
import com.yourcompany.test.pages.LoginPage;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertTrue;
public class LoginDataDrivenTest extends BaseTest {
// 定义数据提供者
@DataProvider(name = "loginData")
public Object[][] provideLoginData() {
return new Object[][] {
{"correctUser", "correctPass", true, "欢迎回来"},
{"wrongUser", "correctPass", false, "用户名或密码错误"},
{"correctUser", "wrongPass", false, "用户名或密码错误"},
{"", "correctPass", false, "请输入用户名"},
// ... 更多测试用例
};
}
@Test(dataProvider = "loginData")
public void testLoginWithMultipleData(String username, String password, boolean expectSuccess, String expectedMessage) {
LoginPage loginPage = new LoginPage(driver);
loginPage.login(username, password);
String actualMessage = loginPage.getWelcomeMessage(); // 假设登录页也能获取错误信息
if (expectSuccess) {
assertTrue(actualMessage.contains(expectedMessage), "成功登录场景断言失败");
} else {
assertTrue(actualMessage.contains(expectedMessage), "失败登录场景断言失败");
}
}
}
这样,通过一个测试方法,就能运行多组数据,极大地提高了测试用例的覆盖率和编写效率。
5. 工程化与持续集成
5.1 配置文件管理
不要把测试环境的URL、账号密码等硬编码在代码里。使用 .properties 或 .yaml 文件来管理配置。
在 src/test/resources 下创建 config.properties :
# 环境配置
base.url=https://your-test-environment.com
browser=chrome
headless=false
# 测试账号
test.username=validUser
test.password=validPass
admin.username=adminUser
admin.password=adminPass
创建一个 ConfigReader 工具类来读取配置:
package com.yourcompany.test.utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ConfigReader {
private static Properties properties = new Properties();
static {
try (InputStream input = ConfigReader.class.getClassLoader().getResourceAsStream("config.properties")) {
if (input == null) {
throw new RuntimeException("找不到配置文件 config.properties");
}
properties.load(input);
} catch (IOException e) {
throw new RuntimeException("加载配置文件失败", e);
}
}
public static String getProperty(String key) {
return properties.getProperty(key);
}
public static String getBaseUrl() {
return getProperty("base.url");
}
// ... 其他getter方法
}
然后在 BaseTest 中,使用 ConfigReader.getBaseUrl() 来获取URL。
5.2 日志记录
使用日志(如Log4j2)替代 System.out.println() ,可以更好地控制输出级别(DEBUG, INFO, ERROR),并输出到文件或控制台。
添加Log4j2依赖到 pom.xml :
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
</dependency>
创建 log4j2.xml 配置文件,并在代码中记录关键操作和错误信息。
5.3 测试报告与截图
TestNG会生成默认的HTML报告,但我们可以做得更好。结合 ExtentReports 或 Allure 可以生成非常美观、信息丰富的测试报告。
以Allure为例,它能自动捕获测试步骤、截图、日志,并生成交互式报告。集成步骤包括添加Allure依赖、添加 @Step 注解到页面操作方法、在测试失败时自动截图并附加到报告。
自动截图工具方法 :
public class ScreenshotUtil {
public static String takeScreenshot(WebDriver driver, String testName) {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String fileName = testName + "_" + timestamp + ".png";
String filePath = "target/screenshots/" + fileName;
File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
try {
FileUtils.copyFile(scrFile, new File(filePath));
return filePath;
} catch (IOException e) {
e.printStackTrace();
return "截图失败: " + e.getMessage();
}
}
}
在 @AfterMethod 中,判断测试是否失败,如果失败则调用此方法截图。
5.4 集成到CI/CD流水线
自动化测试只有集成到CI/CD(如Jenkins, GitLab CI, GitHub Actions)中,才能发挥最大价值——持续反馈。
核心步骤:
- 代码管理 :将测试代码推送到Git仓库。
- CI配置 :在CI服务器上配置一个Job,触发条件可以是定时任务或代码推送。
- 环境准备 :CI Job的第一步是安装JDK、Maven,并可能使用Docker来保证浏览器环境的一致性(例如使用
selenium/standalone-chrome镜像)。 - 执行测试 :运行
mvn clean test命令。通常需要启用无头模式(--headless)以在没有图形界面的服务器上运行。 - 生成报告 :测试完成后,调用Allure或ExtentReports的命令生成报告,并发布到CI服务器的特定页面。
- 结果通知 :将测试结果(成功/失败,报告链接)通过邮件、钉钉、Slack等通知团队。
一个简单的GitHub Actions配置示例( .github/workflows/run-tests.yml ):
name: Java Selenium Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Run Tests with Maven
run: mvn clean test -Dbrowser=chrome -Dheadless=true
- name: Generate Allure Report
run: mvn allure:report
- name: Upload Allure Report
uses: actions/upload-artifact@v3
with:
name: allure-report
path: target/site/allure-maven-plugin
6. 常见问题排查与性能优化
6.1 典型问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
org.openqa.selenium.WebDriverException: unknown error: cannot find Chrome binary |
Chrome浏览器未安装或路径不对。 | 确认Chrome已安装。使用 WebDriverManager 可自动处理。检查 ChromeOptions 是否设置了错误的 binary 路径。 |
org.openqa.selenium.SessionNotCreatedException: ... This version of ChromeDriver only supports Chrome version ... |
浏览器驱动与浏览器版本不匹配。 | 使用 WebDriverManager 。或手动检查Chrome版本( chrome://settings/help ),下载对应版本的ChromeDriver。 |
NoSuchElementException |
元素定位失败。 | 1. 检查定位器(id, xpath等)是否正确,页面结构是否已变更。 2. 增加等待时间(隐式/显式等待)。 3. 检查元素是否在iframe或shadow DOM内,需要先切换上下文。 4. 页面未加载完成,检查网络或增加页面加载超时时间。 |
ElementNotInteractableException |
元素存在但不可交互(如被遮挡、未显示、禁用)。 | 1. 使用显式等待 elementToBeClickable 。 2. 使用JavaScript直接点击: ((JavascriptExecutor)driver).executeScript("arguments[0].click();", element) 。 3. 滚动元素到视图内: ((JavascriptExecutor)driver).executeScript("arguments[0].scrollIntoView(true);", element) 。 |
| 测试在本地通过,在CI服务器失败 | CI环境与本地环境差异。 | 1. 无头模式 :确保CI命令中包含了 -Dheadless=true 或代码中设置了 --headless 选项。 2. 资源不足 :CI服务器可能内存/CPU不足,导致浏览器启动慢或崩溃。尝试增加资源或优化测试。 3. 时区/语言 :CI服务器可能是UTC时区或英文环境,影响某些基于文本的断言。在 ChromeOptions 中设置语言偏好: options.addArguments("--lang=en-US") 。 |
| 测试执行速度慢 | 1. 等待时间设置过长。 2. 网络请求慢。 3. 不必要的页面加载或操作。 |
1. 优化等待策略,用显式等待替代长的隐式等待。 2. 在 ChromeOptions 中禁用图片加载以加速: options.addArguments("--blink-settings=imagesEnabled=false") 。 3. 分析测试流程,合并可以并行或无依赖的操作。 |
6.2 性能与稳定性优化技巧
-
驱动复用与会话隔离 :对于大型测试套件,频繁启动/关闭浏览器非常耗时。可以考虑使用
RemoteWebDriver连接到一个常驻的Selenium Grid或Standalone Server,但要注意测试之间的会话隔离,避免状态污染。一个折中方案是,在@BeforeMethod中启动浏览器,在@AfterMethod中关闭,保证每个测试方法独立。 -
并行测试 :TestNG支持在
testng.xml中配置并行执行。可以将测试类或方法分配到多个线程中同时运行,充分利用多核CPU。但并行测试需要确保测试用例之间没有共享状态(如操作同一个全局变量或数据库记录)。 -
智能等待与重试机制 :对于不稳定的元素或网络波动,可以封装一个“智能查找”方法,在
NoSuchElementException时进行有限次数的重试,而不是立即失败。 -
资源清理 :除了关闭浏览器,还要注意在
@AfterMethod或@AfterClass中清理测试产生的临时数据(如测试账号、上传的文件等),保证测试环境的纯净,避免测试间相互影响。 -
使用更快的定位器 :定位器性能大致排序:ID > Name > CSS Selector > XPath。尽量避免使用复杂的、特别是以
//开头的全局XPath,它们遍历整个DOM,速度很慢。优先使用ID,其次是CSS Selector。
搭建一个基于Java的Selenium自动化测试项目,远不止是引入几个依赖。它涉及环境配置、框架设计、模式应用、工程化集成和持续优化。从最简单的脚本开始,逐步引入Page Object、数据驱动、显式等待,再到配置CI/CD,每一步都在提升测试套件的可维护性、可靠性和价值。过程中遇到的每一个错误,都是加深对这套工具链理解的契机。记住,好的自动化测试不是一蹴而就的,它是一个需要随着产品迭代而不断演进和维护的资产。从今天搭建的这个稳固的基础开始,你可以逐步扩展,加入API测试、移动端测试,构建起一个完整的自动化测试体系。
更多推荐
所有评论(0)