单元测试并行运行实践
1 问题提出
项目现在有775个用例,160个测试用例程序,ZCIP通过脚本UnitTest.sh执行用例程序,并获取用例统计信息。整个流程执行大概8分钟左右,在单元测试运行环境花费204秒,约分钟,将近50%。在后续测试不用不断增加下,这个比例会不断增高。
实现一个UnitTestNew.sh可以并行执行用例,值得研究,以下记录改造中解决的问题。
2 执行脚本重构
参考UnitTest.sh重新实现一个UnitTestNew.sh。
2.1子shell并行
将串行脚本并行化的方法比较,本次改造使用子shell,即() & wait的组合。示例代码如下:
for cur_gtest in $gtest_list
do
echo "running ${cur_gtest} ..."
logfile=../log/${cur_gtest}_${cur_time}.log
(${cur_gtest} >${logfile}
if [ $? -gt 1 ]
then
echo "Run ${cur_gtest} Error!|${result_file_list}|1" 2>&1 | tee $1
exit 0
fi)&
done
wait
2.2并发控制
为了防止所有程序一并执行,可以采用管道通信等方式进行进程数控制。这里给出一种简单实现,每执行10个用例,sleep 1s。来缓解并发数。(最终实现后,发现这一步其实没有必要,不控制也正常,当时出现问题是其他问题引起)
2.3统计信息问题
UnitTest.sh每执行完一个用例,就去分析日志,来获取用例执行结果打印出来。并累计全量数据,输出用例执行情况。
这种方式不适用于并行执行,在不同子进程执行后,统计系统是无法累计的。
实现思路,在脚本执行时,不再统计分析日志,在脚本执行结束后,分析全部日志并获取统计日志。
2.4其他改进
- 扫描目录获取用例程序,获取具有可执行权限的文件。
gtest_list="find . -type f -perm 775 -print
" - 启动脚本是清理目录日志,防止日志污染。
3用例中的典型问题
3.1串行并行的问题
完成脚本测试时,发现早期用例余额数据存在冲突的情况,早期基于RuleCache的数据需要取最新版本等情况,导致部分用例只能串行执行。
而后期改造基于新框架的用例由于运行指定版本,互不干扰,可以并行执行。
考虑用例命名差异,用例以utest_打头时,并行执行,否则就串行执行。
3.2新测试框架用例并行执行冲突问题
新测试框架用例在执行时会自己创建共享内存版本,执行用例,最后删除版本。
共享内存版本的Create,Destory接口存在临界点问题,并行执行会冲突,使用文件锁进行控制。
示例代码:
int fd=-1;
if ((fd = open("../file_lock.test", O_RDWR|O_CREAT,0666)) == -1) //打开文件
{
cout<<"file open error!\n"<<errno<<endl;
return false;
}
int iret=lockf(fd, F_LOCK,0); //文件加锁
if(iret==-1)
{
cout<<"flock error!\n"<<errno<<endl;
return false;
}
......
close(fd); //关闭文件
3.3用例创建RuleCache版本数问题
测试框架中gtest_RuleCache参考RuleCache应用的实现,包含了创建版本数的控制,在并行执行时,可能会突破这个数目,创建失败。因此取消gtest_RuleCache中对版本数控制。RuleCache的代码不受影响。
4测试结果
用time执行UnitTestNew.sh执行结果:
Pass(775)--Total(775)--Percentage(100.00%)<font color="#FF0000"></font>||0
real 1m16.967s
user 2m39.032s
sys 0m15.402s
相比较串行204秒有50%以上的提升,考虑后续用例基于新框架,这种优势会更加明显。
5小结
由于业务的复杂性,实现UnitTestNew.sh无法通用性的解决并行的问题。
但是通过规则给出用例并行执行思路,供参考。以上。