数据提供者 TestNG: Selenium 参数化示例

⚡ 智能摘要

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

  • 🎯 核心宗旨: 参数化从测试脚本中移除硬编码值,以便相同的逻辑可以验证应用程序必须支持的每种输入组合。
  • 📄 XML机制: @Parameters 注解读取 testng.xml 中声明的名称-值对,这适用于组合数量较少的配置式输入。
  • 🏷️ 范围优先性: 在测试级别声明的参数会覆盖套件级别中同名的参数,而该测试之外的类会继续读取套件的值。
  • 🗂️ 数据提供机制: 带有 @DataProvider 注解的方法返回 Object[][],并且 TestNG 每行调用一次测试,并将每一列作为参数传递。
  • 🔗 外部供应商: 将提供程序标记为静态并设置 @Test 的 dataProviderClass,可以让多个测试类共享一个数据源。
  • 🧭 动态数据集: 接受 Method 或 ITestContext 作为提供程序参数,则每个测试方法或每个包含的组都会返回不同的数据。
  • 🛡️ 常见故障: XML 值和方法参数之间的类型不匹配,以及使用 @Optional 注解解决的参数缺失,是导致大多数注解错误的原因。

数据提供者 TestNG

我们在开发软件时,总是希望它能正确处理不同的数据集。说到 测试 仅凭该软件检查一组数据是不够的。我们需要验证系统是否能接受所有预期支持的组合。为此,我们需要对测试脚本进行参数化。这就是参数化的作用所在。

参数化于 Selenium

参数化于 Selenium 是参数化测试脚本的过程,以便在运行时将多个数据传递给应用程序。这是一种使用不同值自动多次运行测试用例的执行策略。通过参数化测试脚本实现的概念称为 数据驱动测试.

参数化类型 TestNG

为了更清晰地阐述参数化,我们将介绍最流行的框架之一中的参数化选项。 Selenium WebDriver — TestNG.

这里有 两种方式 通过它可以实现参数化 TestNG:

  1. 在...的帮助下 参数注释TestNG XML 文件中。

参数化类型 TestNG

  1. 在...的帮助下 数据提供者 注解。

参数化类型 TestNG

参数化类型 TestNG

上图总结了这种划分:testng.xml 中的参数可以在套件或测试级别声明,而来自 DataProvider 的参数可以接受 MethodITestContext 它本身就是一个论点。让我们详细研究一下。

参数注释 TestNG

参数注释 TestNG `@Parameters` 注解是一种使用 .xml 文件将值作为参数传递给测试方法的方法。用户可能需要在运行时将这些值传递给测试方法。`@Parameters` 注解方法可以用于任何带有 `@Test`、`@Before`、`@After` 或 `@Factory` 注解的方法中。

使用Testng.xml进行参数注释

当您不想处理复杂性且输入组合数量较少时,请选择使用注解进行参数化。

让我们看看这是如何工作的。

测试场景

步骤1) 启动浏览器并访问 Google.com

步骤2) 输入搜索关键词

使用 Testng.Xml 进行参数注释

步骤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 进行参数注释

以下是完整代码。

测试级别 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 套房(注:套房)

參數TestNGXML.java 文件

参数可以在两个层面上定义:

  1. 套房级别 — 内部参数 <suite> 标签 TestNG XML 文件是套件级别的参数。
  2. 测试级别 — 内部参数 <test> 标签 TestNG XML 文件是测试级别的参数。

以下是使用套件级别参数的相同测试:

參數TestNGXML.java 文件

注意: 如果套件级别和测试级别的参数名称相同,则测试级别参数优先于套件级别参数。在这种情况下,该测试级别内的所有类共享重写的参数,而该测试级别外的类继续使用套件级别参数。

參數TestNGXML.java 文件

故障排除

问题 # 1: testng.xml 中的参数值如果无法强制转换为相应测试方法的参数类型,则会引发错误。

考虑以下示例:

故障排除

这里,“作者”属性等于“Guru99' 是一个字符串,而相应的测试方法需要一个整数值,因此会引发异常。

问题 # 2: 您的 @Parameters 在 testng.xml 中没有对应的值。

您可以通过添加以下内容来解决这个问题: @Optional 注解 与测试方法中的相应参数对应。

故障排除

问题 # 3: 你想使用 testng.xml 测试同一参数的多个值。

简单来说,这是不可能的。你可以设置多个不同的参数,但每个参数只能存储一个值。这样可以避免将值硬编码到脚本中,从而保持代码的可重用性——你可以把它想象成脚本的配置文件。如果你需要为一个参数设置多个值,请改用数据提供程序(DataProvider)。

数据提供者 TestNG

数据提供者 TestNG 当用户需要传递复杂参数时,可以使用这种方法。复杂参数需要从……创建。 Java — 数据提供程序方法可以传递复杂对象、属性文件中的对象或数据库中的对象。该方法使用 `@DataProvider` 注解,并返回一个对象数组。

使用 Dataprovider 的参数

@Parameters 注解很容易使用,但是要使用多组数据进行测试,我们需要使用数据提供程序。

要使用我们的测试框架填写数千个网页表单,我们需要一种不同的方法,该方法可以在单个执行流程中提供非常大的数据集。

这种数据驱动的概念是通过以下方式实现的: @DataProvider 注释 TestNG.

使用 Dataprovider 的参数

它只有一个 属性,'name'如果您不指定 name 属性,则 DataProvider 的名称与相应的方法名称相同。

数据提供商返回 二维 Java 对象 测试方法会被调用 M 次,每次调用都会针对一个 M×N 的对象数组执行一次。例如,如果 DataProvider 返回一个 2×3 的对象数组,则相应的测试用例会被调用 2 次,每次调用都会传入 3 个参数。

使用 Dataprovider 的参数

完整示例

使用 Dataprovider 的参数

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`。 静止 并添加该属性 数据提供者类 以及 @测试 注解。

从不同的类调用 DataProvider

Code 例如:

从不同的类调用 DataProvider

测试类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 参数。

DataProvider 中的参数类型

在以下示例中:

  • 我们检查方法名称是否为 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" }
            };
        }
    }
}

这是输出:

DataProvider 中的参数类型

测试上下文 — 这可用于根据分组创建不同的测试用例参数。

在实际应用中,您可以使用 ITestContext 根据测试方法、主机或测试配置来改变参数值。

DataProvider 中的参数类型

以下代码示例:

  • 我们有两个小组,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 将每一行转换为一次测试调用。

  1. 添加依赖项。 包括 org.apache.poi:poi-ooxml 在你的 Maven 或 Gradle 建造。 ooxml artifact 处理 .xlsx 格式;plain poi 仅读取旧版 .xls 文件。
  2. 创建工作簿。 将标题行放在第 0 行,在其下方每行放置一个测试用例,每列放置一个测试方法参数。
  3. 将表格内容读入数组。 根据行数和单元格数调整 Object[][] 的大小,以便在不更改代码的情况下获取新行。
  4. 从供应商处退货。 测试方法签名与之前完全相同。
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 前面提到的属性。

常见问题

是的。设置 @DataProvider(name="x", parallel=true) 并发运行这些行。每个线程都需要自己的 WebDriver 实例,因此请将驱动程序存储在 ThreadLocal 中,否则测试会相互干扰。

是的。 TestNG 接受 Iterator<Object[]>它会延迟加载行,而不是在内存中构建整个数组。这适用于非常大的数据集或从数据库游标流式传输的行。

是的。 AI 工具根据字段规范生成边界值、无效输入和逼真的合成记录。 Rev在将行添加到提供程序之前,请检查输出中是否存在重复项,并确认预期结果是否正确。

AI助手会标记执行相同代码路径的冗余数据行,根据共享的输入特征对故障进行聚类,并建议进行自我修复。 定位器 当应用程序标记发生变化时。

testng.xml 中的值始终以文本形式读取。 TestNG 将它们转换为声明的参数类型,因此绑定到 int 参数的非数值会失败。将 XML 值与方法签名匹配,或者接受一个字符串并对其进行解析。

总结一下这篇文章: