数据提供者 TestNG: Selenium 参数化示例
⚡ 智能摘要
参数化于 Selenium 对不同的数据集重复运行单个测试方法,将一个脚本转化为数据驱动的覆盖率。 TestNG 提供了两种机制来实现这一点:由 testng.xml 支持的 @Parameters 注解,以及返回二维对象数组的 @DataProvider 注解。

我们在开发软件时,总是希望它能正确处理不同的数据集。说到 测试 仅凭该软件检查一组数据是不够的。我们需要验证系统是否能接受所有预期支持的组合。为此,我们需要对测试脚本进行参数化。这就是参数化的作用所在。
参数化于 Selenium
参数化于 Selenium 是参数化测试脚本的过程,以便在运行时将多个数据传递给应用程序。这是一种使用不同值自动多次运行测试用例的执行策略。通过参数化测试脚本实现的概念称为 数据驱动测试.
参数化类型 TestNG
为了更清晰地阐述参数化,我们将介绍最流行的框架之一中的参数化选项。 Selenium WebDriver — TestNG.
这里有 两种方式 通过它可以实现参数化 TestNG:
- 在...的帮助下 参数注释 和 TestNG XML 文件中。
- 在...的帮助下 数据提供者 注解。
上图总结了这种划分:testng.xml 中的参数可以在套件或测试级别声明,而来自 DataProvider 的参数可以接受 Method 和 ITestContext 它本身就是一个论点。让我们详细研究一下。
参数注释 TestNG
参数注释 TestNG `@Parameters` 注解是一种使用 .xml 文件将值作为参数传递给测试方法的方法。用户可能需要在运行时将这些值传递给测试方法。`@Parameters` 注解方法可以用于任何带有 `@Test`、`@Before`、`@After` 或 `@Factory` 注解的方法中。
使用Testng.xml进行参数注释
当您不想处理复杂性且输入组合数量较少时,请选择使用注解进行参数化。
让我们看看这是如何工作的。
测试场景
步骤1) 启动浏览器并访问 Google.com
步骤2) 输入搜索关键词
步骤3) 请确认输入的值与测试数据提供的值一致。
步骤4) 重复步骤 2 和 3,直到输入所有值为止。
| 测试作者 | 搜索关键字 |
|---|---|
| Guru99 | 印度 |
| Krishna | 美国 |
| 布佩什 | 中国 |
以下是一个操作示例。 也完全不需要 参数:
package parameters; import org.testng.annotations.Test; import org.testng.AssertJUnit; import java.time.Duration; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; public class NoParameterWithTestNGXML { WebDriver driver; @Test public void testNoParameter() throws InterruptedException { String author = "guru99"; String searchKey = "india"; driver = new FirefoxDriver(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); driver.get("https://google.com"); WebElement searchText = driver.findElement(By.name("q")); // Searching text in the Google text box searchText.sendKeys(searchKey); System.out.println("Welcome ->" + author + " Your search key is->" + searchKey); Thread.sleep(3000); System.out.println("Value in Google Search Box = " + searchText.getDomProperty("value") + " ::: Value given by input = " + searchKey); // Verifying the value in the Google search box AssertJUnit.assertTrue(searchText.getDomProperty("value").equalsIgnoreCase(searchKey)); } }
⚠️ Selenium 4 年备注: 此示例原始版本中的三行代码已无法编译或在当前版本中已被弃用。 Selenium 版本。 System.setProperty("webdriver.gecko.driver", …) 没有必要 Selenium 4.6及以后版本,因为 Selenium 管理器会自动解析驱动程序二进制文件。 implicitlyWait(10, TimeUnit.SECONDS) 过载已被消除 Selenium 4 并被替换为 implicitlyWait(Duration.ofSeconds(10)). getAttribute() 已弃用 Selenium 4.27,所以读取文本框使用 getDomProperty("value")以下每个示例都应用了相同的三个修正。
研究上面的例子,想象一下,如果对三种输入组合重复此操作,代码会变得多么复杂。
现在让我们用以下方式对其进行参数化: TestNG为此,您需要:
- 创建一个用于存储参数的 XML 文件
- 在测试中,添加注解 @Parameters
以下是完整代码。
测试级别 TestNG。XML
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"> <suite name="TestSuite" thread-count="3"> <parameter name="author" value="Guru99" /> <parameter name="searchKey" value="India" /> <test name="testGuru"> <parameter name="searchKey" value="UK" /> <classes> <class name="parameters.ParameterWithTestNGXML"></class> </classes> </test> </suite>
參數TestNGXML.java 文件
package parameters; import org.testng.AssertJUnit; import java.time.Duration; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; import org.testng.annotations.Optional; import org.testng.annotations.Parameters; import org.testng.annotations.Test; public class ParameterWithTestNGXML { WebDriver driver; @Test @Parameters({"author", "searchKey"}) public void testParameterWithXML(@Optional("Abc") String author, String searchKey) throws InterruptedException { driver = new FirefoxDriver(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); driver.get("https://google.com"); WebElement searchText = driver.findElement(By.name("q")); // Searching text in the Google text box searchText.sendKeys(searchKey); System.out.println("Welcome ->" + author + " Your search key is->" + searchKey); Thread.sleep(3000); System.out.println("Value in Google Search Box = " + searchText.getDomProperty("value") + " ::: Value given by input = " + searchKey); // Verifying the value in the Google search box AssertJUnit.assertTrue(searchText.getDomProperty("value").equalsIgnoreCase(searchKey)); } }
要运行脚本,请选择 XML 文件并将其作为命令运行。 TestNG 套房。
右键单击 .xml 文件 -> 选择“运行方式” -> TestNG 套房(注:套房)
参数可以在两个层面上定义:
- 套房级别 — 内部参数
<suite>标签 TestNG XML 文件是套件级别的参数。 - 测试级别 — 内部参数
<test>标签 TestNG XML 文件是测试级别的参数。
以下是使用套件级别参数的相同测试:
注意: 如果套件级别和测试级别的参数名称相同,则测试级别参数优先于套件级别参数。在这种情况下,该测试级别内的所有类共享重写的参数,而该测试级别外的类继续使用套件级别参数。
故障排除
问题 # 1: testng.xml 中的参数值如果无法强制转换为相应测试方法的参数类型,则会引发错误。
考虑以下示例:
这里,“作者”属性等于“Guru99' 是一个字符串,而相应的测试方法需要一个整数值,因此会引发异常。
问题 # 2: 您的 @Parameters 在 testng.xml 中没有对应的值。
您可以通过添加以下内容来解决这个问题: @Optional 注解 与测试方法中的相应参数对应。
问题 # 3: 你想使用 testng.xml 测试同一参数的多个值。
简单来说,这是不可能的。你可以设置多个不同的参数,但每个参数只能存储一个值。这样可以避免将值硬编码到脚本中,从而保持代码的可重用性——你可以把它想象成脚本的配置文件。如果你需要为一个参数设置多个值,请改用数据提供程序(DataProvider)。
数据提供者 TestNG
数据提供者 TestNG 当用户需要传递复杂参数时,可以使用这种方法。复杂参数需要从……创建。 Java — 数据提供程序方法可以传递复杂对象、属性文件中的对象或数据库中的对象。该方法使用 `@DataProvider` 注解,并返回一个对象数组。
使用 Dataprovider 的参数
@Parameters 注解很容易使用,但是要使用多组数据进行测试,我们需要使用数据提供程序。
要使用我们的测试框架填写数千个网页表单,我们需要一种不同的方法,该方法可以在单个执行流程中提供非常大的数据集。
这种数据驱动的概念是通过以下方式实现的: @DataProvider 注释 TestNG.
它只有一个 属性,'name'如果您不指定 name 属性,则 DataProvider 的名称与相应的方法名称相同。
数据提供商返回 二维 Java 对象 测试方法会被调用 M 次,每次调用都会针对一个 M×N 的对象数组执行一次。例如,如果 DataProvider 返回一个 2×3 的对象数组,则相应的测试用例会被调用 2 次,每次调用都会传入 3 个参数。
完整示例
package parameters; import java.time.Duration; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class ParameterByDataprovider { WebDriver driver; @BeforeTest public void setup() { // Create the Firefox driver object driver = new FirefoxDriver(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); driver.get("https://google.com"); } /** Test case to verify the Google search box */ @Test(dataProvider = "SearchProvider") public void testMethod(String author, String searchKey) throws InterruptedException { WebElement searchText = driver.findElement(By.name("q")); // Search value in the Google search box searchText.sendKeys(searchKey); System.out.println("Welcome ->" + author + " Your search key is->" + searchKey); Thread.sleep(3000); String testValue = searchText.getDomProperty("value"); System.out.println(testValue + "::::" + searchKey); searchText.clear(); // Verify if the value in the Google search box is correct Assert.assertTrue(testValue.equalsIgnoreCase(searchKey)); } /** * @return Object[][] where the first column contains 'author' * and the second column contains 'searchKey' */ @DataProvider(name = "SearchProvider") public Object[][] getDataFromDataprovider() { return new Object[][] { { "Guru99", "India" }, { "Krishna", "UK" }, { "Bhupesh", "USA" } }; } }
从不同的类调用 DataProvider
默认情况下,DataProvider 与测试方法位于同一类或其基类中。要将其放置在其他类中,请将数据提供程序方法声明为 `DataProvider`。 静止 并添加该属性 数据提供者类 以及 @测试 注解。
Code 例如:
测试类ParameterDataproviderWithClassLevel.java
package parameters; import java.time.Duration; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; public class ParameterDataproviderWithClassLevel { WebDriver driver; @BeforeTest public void setup() { driver = new FirefoxDriver(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); driver.get("https://google.com"); } @Test(dataProvider = "SearchProvider", dataProviderClass = DataproviderClass.class) public void testMethod(String author, String searchKey) throws InterruptedException { WebElement searchText = driver.findElement(By.name("q")); // Search text in the Google text box searchText.sendKeys(searchKey); System.out.println("Welcome ->" + author + " Your search key is->" + searchKey); Thread.sleep(3000); // Get text from the search box String testValue = searchText.getDomProperty("value"); System.out.println(testValue + "::::" + searchKey); searchText.clear(); // Verify if the search box has the correct value Assert.assertTrue(testValue.equalsIgnoreCase(searchKey)); } }
数据提供者类.java
package parameters; import org.testng.annotations.DataProvider; public class DataproviderClass { @DataProvider(name = "SearchProvider") public static Object[][] getDataFromDataprovider() { return new Object[][] { { "Guru99", "India" }, { "Krishna", "UK" }, { "Bhupesh", "USA" } }; } }
Dataprovider 中的参数类型
DataProvider 方法支持两种类型的参数。
付款方式 — 如果 同 对于不同的测试方法,DataProvider 的行为应该有所不同,请使用 Method 参数。
在以下示例中:
- 我们检查方法名称是否为 testMethodA
- 如果是,则返回一组值
- 否则返回另一组值
package parameters; import java.lang.reflect.Method; import java.time.Duration; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; import org.testng.Assert; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class ParameterByMethodInDataprovider { WebDriver driver; @BeforeTest public void setup() { driver = new FirefoxDriver(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); driver.get("https://google.com"); } @Test(dataProvider = "SearchProvider") public void testMethodA(String author, String searchKey) throws InterruptedException { WebElement searchText = driver.findElement(By.name("q")); searchText.sendKeys(searchKey); // Print author and search string System.out.println("Welcome ->" + author + " Your search key is->" + searchKey); Thread.sleep(3000); String testValue = searchText.getDomProperty("value"); System.out.println(testValue + "::::" + searchKey); searchText.clear(); Assert.assertTrue(testValue.equalsIgnoreCase(searchKey)); } @Test(dataProvider = "SearchProvider") public void testMethodB(String searchKey) throws InterruptedException { WebElement searchText = driver.findElement(By.name("q")); searchText.sendKeys(searchKey); // Print only the search string System.out.println("Welcome ->Unknown user Your search key is->" + searchKey); Thread.sleep(3000); String testValue = searchText.getDomProperty("value"); System.out.println(testValue + "::::" + searchKey); searchText.clear(); Assert.assertTrue(testValue.equalsIgnoreCase(searchKey)); } /** The DataProvider returns values based on the test method name */ @DataProvider(name = "SearchProvider") public Object[][] getDataFromDataprovider(Method m) { if (m.getName().equalsIgnoreCase("testMethodA")) { return new Object[][] { { "Guru99", "India" }, { "Krishna", "UK" }, { "Bhupesh", "USA" } }; } else { return new Object[][] { { "Canada" }, { "Russia" }, { "Japan" } }; } } }
这是输出:
测试上下文 — 这可用于根据分组创建不同的测试用例参数。
在实际应用中,您可以使用 ITestContext 根据测试方法、主机或测试配置来改变参数值。
以下代码示例:
- 我们有两个小组,A组和B组。
- 每种测试方法都分配到一个组
- 如果分组值为 A,则返回一个数据集
- 如果组值为 B,则返回另一个数据集。
package parameters; import java.time.Duration; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; import org.testng.Assert; import org.testng.ITestContext; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class ParameterByITestContextInDataprovider { WebDriver driver; @BeforeTest(groups = {"A", "B"}) public void setup() { driver = new FirefoxDriver(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); driver.get("https://google.com"); } @Test(dataProvider = "SearchProvider", groups = "A") public void testMethodA(String author, String searchKey) throws InterruptedException { WebElement searchText = driver.findElement(By.name("q")); searchText.sendKeys(searchKey); System.out.println("Welcome ->" + author + " Your search key is->" + searchKey); Thread.sleep(3000); String testValue = searchText.getDomProperty("value"); System.out.println(testValue + "::::" + searchKey); searchText.clear(); Assert.assertTrue(testValue.equalsIgnoreCase(searchKey)); } @Test(dataProvider = "SearchProvider", groups = "B") public void testMethodB(String searchKey) throws InterruptedException { WebElement searchText = driver.findElement(By.name("q")); searchText.sendKeys(searchKey); System.out.println("Welcome ->Unknown user Your search key is->" + searchKey); Thread.sleep(3000); String testValue = searchText.getDomProperty("value"); System.out.println(testValue + "::::" + searchKey); searchText.clear(); Assert.assertTrue(testValue.equalsIgnoreCase(searchKey)); } /** The DataProvider supplies an Object array based on ITestContext */ @DataProvider(name = "SearchProvider") public Object[][] getDataFromDataprovider(ITestContext c) { Object[][] groupArray = null; for (String group : c.getIncludedGroups()) { if (group.equalsIgnoreCase("A")) { groupArray = new Object[][] { { "Guru99", "India" }, { "Krishna", "UK" }, { "Bhupesh", "USA" } }; break; } else if (group.equalsIgnoreCase("B")) { groupArray = new Object[][] { { "Canada" }, { "Russia" }, { "Japan" } }; break; } } return groupArray; } }
注意: 如果你运行 TestNG 直接调用该类时,它会先调用数据提供程序,但由于分组信息尚未可用,提供程序无法读取分组信息。通过 testng.xml 调用该类,则可以通过 ITestContext 获取分组信息。请使用以下 XML 运行测试:
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"> <suite name="test-parameter"> <test name="example1"> <groups> <run> <include name="A" /> </run> </groups> <classes> <class name="parameters.ParameterByITestContextInDataprovider" /> </classes> </test> <test name="example2"> <groups> <run> <include name="B" /> </run> </groups> <classes> <class name="parameters.ParameterByITestContextInDataprovider" /> </classes> </test> </suite>
两种机制现在都已涵盖,因此实际问题是应该选择哪一种。
@Parameters 与 @DataProvider:应该使用哪一个?
这两种方法不能互换。最终取决于测试需要多少种数值组合以及这些数值的来源。
| 标准 | @Parameters + testng.xml | @DataProvider |
|---|---|---|
| 每个参数的值 | 正好一个 | 行数不限 |
| 数据源 | XML 文件中的静态文本 | Java 代码、Excel、CSV、数据库、API |
| 支持的类型 | 将字符串类型的值进行类型转换 | 任何 Java 对象 |
| 测试调用 | 每个配置一次 | 每行数据一次 |
| 更改数据需要 | 编辑 XML 文件 | 编辑源代码,通常无需重新编译 |
| 最适合 | 环境和配置值,例如浏览器、 URL, 证书 | 针对多种输入组合进行数据驱动测试 |
实际上,大多数测试套件都会同时使用这两种方法:`@Parameters` 承载着运行过程中保持不变的环境设置,而 `@DataProvider` 则提供不断变化的业务数据。当数据集超出硬编码数组的容量时,下一步就是从电子表格中读取数据。
如何使用数据提供程序从 Excel 文件中读取测试数据
一旦测试人员不编写代码,硬编码数组就无法扩展了。 Java 需要维护数据。将行移动到电子表格中可以将测试数据与测试逻辑分开,这是实际应用的形式。 数据驱动测试Apache POI 读取工作簿,DataProvider 将每一行转换为一次测试调用。
- 添加依赖项。 包括
org.apache.poi:poi-ooxml在你的 Maven 或 Gradle 建造。ooxmlartifact 处理 .xlsx 格式;plainpoi仅读取旧版 .xls 文件。 - 创建工作簿。 将标题行放在第 0 行,在其下方每行放置一个测试用例,每列放置一个测试方法参数。
- 将表格内容读入数组。 根据行数和单元格数调整 Object[][] 的大小,以便在不更改代码的情况下获取新行。
- 从供应商处退货。 测试方法签名与之前完全相同。
import java.io.FileInputStream; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.testng.annotations.DataProvider; public class ExcelDataProvider { @DataProvider(name = "SearchProvider") public Object[][] readSearchData() throws Exception { FileInputStream file = new FileInputStream("src/test/resources/testdata.xlsx"); Workbook workbook = new XSSFWorkbook(file); Sheet sheet = workbook.getSheetAt(0); // Skip the header row, so subtract one from the physical row count int rows = sheet.getLastRowNum(); int cols = sheet.getRow(0).getPhysicalNumberOfCells(); Object[][] data = new Object[rows][cols]; for (int i = 1; i <= rows; i++) { for (int j = 0; j < cols; j++) { data[i - 1][j] = sheet.getRow(i).getCell(j).getStringCellValue(); } } workbook.close(); file.close(); return data; } }
💡提示: 电话联系 getStringCellValue() 仅适用于格式为文本的单元格。数值单元格会抛出异常。 IllegalStateException这是电子表格包含 ID 或金额时最常见的错误。请使用 DataFormatter 将每种单元格类型读取为字符串,或开启 cell.getCellType() 并显式转换。
数据外部化后,添加一百个测试用例只需编辑电子表格,而无需更改代码,而且同一个提供程序可以通过该提供程序向多个测试类提供数据。 dataProviderClass 前面提到的属性。
















