TestNG入门(二):数据驱动与分组测试策略

TestNG入门(二):数据驱动与分组测试策略

1. 数据驱动测试概述

数据驱动测试(Data-Driven Testing)是自动化测试的核心理念之一。它的核心思想是将测试数据测试逻辑分离,使得同一套测试代码可以验证多组不同的测试数据。

1.1 为什么需要数据驱动?

  • 提高测试覆盖率:一个测试方法可以验证多组数据
  • 减少代码重复:避免为相似场景编写多个测试方法
  • 便于维护:测试数据变更时无需修改测试代码
  • 支持外部数据源:可以从数据库、Excel、CSV等外部文件读取数据

2. 项目实战:用户登录系统测试

在这个项目中,我们将模拟一个用户登录系统,学习如何通过数据驱动的方式测试不同的登录场景。

2.1 被测业务类:LoginService

package org.example.proj2;

public class LoginService {

    // 模拟登录逻辑
    public boolean login(String username, String password) {
        if("admin".equals(username) && "123456".equals(password)) {
            return true;
        }

        return false;
    }
}

2.2 数据库工具类:DBUtils

为了演示真实场景,我们创建了一个数据库工具类来模拟从数据库读取测试数据:

package org.example.proj2.utils;

import cn.hutool.db.Db;
import cn.hutool.db.Entity;

import java.sql.SQLException;
import java.util.List;

public class DBUtils {

    public static Object[][] getLoginData() {
        try {
            // 1. 一行代码查询所有数据
            // Hutool 会自动读取 db.setting 并连接数据库
            // findAll("表名") 返回的是 List<Entity>,Entity 本质上是一个 Map
            List<Entity> entityList = Db.use().findAll("login_data");

            // 2. 将 List<Entity> 转换为 TestNG 需要的 Object[][]
            Object[][] data = new Object[entityList.size()][3];

            for (int i = 0; i < entityList.size(); i++) {
                Entity entity = entityList.get(i);
                data[i][0] = entity.getStr("username");
                data[i][1] = entity.getStr("password");
                // 数据库里的 1/0 自动转 boolean
                data[i][2] = entity.getBool("expected_result");
            }

            return data;

        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("数据库读取失败");
        }
    }
}

3. 参数化测试详解

TestNG提供了两种主要的参数化方式:@Parameters@DataProvider

3.1 @Parameters:静态参数注入

@Parameters用于从testng.xml文件中注入静态配置参数:

// --- 1.参数注入 ---
// 从testng.xml中获取配置
// Parameters 参数化的核心  从xml中读取  比如将dbUrl改为测试环境地址, 就能直接运行
@Parameters({"testEnv"})  // 传入不同的值, 就可以在不同的环境中测试!
@BeforeClass(alwaysRun = true)  // 保证在分组情况下也能运行
public void setUp(String env) throws Exception {
       loginService = new LoginService();

       System.out.println("【初始化】当前环境是: " + env);
}

对应的testng.xml配置:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" >

<suite name="电商项目测试套件">
<!-- 全局参数定义:这里的值会被注入到 @Parameters 注解中 -->
    <parameter name="testEnv" value="http://testEnv.com" />
    <parameter name="prodEnv" value="http://prodEnv.com" />

    <test name="登录模块测试">
        <!-- 分组控制(打标签):尝试取消注释下面的代码块来看看效果 -->
        <groups>
            <run>
                <include name="smoke"/>
                <include name="regression"/>
            </run>
        </groups>

        <classes>
            <!-- 指定要运行的测试类 -->
            <class name="proj2.testLoginService" />
        </classes>
    </test>

</suite>

3.2 @DataProvider:动态数据驱动

@DataProvider是TestNG数据驱动测试的核心,它可以从各种数据源提供测试数据:

// --- 2.绑定数据 ---
// 数据驱动层 : 将测试数据和测试代码分离  可以从数据库/excel中读取数据  参数name只是一个标识
@DataProvider(name = "testData")
public Object[][] createData() {
    System.out.println("【数据驱动】正在从 MySQL 数据库加载测试数据...");
    // 调用工具类获取数据
    return DBUtils.getLoginData();
}

3.3 绑定DataProvider的测试方法

// --- 3.绑定数据的测试方法 ---
// 这里的dataProvider要和上面保持一致  在xml中配置分组
// groups : 分给测试打标签, 灵活选择要执行的分组(smoke, regression, p0...)
@Test(dataProvider = "testData", groups = {"regression"})
public void testLogin(String username, String password, boolean expectedResult) {
    System.out.println(
            "【回归测试】正在测试登录功能,username: " + username + ", password: " + password
    );
    boolean result = loginService.login(username, password);
    Assert.assertEquals(result, expectedResult);
}

4. 测试分组策略

TestNG的分组功能允许我们为测试方法打标签,然后根据需要选择性地执行特定的测试组。

4.1 定义测试分组

// --- 4.冒烟测试 ---
@Test(groups = {"smoke"})
public void testHomepage() {
    System.out.println("【冒烟测试】正在冒烟测试登录功能");
}

4.2 分组的作用

  • 冒烟测试(Smoke Test):验证系统的核心功能是否正常工作
  • 回归测试(Regression Test):验证现有功能没有被新代码破坏
  • P0测试:最关键的测试用例
  • 集成测试:验证模块间的集成

4.3 分组执行控制

通过testng.xml可以灵活控制执行哪些分组:

<groups>
    <run>
        <include name="smoke"/>      <!-- 只执行冒烟测试 -->
        <exclude name="regression"/> <!-- 排除回归测试 -->
    </run>
</groups>

5. DataProvider高级用法

5.1 内联DataProvider数据

除了从数据库读取,DataProvider也可以直接返回内联数据:

@DataProvider(name = "loginTestData")
public Object[][] provideLoginData() {
    return new Object[][] {
        {"admin", "123456", true},    // 正确用户名密码
        {"user", "wrongpass", false},   // 错误密码
        {null, null, false},            // 空用户名密码
        {"admin", null, false},         // 空密码
        {null, "123456", false}         // 空用户名
    };
}

5.2 多参数DataProvider

DataProvider可以返回任意数量和类型的参数:

@DataProvider(name = "userProfileData")
public Object[][] provideUserProfileData() {
    return new Object[][] {
        {"张三", 25, "男", "北京市", true},
        {"李四", 30, "女", "上海市", true},
        {"王五", 17, "男", "广州市", false}, // 未成年
        {"", 22, "女", "深圳市", false}       // 空姓名
    };
}

@Test(dataProvider = "userProfileData")
public void testUserProfile(String name, int age, String gender, String city, boolean expectedValid) {
    // 测试逻辑
}

6. 完整测试类示例

package proj2;

import org.example.proj2.LoginService;
import org.example.proj2.utils.DBUtils;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

/**
 * 参数化测试核心注解:
 *  @Parameters() : 从testng.xml中获取配置, 适用于"动态化配置", 比如prod和test环境的切换
 *  @DataProvider() : 一般是从外部数据源获取, 比如从数据库/excel中读取数据, 返回Object[][]
 *  理解数据驱动测试, 参数化测试
 */

public class testLoginService {

    private LoginService loginService;

    // --- 1.参数注入 ---
    // 从testng.xml中获取配置
    // Parameters 参数化的核心  从xml中读取  比如将dbUrl改为测试环境地址, 就能直接运行
    @Parameters({"testEnv"})  // 传入不同的值, 就可以在不同的环境中测试!
    @BeforeClass(alwaysRun = true)  // 保证在分组情况下也能运行
    public void setUp(String env) throws Exception {
           loginService = new LoginService();

           System.out.println("【初始化】当前环境是: " + env);
    }

    // --- 2.绑定数据 ---
    // 数据驱动层 : 将测试数据和测试代码分离  可以从数据库/excel中读取数据  参数name只是一个标识
    @DataProvider(name = "testData")
    public Object[][] createData() {
        System.out.println("【数据驱动】正在从 MySQL 数据库加载测试数据...");
        // 调用工具类获取数据
        return DBUtils.getLoginData();
    }

    // --- 3.绑定数据的测试方法 ---
    // 这里的dataProvider要和上面保持一致  在xml中配置分组
    // groups : 分给测试打标签, 灵活选择要执行的分组(smoke, regression, p0...)
    @Test(dataProvider = "testData", groups = {"regression"})
    public void testLogin(String username, String password, boolean expectedResult) {
        System.out.println(
                "【回归测试】正在测试登录功能,username: " + username + ", password: " + password
        );
        boolean result = loginService.login(username, password);
        Assert.assertEquals(result, expectedResult);
    }

    // --- 4.冒烟测试 ---
    @Test(groups = {"smoke"})
    public void testHomepage() {
        System.out.println("【冒烟测试】正在冒烟测试登录功能");
    }

}

7. 核心知识点总结

7.1 testng.xml配置

  • 套件(Suite):最高级别的容器,可以包含多个测试
  • 测试(Test):包含一个或多个测试类
  • 类(Class):具体的测试类
  • 参数(Parameter):全局配置参数

7.2 @Parameters vs @DataProvider

特性 @Parameters @DataProvider
数据来源 testng.xml文件 Java方法返回
数据类型 字符串 任意类型
使用场景 环境配置、全局设置 测试数据、业务数据
灵活性 较低,静态配置 很高,动态生成

7.3 分组策略优势

  • 灵活执行:可以选择只运行特定分组的测试
  • 分类管理:将测试用例按照重要性和类型分类
  • CI/CD集成:在不同阶段运行不同的测试组

8. 最佳实践

  1. DataProvider命名

    • 使用有意义的DataProvider名称
    • 避免使用默认名称,便于维护
  2. 数据源选择

    • 简单数据:直接在代码中定义
    • 复杂数据:从外部文件(Excel、CSV、JSON)读取
    • 动态数据:从数据库实时获取
  3. 分组策略

    • 遵循业界标准命名(smoke、regression、integration)
    • 避免过度细分,保持分组简洁明了
  4. 参数化配置

    • 将环境相关的配置放入testng.xml
    • 将测试数据放入DataProvider

9. 下一步学习

掌握了数据驱动和分组测试后,下一步我们将学习:

  • 测试依赖管理
  • 软断言与硬断言
  • 并发测试与性能模拟
  • 超时控制机制

这些高级特性将让你能够处理更复杂的测试场景。

下一篇文章我们将深入探讨TestNG的依赖管理和并发测试,敬请期待!

#测试开发学习路线##简历中的项目经历要怎么写#
全部评论

相关推荐

02-07 12:06
已编辑
华侨大学 测试开发
最近看到很多&nbsp;92&nbsp;的,甚至是硕士,开始往测开赛道卷,说实话有点看不懂。先把话说清楚,大厂里的测开,绝大多数时间干的还是测试的活,只是写点自动化脚本、维护测试平台、接接流水线,真正像开发一样做系统、做架构、做核心平台的测开少得可怜,基本都集中在核心提效组,而且人很少,外面进去的大概率轮不到你,我想真正干过人都清楚。很多人被洗脑了,以为测开也是开,和后端差不多,只是更简单、更轻松、还高薪。现实情况是,测开和开发的职业路径完全不一样。开发的核心是业务和系统能力,测开的核心是稳定性和覆盖率,前者是往上走,后者天花板非常明显。你可以见到很多开发转测开,但你很少见到干了几年测开还能顺利转回开发的。更现实一点说,92&nbsp;的高学历如果拿来做测开,大部分时间就是在做重复性很强的杂活,这种工作对个人能力的放大效应非常弱。三年下来,你和一个双非的,甚至本科的测开差距不会太大,但你和同龄的后端、平台开发差距会非常明显。这不是努不努力的问题,是赛道问题。所谓测开简单高薪,本质上是把极少数核心测开的上限,当成了整个岗位的常态来宣传。那些工资高、技术强的测开,本身就是开发水平,只是挂了个测开的名。普通人进去,99%&nbsp;做的都是项目兜底型工作,而不是你想象中的平台开发。测开不是不能做,但它绝对不是开发的平替,也不是性价比最优解。如果你是真的不想做开发,追求稳定,那测开没问题。但如果你只是觉得测开比后端容易,还能进大厂,那我劝你冷静一点,这只是在用短期安全感换长期天花板。有92的学历,如果你连测开这些重复性工作都能心甘情愿接受,那你把时间精力用在真正的开发、系统、业务深度上,回报大概率比卷测开要高得多。想清楚再下场,别被岗位名和话术带偏了,就算去个前端客户端也是随便占坑的,测开是一个坑位很少赛道,反而大面积学历下放,不用想也能知道会是什么结果,我想各位在JAVA那里已经看到了
小浪_Coding:工作只是谋生的手段 而不是相互比较和歧视
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务