xunit
基于GS语言的现代化单元测试框架,参考.NET xunit框架设计理念,专为M95项目开发的通用测试解决方案。
✨ 核心特性
- 🎯 命名约定驱动: 基于函数名前缀自动识别测试类型,无需装饰器注册
- 📊 多种测试类型: 支持Fact、Theory(参数化)、Benchmark测试
- 📈 基准测试: 内置性能测试支持,包含统计分析
- 📋 多格式报告: 支持控制台、JSON、JUnit XML格式输出
- 📁 按文件分组: 控制台输出按源文件分组显示,便于查看
- 🔗 CI/CD集成: 生成JUnit XML格式,支持GitLab CI集成
- 🛠️ 测试套件管理: 支持通过
get_suite_names()
函数定义测试套件 - ⚙️ 灵活配置: 支持并行执行、超时控制等多种配置选项
🚀 快速开始
1. 基本测试示例
import src.xunit;
// 可选:定义测试套件名称列表
public array get_suite_names()
{
return ["用户管理", "产品管理", "系统工具"];
}
// 基本的事实测试
public void test_user_creation()
{
string username = "testuser";
string email = "test@example.com";
xassert.not_null(username, "用户名不能为空");
xassert.contains(email, "@", "邮箱格式不正确");
}
// 另一个事实测试
public void test_user_validation()
{
string email = "test@example.com";
xassert.success(email.contains("@"), "邮箱必须包含@符号");
xassert.success(email.contains("."), "邮箱必须包含域名");
}
// 参数化测试
public void theory_user_age_validation(int age, bool expected)
{
bool is_valid = (age > 0 && age < 120);
xassert.equal(expected, is_valid, sprintf("年龄 %d 的验证结果不匹配", age));
}
// 测试数据提供者
public array theory_user_age_validation_data()
{
return [
[ 25, true ], // 正常年龄
[ -1, false ], // 负数年龄
[ 0, false ], // 零年龄
[ 150, false ], // 超大年龄
];
}
// 基准测试
public void bench_user_search_performance()
{
// 性能测试代码
array users = generate_test_users(1000);
search_users_by_name(users, "张三");
}
// 全局前置函数(如果需要的话)
public void setup()
{
printf("初始化测试环境\n");
// 准备测试数据
}
// 全局后置函数(如果需要的话)
public void teardown()
{
printf("清理测试环境\n");
// 清理测试数据
}
// 主函数:运行测试
void create()
{
// 配置框架
xunit.configure({
"verbose": true,
"output_format": "console"
});
// 自动发现并注册测试
int count = xunit.discover_tests_by_naming_convention(this_object());
printf("发现 %d 个测试用例\n", count);
// 运行所有测试
map report = xunit.run_all_tests({});
// 输出结果
printf("测试完成 - 通过: %d, 失败: %d, 跳过: %d\n",
report["summary"]["passed"],
report["summary"]["failed"],
report["summary"]["skipped"]);
}
2. 运行测试
通过--script-args
传递命令行参数:
# 基本运行
gs.exe /r . /eq your_test_file.gs
# 详细输出
gs.exe /r . /eq your_test_file.gs --script-args "--verbose"
# 按套件过滤执行
gs.exe /r . /eq your_test_file.gs --script-args "--suite 用户管理"
# 按多个套件过滤执行
gs.exe /r . /eq your_test_file.gs --script-args "--suite 用户管理,产品管理"
# 按测试用例名称过滤
gs.exe /r . /eq your_test_file.gs --script-args "--filter test_user_creation"
# 按多个测试用例过滤
gs.exe /r . /eq your_test_file.gs --script-args "--filter test_user_creation,test_user_validation"
# 只运行基准测试
gs.exe /r . /eq your_test_file.gs --script-args "--benchmark-only"
# 组合使用:详细输出 + 套件过滤
gs.exe /r . /eq your_test_file.gs --script-args "--verbose --suite 用户管理"
# 并行执行测试
gs.exe /r . /eq your_test_file.gs --script-args "--parallel --verbose"
# 设置超时时间
gs.exe /r . /eq your_test_file.gs --script-args "--timeout 60"
# 生成XML报告(用于CI/CD)
gs.exe /r . /eq your_test_file.gs --script-args "--format xml"
# 组合性能测试:只运 行基准测试,详细输出,设置迭代次数
gs.exe /r . /eq your_test_file.gs --script-args "--benchmark-only --verbose --benchmark-iterations 1000"
📖 测试类型详解
Fact测试(事实测试)
用于验证固定逻辑的简单测试,无需参数。
命名约定: test_*
、fact_*
、should_*
public void test_user_creation()
{
string username = create_user("testuser");
xassert.equal("testuser", username, "用户名应该正确");
}
public void should_validate_email()
{
bool result = is_valid_email("test@example.com");
xassert.success(result, "有效邮箱应该通过验证");
}
Theory测试(参数化测试)
支持多组数据的参数化测试。
命名约定: theory_*
public void theory_login_validation(string username, string password, bool expected)
{
bool result = validate_login(username, password);
xassert.equal(expected, result, "登录验证结果不匹配");
}
// 数据提供者函数:测试函数名 + "_data"
public array theory_login_validation_data()
{
return [
["admin", "password123", true],
["user", "wrongpass", false],
["", "password", false],
["admin", "", false]
];
}
Benchmark测试(基准测试)
自动测量性能指标的测试类型。
命名约定: bench_*
、benchmark_*
、perf_*
public void bench_string_operations()
{
string result = "";
for (int i = 0; i < 1000; i++)
{
result += "test";
}
}
public void benchmark_array_operations()
{
// 测试数组操作性能
array arr = [];
for (int i = 0; i < 1000; i++)
{
arr.push_back(i);
}
}
// 自定义迭代次数
public void perf_iter_5000_map_operations()
{
// 这个基准测试会运行5000次迭代
map data = {};
for (int i = 0; i < 100; i++)
{
data[sprintf("key_%d", i)] = i;
}
}
// 自定义预热次数
public void bench_warmup_200_function_call()
{
// 这个基准测试会有200次预热
expensive_function_call();
}
// 同时设置迭代和预热次数
public void benchmark_iter_2000_warmup_50_sorting()
{
// 2000次迭代,50次预热
array data = generate_random_array(100);
sort_array(data);
}
设置迭代次数
在函数名中包含 _iter_数字_
标记:
public void perf_iter_5000_map_operations()
{
// 这个基准测试会运行5000次迭代
map data = {};
data["key"] = "value";
}
设置预热次数
在函数名中包含 _warmup_数字_
标记:
public void bench_warmup_200_function_call()
{
// 这个基准测试会 有200次预热
cached_function_call();
}
同时设置迭代和预热次数
public void benchmark_iter_2000_warmup_50_sorting()
{
// 2000次迭代,50次预热
array data = [3, 1, 4, 1, 5, 9, 2, 6];
data.sort();
}
🔧 测试元数据
通过在函数名中添加特殊标记,可以设置测试的元数据属性:
跳过测试
在函数名中包含 _skip_
标记:
public void test_skip_incomplete_feature()
{
// 这个测试会被自动跳过
}
设置超时时间
在函数名中包含 _timeout_数字_
标记:
public void test_timeout_30_slow_operation()
{
// 这个测试有30秒的超时时间
slow_database_operation();
}
🔧 测试套件
测试套件通过get_suite_names()
函数来定义:
// 定义测试套件名称列表(可选)
public array get_suite_names()
{
return ["用户管理", "产品管理", "系统工具"];
}
框架支持全局的前置后置函数:
// 全局前置函数
public void setup()
{
// 测试前的准备工作
}
// 全局后置函数
public void teardown()
{
// 测试后的清理工作
}
⚙️ 配置选项
xunit.configure({
// 基础设置
"parallel": false, // 是否并行执行
"timeout": 30, // 超时时间(秒)
"verbose": false, // 详细输出
"output_format": "console", // 输出格式:console/json/xml
"base_path": ".", // 基础路径
// 测试发现设置
"auto_discover": true, // 自动发现测试
"test_patterns": [ // 测试文件匹配模式
"**/*Test.gs",
"**/*_test.gs",
"**/test_*.gs"
],
"fact_prefixes": [ // Fact测试前缀
"test_", "fact_", "should_"
],
"theory_prefixes": ["theory_"], // Theory测试前缀
"benchmark_prefixes": [ // Benchmark测试前缀
"bench_", "benchmark_", "perf_"
],
// 基准测试设置
"benchmark_iterations": 100, // 默 认迭代次数
"benchmark_warmup": 0, // 默认预热次数
// 报告输出配置
"xml_output_file": "test-results.xml", // JUnit XML输出文件
"json_output_file": "test-results.json", // JSON输出文件
"console_group_by_file": true, // 控制台按文件分组显示
"show_file_paths": true, // 显示文件路径信息
});