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 依赖执行流程
- 先执行
testLogin()方法 - 如果登录成功,继续执行
testAddToCart() - 如果登录失败,
testAddToCart()将被跳过(标记为SKIP状态,而不是FAILED) - 这种机制避免了因为前置条件失败而导致后续测试误报错误
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 软断言的优势
- 全面检查:可以验证所有字段,不会因为某个字段错误而停止
- 问题集中:一次性报告所有问题,便于快速定位
- 节省时间:避免重复执行测试来逐一发现问题
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 并发测试的应用场景
- 秒杀场景:模拟大量用户同时抢购
- 接口压测:验证API的并发处理能力
- 数据库并发:测试数据库的锁机制
- 缓存击穿:模拟缓存失效时的并发访问
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. 最佳实践
-
依赖设计:
- 合理规划测试依赖关系
- 避免循环依赖
- 关键路径使用依赖,独立功能避免依赖
-
软断言使用:
- 多点验证场景使用软断言
- 关键业务流程使用硬断言
- 记得调用
assertAll()
-
并发测试:
- 合理设置线程数和执行次数
- 注意线程安全问题
- 设置合适的超时时间
-
测试隔离:
- 尽量让测试方法相互独立
- 依赖关系要清晰明确
- 避免过度依赖导致测试脆弱
9. 下一步学习
掌握了依赖管理、软断言和并发测试后,下一步我们将学习:
- 自定义监听器
- 测试报告集成
- 动态测试构建
- 企业级框架设计
这些高级特性将让你能够构建完整的自动化测试框架。
下一篇文章我们将深入探讨TestNG的监听器和报告集成,敬请期待!
三奇智元机器人科技有限公司公司福利 131人发布