Ohhnews

分类导航

$ cd ..
DZone Java原文

使用 REST Assured 和 TestNG 实现 Java 数据驱动 API 测试(三)

#api测试#java#数据驱动测试#testng#自动化测试

数据驱动测试允许测试人员使用多组输入数据执行相同的测试逻辑,从而以最小的投入提高测试覆盖率和可靠性。通过将 CSV 文件与 TestNG 的 @DataProvider 注解相结合,可以将测试数据与测试逻辑轻松分离。这种方法增强了可维护性,并使测试自动化更具扩展性和灵活性。本文将以清晰、实用且易于遵循的方式介绍如何使用 CSV 文件和 TestNG 实现数据驱动测试。

使用 TestNG 的 @DataProvider 注解和 CSV 文件进行数据驱动测试

设置和配置与之前讨论的教程相同。此外,需要在 pom.xml 中添加以下 Jackson-dataformat-csv 依赖项以处理 CSV 文件:

$ xml
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-csv</artifactId>
    <version>2.21.0</version>
</dependency>

本演示将使用 RESTful 电子商务 演示应用程序的 POST /addOrder API。该 API 的模式如下所示:

$ cat
[
  {
    "user_id": "string",
    "product_id": "string",
    "product_name": "string",
    "product_amount": 0,
    "qty": 0,
    "tax_amt": 0,
    "total_amt": 0
  }
]

[LOADING...]

创建 POJO 类

应创建以下 POJO 类,将 CSV 文件数据映射到数据提供程序:

$ java
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    @JsonProperty("user_id")
    private String userId;
    @JsonProperty("product_id")
    private String productId;
    @JsonProperty("product_name")
    private String productName;
    @JsonProperty("product_amount")
    private int productAmount;
    private int qty;
    @JsonProperty("tax_amt")
    private int taxAmt;
    @JsonProperty("total_amt")
    private int totalAmt;
}

代码解析

Order POJO 通过将每一行映射为一个对象,并将行中的每一列映射到相应的类变量,从而简化了读取 CSV 文件的过程。使用 POJO 有助于消除硬编码的映射逻辑,允许 CSV 解析库根据注解或命名约定自动将列名绑定到类字段。POJO 不再需要手动按列索引提取值(例如 row[0]row[1] 等),而是将每个 CSV 列直接映射到相应的属性。

@Getter@Setter 注解会在编译期间自动为所有字段创建 getter 和 setter 方法。@AllArgsConstructor 注解支持快速创建完全初始化的对象,这在手动创建测试数据时非常有用。@NoArgsConstructor 注解生成一个无参数的默认构造函数。如果没有它,库在进行 CSV 映射时可能无法创建 POJO 实例。在这个 Order 类中,@NoArgsConstructor 确保了从 CSV 文件读取数据时能够自动创建对象。

如果您想深入了解如何使用 Java 中的 REST Assured 测试 POST 请求,请参阅此详细指南

创建读取 CSV 文件的工具类

我们将使用以下 CSV 文件执行数据驱动测试:

$ csv
user_id,product_id,product_name,product_amount,qty,tax_amt,total_amt
"U001","P1001","Laptop",850,1,42,892
"U002","P1002","Smartphone",600,2,60,1260
"U003","P1003","Headphones",120,3,18,378
"U004","P1004","Keyboard",75,2,7,157
"U005","P1005","Mouse",40,4,8,168
"U006","P1006","Monitor",300,1,15,315
"U007","P1007","Tablet",450,2,45,945
"U008","P1008","Printer",200,1,10,210
"U009","P1009","Webcam",90,3,13,283
"U010","P1010","Speaker",150,2,15,315

让我们创建一个工具方法,用于从 src/test/resources/ 文件夹中读取 CSV 文件。

$ java
public class CSVReader {
    @SneakyThrows
    public static List<Order> getOrderData(String filename) {
        InputStream inputStream = CSVReader.class.getClassLoader().getResourceAsStream(filename);
        if (inputStream == null) {
            throw new RuntimeException("File not found: " + filename);
        }
        CsvSchema schema = CsvSchema.emptySchema().withHeader();
        MappingIterator<Order> iterator;
        try {
            iterator = new CsvMapper().readerFor(Order.class)
                    .with(schema)
                    .readValues(inputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return iterator.readAll();
    }
}

代码解析

getOrderData() 方法是静态的,它读取 CSV 文件并返回一个 Order 对象列表。以下步骤解释了 CSV 文件读取过程:

  • 加载 CSV 文件:使用类加载器从资源文件夹 (src/test/resources/) 中加载 CSV 文件。如果找不到文件,则抛出 RuntimeException
$ java
CsvSchema schema = CsvSchema.emptySchema().withHeader();
  • 定义 CSV 模式:上述代码语句为 Jackson 的解析器定义了 CSV 模式。emptySchema().withHeader() 告诉 Jackson 使用第一行作为列标题,以便将它们映射到 Order POJO 中的字段。
$ java
MappingIterator<Order> iterator;
try {
    iterator = new CsvMapper().readerFor(Order.class)
            .with(schema)
            .readValues(inputStream);
} catch (IOException e) {
    throw new RuntimeException(e);
}
return iterator.readAll();
  • 读取 CSV 文件:使用 Jackson-dataformat-csv 库中的 CsvMapper 读取 CSV 文件。它使用 readerFor(Order.class).with(schema) 代码将每一行映射到 Order 对象。readValues(inputStream) 代码创建了一个 MappingIterator,允许迭代 CSV 的所有行。如果在读取文件时发生任何 I/O 错误,它会被捕获并重新抛出为 RuntimeException,以简化错误处理。最后,迭代器读取所有剩余行并将其作为 List 返回。

创建 DataProvider 方法

以下数据提供程序方法将 CSV 文件中的测试数据作为 Iterator<Object[]> 返回,供测试使用。

$ java
@DataProvider(name = "orderData")
public Iterator<Object[]> getOrders() {
    List<Order> orderList = CSVReader.getOrderData("order_data.csv");
    return orderList.stream()
            .map(order -> new Object[] { order })
            .iterator();
}

代码解析

getOrders() 方法从 "order_data.csv" 文件中读取测试数据。它使用 CSVReader 工具将所有记录加载为 Order 对象列表。stream 操作将每个 Order 转换为 Object[] 格式,然后返回一个 Iterator,允许 TestNG 高效地使用不同的输入数据多次运行同一个测试。

编写 API 自动化测试

让我们为 POST /addOrder API 编写测试,该 API 使用数据提供程序从 CSV 文件提供的测试数据来创建订单:

$ java
@Test(dataProvider = "orderData")
public void testCreateOrder(Order order) {
    List<Order> orderList = List.of(order);
    given()
        .contentType(ContentType.JSON)
    .when()
        .log().all()
        .body(orderList)
        .post("http://localhost:3004/addOrder")
    .then()
        .log().all()
        .statusCode(201)
        .and()
        .assertThat()
        .body("message", equalTo("Orders added successfully!"));
}

testCreateOrder() 方法使用 orderData DataProvider 多次运行同一个测试,每次都使用来自 CSV 文件的不同 Order 对象。在执行 POST 请求之前,每个订单都会使用 List.of(order) 包装在 List 中,因为 POST /addOrder API 需要请求体中包含订单列表。测试随后通过检查状态码是否为 201 并确认返回了成功消息 "Orders added successfully" 来验证响应。

查看此详细教程:如何在 Java REST-Assured 中执行 API 测试的响应验证

测试执行

执行测试时,TestNG 会自动多次运行 testCreateOrder() 方法,每次使用通过 orderData DataProvider 从 CSV 文件中读取的不同测试数据。

[LOADING...]

总结

将 CSV 文件与 TestNG 的 DataProvider 结合使用,通过将测试数据与测试逻辑分离,提供了一种实现数据驱动测试的简单有效的方法。这种方法使测试用例更易于维护,提高了可读性,并允许测试人员在不修改代码的情况下更新或扩展测试场景。祝测试愉快!