QC API全系列揭秘之Test Execution操作
liebian365 2024-10-19 07:56 22 浏览 0 评论
以下内容来自公众号:诗泽园
QC简介
Quality Center存在至今已经走过了10多个年头,名字从一开始的TD,到后来的QC,再到现在的ALM。所属公司从开始的Mercury到现在的HP,核心一直没变,变的只有名字。随着Mercury最核心的高层、架构师和专家的离开,现在每每的升级都带来诸多失望,再也没有当初使用Mercury工具的时候那样心潮澎湃,看看QC,看看QTP,不多言语。如果能够坚持做好的话,现在哪有TestLink、哪有禅道什么事。然而,QC框架的设计核心,拿到现在来看,依然是测试管理框架的主流。QC设计思路简单清晰,从测试需求到测试用例,再到执行测试用例、提交缺陷、跟踪缺陷,整个过程清晰且易于理解,时至今日,依然被广泛沿用。(微信公众号“诗泽园”)
写作目的
写此系列的目的,不是为了情怀,而是为了将QC接口的调用方式整理成册。一来是为了通过对QC接口调用的理解,去更深入的理解测试框架的概念(当然仅仅包含小部分);二来是为了通过二次开发,解决QC使用过程中的诸多不便。
解决问题
QC当前存在以下问题:
1、需求或用例的导入导出依然不完善。之前就写过一个工具解决这个问题:https://download.csdn.net/download/yoyoalphax/4441588,但近期依然常有人催促我更新版本,之后会专门发一篇关于用例树解析的图文。(网上资料大部分需要admin账户通过后台SQL做关键字筛选获得结果,但事实上与实际的导入导出过程有所出入。)
2、用例执行顺序需要参考已有用例集,无法随心所欲变化。
3、每次执行的用例集列表无法保存并复用。
4、手动执行前需要处理解锁等额外步骤。
5、测试结果随有统一展示,但需要人工收集结果等等。
本文重点
本文着重介绍Test Execution部分,解决了以上罗列的后几个问题,并为某些问题提供解决的条件。其余需求树、用例树读取及写入部分以后再介绍。
Test Execution属于自动化测试框架的一部分,我先从框架说起。
测试框架
测试框架的话题,范围实在是太大了,我们围绕QC来说。我们仅仅考虑从测试用例的编写、测试用例的执行和测试报告来看。用过的同学都清楚,TestPlan里可以存放测试用例,而TestLab里可以建TestSet并形成用例集并且执行,Report里查看用例,这就是基本流程。而对于自动化测试的流程而言,用QC的方式又有所区别:首先,是测试工具的关联。QC需要安装QTP或LR的插件,使得QC的测试执行模块里可以识别这两类代码。又或者你用的是其他第三方工具或用JAVA和.Net自开发的测试工具,那你需要用VB6编写关联脚本,使得QC能够调用你写的代码,这个过程我们不在本文中讨论。其次,是测试脚本的存储。以用例的形式存储在TestPlan里,最终落到QC的后台SQL数据库里,并能实现脚本与数据的分开存储。再者是测试用例集的构成,这部分是放在TestLab里去管理。按照业务逻辑,将已有用例归集并排序,形成业务逻辑并保存。最后是测试执行,按照被测系统版本、范围,选择相应的业务节点去触发执行,获得结果。
这个过程其实分两个阶段,一个是测试开发阶段,另一个是测试执行阶段,两个阶段各有各的自动化设计方面的考虑。这个不是本文的主旨,但是我也顺便捋一下,加深理解。测试开发阶段,其实是要设计出狭义的测试框架,也就是一个可以团队合作开发的测试脚本的模式,包含底层库、业务的封装、上层调用及断言等等。有了狭义的框架后,需要有偏业务的测试人员介入,将测试脚本归集形成测试集。在测试开发阶段,往往这两块是一同进行的,边改边拼接。测试Q执行阶段,其实是要有测试执行框架去支撑的,尤其是有大批量的测试脚本和测试机需要团队去匹配执行时,这个框架就显得尤为重要。其中涉及到的关键点,如待测范围的选择及保存、测试机的管理(vmware or docker)、用例执行时的动态分配、异常处理、报告收集等等。
本文所涉及的内容应该是通过QC实现自动化测试脚本运行的前提下,实现测试范围的选择与保存,测试脚本的自动化执行并做后续的报告收集工作。
QC接口规范
具体的接口规范你可以尝试通过百度查询“QC OTA”或者“QC对象模型”,获得接口说明及使用规范。但以下的核心代码均是本人键盘手打敲击而成,尤其是核心的业务树生成及测试执行部分,均为首次发布。还望转载或代码复用时注明出处。(出自微信公众号“诗泽园”或博客园“朝花夕拾”-https://www.cnblogs.com/alphaxu/)
QC接口操作Test Execution
下面正式切入正题:
定义全局变量
真实代码中有很多控制类及展示类,都已经去除了。这里只展示核心代码。
TDConnection tdc = new TDConnection();
TDAPIOLELib.TSScheduler QCscheduler;
//用于最终真实启动用例及监测执行结果
TDAPIOLELib.ExecutionStatus QCexecutionStatus;
//用于反馈执行结果及做结果的动态刷新
List QClistForTSTest;
QC服务器连接、登录及项目连接
服务器连接为初始化连接,好比你刚登录QC终端时它给你的反馈。一般会碰到要你reload ActiveX或者OTA初始化失败之类的错误。处理方法是把QC缓存文件夹删除,再访问,让其重新reload。这块代码里会有处理,但这类代码就不贴了。
//初始化连接
private void InitConnect(string serverName)
{
try
{
if ((tdc.Connected == false) || (tdc.Connected == true && tdc.ServerName != serverName))
tdc.InitConnectionEx(serverName);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
MessageBox.Show("Server Error", "Warning");
}
}
//身份验证
private void GetAuthenticate(string userName, string passWord)
{
try
{
tdc.Login(userName, passWord);
TDAPIOLELib.List projectList = tdc.get_VisibleProjects(tdc.VisibleDomains[1].ToString());
//VisibleDomains[1]为项目列表中的第一个Domain,多的话自行处理
for (int i = 1; i <= projectList.Count; i++)
{
projectsBox.Items.Add(projectList[i]);
//将Domain下所有项目读取出来,以备后用
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
MessageBox.Show("Please check the User Name is correct or not.", "Warning");
}
}
//项目连接
private void LoginButton_Click(object sender, EventArgs e)
{
try
{
string ProjectName = projectsBox.SelectedItem.ToString();
tdc.Connect(tdc.VisibleDomains[1].ToString(), ProjectName);
//调用生成业务树代码,也可以不在此处调用
Thread td_tree = new Thread(new ThreadStart(this.CreateTreeView));
td_tree.Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
MessageBox.Show("Please choose the project.", "Warning");
}
}
重点之一:递归生成业务树
同样需要新开线程调用,先生成根节点,再递归生成业务树
//生成根节点并调用递归树
private void CreateTreeView()
{
try
{
TreeNode mainNode = new TreeNode();
mainNode.Name = "Root";
mainNode.Text = "Root";
Add_TreeRoot(mainNode);
SysTreeNode test_folder;
TestSetFactory globalTestSetFactory;
List l_List;
TreeNode r_node = new TreeNode();
int nodeCount;
nodeCount = qcProjectTree.GetNodeCount(true);
TreeNode[] r_nodeArray = new TreeNode[10000];
r_nodeArray = qcProjectTree.Nodes.Find("Root", false);
r_node = r_nodeArray[0];
TestSetTreeManager tm;
tm = (TDAPIOLELib.TestSetTreeManager)tdc.TestSetTreeManager;
test_folder = (TDAPIOLELib.SysTreeNode)tm.Root;
globalTestSetFactory = (TDAPIOLELib.TestSetFactory)tdc.TestSetFactory;
l_List = globalTestSetFactory.NewList("");
recursiveTreeBuilder(test_folder, r_node);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
//递归生成业务树
private bool recursiveTreeBuilder(TDAPIOLELib.SysTreeNode folder, TreeNode parent)
{
TDAPIOLELib.List folders, tests;
TDAPIOLELib.TestSetFactory objTestSetFactory;
TDAPIOLELib.TestSetFolder objTSFolder;
TreeNode n;
try
{
folders = folder.NewList();
foreach (TDAPIOLELib.SysTreeNode f in folders)
{
TreeNode nodeChild = new TreeNode();
nodeChild.Name = f.Name;
nodeChild.Text = f.Name;
nodeChild.ImageIndex = 0;
Add_TreeNode(parent, nodeChild);
n = parent.Nodes[nodeChild.Name];
recursiveTreeBuilder(f, n);
}
objTSFolder = (TDAPIOLELib.TestSetFolder)folder;
if (objTSFolder.NodeID != 0)
{
objTestSetFactory = (TDAPIOLELib.TestSetFactory)objTSFolder.TestSetFactory;
tests = objTestSetFactory.NewList("");
foreach (TDAPIOLELib.TestSet testSet in tests)
{
TreeNode nodeChild1 = new TreeNode();
nodeChild1.Name = testSet.ID.ToString();
nodeChild1.Text = testSet.Name;
nodeChild1.ImageIndex = 1;
Add_TreeNode(parent, nodeChild1);
n = parent.Nodes[nodeChild1.Name];
n.Tag = objTSFolder.Path + @"\" + testSet.Name;
}
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return false;
}
}
以下代码用委托的方式生成节点,保证在业务树生成过程中可随时点击并保证界面不出现假死(跟业务树生成无直接关系,可忽略)
delegate void Add_Node(TreeNode parent, TreeNode node);
private void Add_TreeNode(TreeNode parent, TreeNode node)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new Add_Node(Add_TreeNode), parent, node);
}
else
{
parent.Nodes.Add(node);
}
Thread.Sleep(10);
}
生成业务树后,由用户通过业务树选择需要运行的节点,形成待测试列表,就是后续代码中的TestSetList,这部分代码跟QC无关,也不列举了。
重点之二:测试执行
先看一个总体调用RunTestSetPlan,当然也是需要新开线程调用的:
Thread td_runTestSetPlan = new Thread(new ThreadStart(this.RunTestSetPlan));
td_runTestSetPlan.Start();
调用步骤是先检验validate,然后运行run,最后收集结果monitor:
private void RunTestSetPlan()
{
try
{
if (tdc.ProjectConnected == true)
{
if (TestSetNameList.Items.Count != 0)
{
for (int i = 0; i < TestSetList.Items.Count; i++)
{
if (validateTestSetID(TestSetList.Items[i].ToString(), i) == true)
{
if (runTestSet(TestSetList.Items[i].ToString(), i) == true)
{
if (monitorTestSet(TestSetList.Items[i].ToString()) == true)
{
QCexecutionStatus.RefreshExecStatusInfo("all", true);
}
}
}
}
}
else
MessageBox.Show("Empty Test Set List.", "Warning");
}
else
MessageBox.Show("Connection Error, please login again.", "Warning");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
validate通过QCfilter,使用testSetID去做筛选,取得我们需要的测试集,然后根据判断测试集是否为空来确定测试集是否有效,代码如下:
private bool validateTestSetID(string testSetID, int i)
{
TDAPIOLELib.TestSetFactory QCtestSetFactory = (TDAPIOLELib.TestSetFactory)tdc.TestSetFactory;
TDAPIOLELib.TDFilter QCfilter = (TDAPIOLELib.TDFilter)QCtestSetFactory.Filter;
QCfilter["CY_CYCLE_ID"] = testSetID;
List QClist = QCfilter.NewList();
if (QClist.Count != 0)
{
return true;
}
else
{
return false;
}
}
runTest与之前类似,获取首只测试集对象后,通过QCTSTestFactory将测试集下的所有用例形成QClistForTSTest列表,并用QCscheduler执行,代码如下:
private bool runTestSet(string testSetID, int i)
{
try
{
TDAPIOLELib.TestSetFactory QCtestSetFactory = (TDAPIOLELib.TestSetFactory)tdc.TestSetFactory;
TDAPIOLELib.TDFilter QCfilter = (TDAPIOLELib.TDFilter)QCtestSetFactory.Filter;
QCfilter["CY_CYCLE_ID"] = testSetID;
List QClist = QCfilter.NewList();
TDAPIOLELib.TestSet QCtestSet = (TDAPIOLELib.TestSet)QClist[1];
TDAPIOLELib.TestSetFolder QCtestSetFolder = (TDAPIOLELib.TestSetFolder)QCtestSet.TestSetFolder;
TDAPIOLELib.TSTestFactory QCTSTestFactory = (TDAPIOLELib.TSTestFactory)QCtestSet.TSTestFactory;
QClistForTSTest = QCTSTestFactory.NewList("");
try
{
string applicationCreationTime = File.GetCreationTime(@"The path of Your application").ToString();
string machineName = System.Net.Dns.GetHostEntry("IP address of test machine").HostName.Split('.')[0];
QCtestSet["CY_USER_01"] = applicationCreationTime;
Thread.Sleep(1000);
QCtestSet["CY_USER_02"] = machineName;
Thread.Sleep(1000);
QCtestSet.ResetTestSet(false);
Thread.Sleep(1000);
QCtestSet.Post();
Thread.Sleep(1000);
QCtestSet.Refresh();
Thread.Sleep(10000);
QCscheduler = (TDAPIOLELib.TSScheduler)QCtestSet.StartExecution("");
QCscheduler.Run(QClistForTSTest);
Thread.Sleep(5000);
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return false;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return false;
}
}
Monitor,使用QCTestExecStatus中的QCexecutionStatus作为计数器,逐个判断当前步骤是否跑完,汇总运行结果,代码如下:
private bool monitorTestSet(string testSetID)
{
try
{
QCexecutionStatus = (TDAPIOLELib.ExecutionStatus)QCscheduler.ExecutionStatus;
QCexecutionStatus.RefreshExecStatusInfo("all", true);
TDAPIOLELib.TestExecStatus QCTestExecStatus;
int checkStep = 1;
while (checkStep <= QCexecutionStatus.Count)
{
QCexecutionStatus.RefreshExecStatusInfo("all", true);
QCTestExecStatus = (TDAPIOLELib.TestExecStatus)QCexecutionStatus[checkStep];
if (QCTestExecStatus.Message == "Nothing" || QCTestExecStatus.Message == "Waiting..." || QCTestExecStatus.Message == "Connecting...")
{
Thread.Sleep(10000);
QCexecutionStatus.RefreshExecStatusInfo("all", true);
}
else
{
TDAPIOLELib.TSTest QCtestOfTestSet = (TDAPIOLELib.TSTest)QClistForTSTest[checkStep];
QCtestOfTestSet.Refresh();
switch (QCTestExecStatus.Message)
{
case "Completed":
if (QCtestOfTestSet.Status == "Passed")
CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution completed -> " + QCtestOfTestSet.Status);
else
if (QCtestOfTestSet.Status == "Failed")
CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution completed -> " + QCtestOfTestSet.Status);
else
CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution unknown -> " + QCtestOfTestSet.Status);
break;
case "No available hosts":
CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution failed (No available hosts) -> " + QCtestOfTestSet.Status);
break;
case "Cannot get RemoteAgent's ClassID for test type <TestType>":
CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " Cannot get RemoteAgent's ClassID for test type <TestType> -> " + QCtestOfTestSet.Status);
break;
case "Host connected":
CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution failed (Host connected) -> " + QCtestOfTestSet.Status);
break;
default:
CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " unhandled case -> " + QCtestOfTestSet.Status);
break;
}
checkStep = checkStep + 1;
}
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return false;
}
}
至此,整个测试运行过程结束。关于收集结果中除了主线程结果刷新之外,还需要有其他线程做结果的收集和展示,否则无法实现动态实时展示,这部分代码与QC无直接关系,也暂时不展示。
可以看出,本文所涉及的内容,对于测试框架来说,也仅仅是一小部分。关于其他部分,以后有时间再分拆开逐一讨论。
以上内容来自公众号:诗泽园
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)