TestNG入门(三):依赖管理与并发测试

TestNG入门(三):依赖管理与并发测试

1. 复杂测试场景概述

在实际的自动化测试中,我们经常遇到以下复杂场景:

  • 流程依赖:后续测试步骤依赖前置步骤的状态
  • 多点验证:需要同时验证多个字段或条件
  • 并发场景:模拟多用户同时操作的场景
  • 性能要求:验证接口响应时间或系统承载能力

本篇文章将通过一个电商购物流程来学习TestNG如何处理这些复杂场景。

2. 项目实战:电商购物流程测试

2.1 被测业务类:ShopService

package org.example.proj3;

import java.util.Random;

/**
 * 被测业务类
 * 模拟登录 -- 搜索商品 -- 加入购物车 -- 支付(并发)
 */
public class ShopService {
    private int stock = 5; // 库存设置为5
    private boolean isLogin = false; // 模拟用户登录状态

    // --- 1.登录 ---
    public boolean login(String username, String password) {
        if("admin".equals(username) && "123".equals(password)) {
            System.out.println("登录成功");
            isLogin = true;
            return true;
        }

        return false;
    }

    // --- 2.搜索商品 ---
    public Product searchProduct(String name) {
        return new Product("iphone", 10.0, 5);
    }

    // --- 3.加入购物车 ---
    public boolean addToCart(String name) {
        if(!isLogin)
            return false;

        System.out.println("加入购物车成功 : " + name);
        return true;
    }

    // --- 4.支付 ---
    public boolean pay(){
        if(!isLogin) return false;

        try
        {
            int waitTime = new Random().nextInt(200);
            Thread.sleep(waitTime);

            // 简单线程安全模拟
            synchronized (this)
            {
                if(stock > 0) {
                    stock--;
                    System.out.println("💰 支付成功!剩余库存: " + stock);
                    return true;
                }
                else
                {
                    System.out.println("库存不足");
                    return false;
                }
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        return true;
    }
    /**
     * 商品类
     */
    public class Product {
        private String name;
        private double price;
        private int stock;

        public Product(String name, double price, int stock) {
            this.name = name;
            this.price = price;
            this.stock = stock;
        }

        public String getName() {
            return name;
        }

        public double getPrice() {
            return price;
        }

        public int getStock() {
            return stock;
        }
    }
}

3. 测试依赖管理

3.1 依赖测试的概念

在实际测试场景中,某些测试必须在其他测试成功执行后才能运行。例如:

  • 必须先登录成功,才能进行购物操作
  • 必须先添加商品到购物车,才能进行支付

3.2 dependsOnMethods的使用

// --- 1.测试登录 ---
@Test
public void testLogin() {
    System.out.println("【回归测试】正在测试登录功能");
    boolean result = shopService.login("admin", "123");
    Assert.assertTrue(result, "登录失败!");
}

// --- 3.测试加入购物车 ---
// 依赖于login方法

/**
 * dependsOnMethods : 依赖方法
 * 作用 : 构建依赖方法链条
 * 价值 : 依赖方法出错, 直接跳过当前测试方法, 而不是报错(Test ignored) 更节省时间
 * TODO:依赖关系在UI中更重要??依赖于前一步骤的状态
 */
@Test(dependsOnMethods = {"testLogin"})
public void testAddToCart() {
    System.out.println("【回归测试】正在测试加入购物车功能");
    boolean result = shopService.addToCart("iphone");
    Assert.assertTrue(result, "加入购物车失败!");
}

3.3 依赖执行流程

  1. 先执行testLogin()方法
  2. 如果登录成功,继续执行testAddToCart()
  3. 如果登录失败,testAddToCart()将被跳过(标记为SKIP状态,而不是FAILED)
  4. 这种机制避免了因为前置条件失败而导致后续测试误报错误

3.4 dependsOnGroups:依赖分组

除了依赖具体方法,还可以依赖整个测试分组:

@Test(groups = {"authentication"})
public void testLogin() {
    // 登录逻辑
}

@Test(groups = {"shopping"}, dependsOnGroups = {"authentication"})
public void testAddToCart() {
    // 购物车逻辑,依赖认证分组成功
}

4. 软断言(Soft Assert)

4.1 硬断言 vs 软断言

硬断言(Hard Assert)

  • 断言失败时立即停止当前测试方法
  • 后续代码不再执行
  • 适用于关键路径验证

软断言(Soft Assert)

  • 断言失败时不会停止执行
  • 收集所有断言结果,最后统一报告
  • 适用于多点验证场景

4.2 SoftAssert的使用

// --- 2.测试搜索商品 ---
// SoftAssert : 软断言
// 在类似于UI/API检测时, 需要创建SoftAssert对象, 只想在最后一次性找到所有错误(字段错误)
// 作用 : 多点检查
// 价值 : assertAll一次性找到所有错误
@Test
public void testSearchProduct() {
    System.out.println("【回归测试】正在测试搜索商品功能");
    // 创建软断言
    SoftAssert softAssert = new SoftAssert();
    ShopService.Product product = shopService.searchProduct("iphone");

    softAssert.assertEquals(product.getName(), "iphone", "搜索商品失败!");
    softAssert.assertEquals(product.getPrice(), 10.0, "搜索商品失败!");
    softAssert.assertEquals(product.getStock(), 5, "搜索商品失败!");
    // 在最后统一显示断言结果
    try
    {
        System.out.println("    -> 软断言收集完毕,准备汇报...");
        softAssert.assertAll();
    }
    catch (Exception e)
    {
        System.out.println("存在异常!");
        e.printStackTrace();
    }
    finally {
        System.out.println("    -> 软断言汇报完毕,准备退出...");
    }
}

4.3 软断言的优势

  1. 全面检查:可以验证所有字段,不会因为某个字段错误而停止
  2. 问题集中:一次性报告所有问题,便于快速定位
  3. 节省时间:避免重复执行测试来逐一发现问题

5. 并发测试与性能模拟

5.1 并发测试的概念

并发测试模拟多个用户同时访问系统的场景,用于验证:

  • 系统的并发处理能力
  • 线程安全性
  • 资源竞争情况

5.2 支付并发测试

// --- 4.测试支付 ---

/**
 * threadPoolSize : 线程池大小
 * 作用 : 模拟同一时间有8个用户并发访问
 * 价值 : 不使用Jmeter也可以完成压测(简单压测)
 *
 * timeOut : 超时时间  性能底线
 */
@Test(dependsOnMethods = {"testAddToCart"},
        invocationCount = 8, // 这个测试方法会被执行8次
        threadPoolSize = 8, // 模拟同一时间有8个用户并发访问
        timeOut = 10000 // 如果单次执行超过1000ms就报错
)
public void testPay() {
    System.out.println("【回归测试】正在测试支付功能");
    long id = Thread.currentThread().getId();
    System.out.println("当前线程ID: " + id + "正在尝试支付");
    boolean result = shopService.pay();
    Assert.assertTrue(result, "支付失败!");
}

5.3 并发参数详解

  • invocationCount:方法执行次数
  • threadPoolSize:并发线程数
  • timeOut:超时时间(毫秒)

5.4 并发测试的应用场景

  1. 秒杀场景:模拟大量用户同时抢购
  2. 接口压测:验证API的并发处理能力
  3. 数据库并发:测试数据库的锁机制
  4. 缓存击穿:模拟缓存失效时的并发访问

6. 完整的测试类

package proj3;

import org.example.proj3.ShopService;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;

public class ShopTest {
    private ShopService shopService;

    @BeforeClass(alwaysRun = true)
    public void setUp() throws Exception {
        shopService = new ShopService();
    }

    // --- 1.测试登录 ---
    @Test
    public void testLogin() {
        System.out.println("【回归测试】正在测试登录功能");
        boolean result = shopService.login("admin", "123");
        Assert.assertTrue(result, "登录失败!");
    }

    // --- 2.测试搜索商品 ---
    // SoftAssert : 软断言
    // 在类似于UI/API检测时, 需要创建SoftAssert对象, 只想在最后一次性找到所有错误(字段错误)
    // 作用 : 多点检查
    // 价值 : assertAll一次性找到所有错误
    @Test
    public void testSearchProduct() {
        System.out.println("【回归测试】正在测试搜索商品功能");
        // 创建软断言
        SoftAssert softAssert = new SoftAssert();
        ShopService.Product product = shopService.searchProduct("iphone");

        softAssert.assertEquals(product.getName(), "iphone", "搜索商品失败!");
        softAssert.assertEquals(product.getPrice(), 10.0, "搜索商品失败!");
        softAssert.assertEquals(product.getStock(), 5, "搜索商品失败!");
        // 在最后统一显示断言结果
        try
        {
            System.out.println("    -> 软断言收集完毕,准备汇报...");
            softAssert.assertAll();
        }
        catch (Exception e)
        {
            System.out.println("存在异常!");
            e.printStackTrace();
        }
        finally {
            System.out.println("    -> 软断言汇报完毕,准备退出...");
        }
    }

    // --- 3.测试加入购物车 ---
    // 依赖于login方法

    /**
     * dependsOnMethods : 依赖方法
     * 作用 : 构建依赖方法链条
     * 价值 : 依赖方法出错, 直接跳过当前测试方法, 而不是报错(Test ignored) 更节省时间
     * TODO:依赖关系在UI中更重要??依赖于前一步骤的状态
     */
    @Test(dependsOnMethods = {"testLogin"})
    public void testAddToCart() {
        System.out.println("【回归测试】正在测试加入购物车功能");
        boolean result = shopService.addToCart("iphone");
        Assert.assertTrue(result, "加入购物车失败!");
    }

    // --- 4.测试支付 ---

    /**
     * threadPoolSize : 线程池大小
     * 作用 : 模拟同一时间有8个用户并发访问
     * 价值 : 不使用Jmeter也可以完成压测(简单压测)
     *
     * timeOut : 超时时间  性能底线
     */
    @Test(dependsOnMethods = {"testAddToCart"},
            invocationCount = 8, // 这个测试方法会被执行8次
            threadPoolSize = 8, // 模拟同一时间有8个用户并发访问
            timeOut = 10000 // 如果单次执行超过1000ms就报错
    )
    public void testPay() {
        System.out.println("【回归测试】正在测试支付功能");
        long id = Thread.currentThread().getId();
        System.out.println("当前线程ID: " + id + "正在尝试支付");
        boolean result = shopService.pay();
        Assert.assertTrue(result, "支付失败!");
    }
}

7. 核心知识点总结

7.1 依赖管理

  • dependsOnMethods:依赖具体测试方法
  • dependsOnGroups:依赖测试分组
  • 执行顺序:先执行依赖项,成功后才执行当前测试
  • 失败处理:依赖失败时跳过当前测试(SKIP状态)

7.2 软断言

  • SoftAssert:收集断言结果,最后统一验证
  • assertAll():触发所有断言验证
  • 适用场景:UI元素检查、API响应验证、配置文件检查

7.3 并发测试

  • invocationCount:执行次数
  • threadPoolSize:并发线程数
  • timeOut:超时控制
  • 应用场景:性能验证、并发安全、压力测试

8. 最佳实践

  1. 依赖设计

    • 合理规划测试依赖关系
    • 避免循环依赖
    • 关键路径使用依赖,独立功能避免依赖
  2. 软断言使用

    • 多点验证场景使用软断言
    • 关键业务流程使用硬断言
    • 记得调用assertAll()
  3. 并发测试

    • 合理设置线程数和执行次数
    • 注意线程安全问题
    • 设置合适的超时时间
  4. 测试隔离

    • 尽量让测试方法相互独立
    • 依赖关系要清晰明确
    • 避免过度依赖导致测试脆弱

9. 下一步学习

掌握了依赖管理、软断言和并发测试后,下一步我们将学习:

  • 自定义监听器
  • 测试报告集成
  • 动态测试构建
  • 企业级框架设计

这些高级特性将让你能够构建完整的自动化测试框架。

下一篇文章我们将深入探讨TestNG的监听器和报告集成,敬请期待!

全部评论

相关推荐

评论
1
收藏
分享

创作者周榜

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