(原创文章,转载请注明出处。)

一、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:

正式切入正题:

定义全局变量

真实代码中有很多控制类及展示类,都已经去除了。这里只展示核心代码。

1        TDConnection tdc = new TDConnection();
2        TDAPIOLELib.TSScheduler QCscheduler;
3        //用于最终真实启动用例及监测执行结果
4        TDAPIOLELib.ExecutionStatus QCexecutionStatus;
5        //用于反馈执行结果及做结果的动态刷新
6        List QClistForTSTest;

QC服务器连接、登录(身份验证)及项目连接

服务器连接为初始化连接,好比你刚登录QC终端时它给你的反馈。一般会碰到要你reload ActiveX或者OTA初始化失败之类的错误。处理方法是把QC缓存文件夹删除,再访问,让其重新reload。这块代码里会有处理,但这类代码就不贴了。

 1        //初始化连接
2        private void InitConnect(string serverName)
3        {
4            try
5            {
6                if ((tdc.Connected == false) || (tdc.Connected == true && tdc.ServerName != serverName))
7                    tdc.InitConnectionEx(serverName);
8            }
9            catch (Exception ex)
10            {
11                Console.WriteLine(ex.ToString());
12                MessageBox.Show("Server Error", "Warning");
13            }
14        }
15
16        //身份验证
17        private void GetAuthenticate(string userName, string passWord)
18        {
19            try
20            {
21                tdc.Login(userName, passWord);
22                TDAPIOLELib.List projectList = tdc.get_VisibleProjects(tdc.VisibleDomains[1].ToString());
23                //VisibleDomains[1]为项目列表中的第一个Domain,多的话自行处理
24                for (int i = 1; i <= projectList.Count; i++)
25                {
26                    projectsBox.Items.Add(projectList[i]);
27                    //将Domain下所有项目读取出来,以备后用
28                }            
29            }
30            catch (Exception ex)
31            {
32                Console.WriteLine(ex.ToString());
33                MessageBox.Show("Please check the User Name is correct or not.", "Warning");
34            }
35        }
 1        //项目连接
2        private void LoginButton_Click(object sender, EventArgs e)
3        {
4            try
5            {
6                string ProjectName = projectsBox.SelectedItem.ToString();
7                tdc.Connect(tdc.VisibleDomains[1].ToString(), ProjectName);
8
9                //调用生成业务树代码,也可以不在此处调用
10                Thread td_tree = new Thread(new ThreadStart(this.CreateTreeView));
11                td_tree.Start();
12            }
13            catch (Exception ex)
14            {
15                Console.WriteLine(ex.ToString());
16                MessageBox.Show("Please choose the project.", "Warning");
17            }
18        }

重点之一:递归生成业务树

同样需要新开线程调用,先生成根节点,再递归生成业务树

 1        //生成根节点并调用递归树
2        private void CreateTreeView()
3        {
4            try
5            {
6                TreeNode mainNode = new TreeNode();
7                mainNode.Name = "Root";
8                mainNode.Text = "Root";
9                Add_TreeRoot(mainNode);
10
11                SysTreeNode test_folder;
12                TestSetFactory globalTestSetFactory;
13                List l_List;
14                TreeNode r_node = new TreeNode();
15
16                int nodeCount;
17                nodeCount = qcProjectTree.GetNodeCount(true);
18                TreeNode[] r_nodeArray = new TreeNode[10000];
19                r_nodeArray = qcProjectTree.Nodes.Find("Root", false);
20                r_node = r_nodeArray[0];
21
22                TestSetTreeManager tm;
23                tm = (TDAPIOLELib.TestSetTreeManager)tdc.TestSetTreeManager;
24                test_folder = (TDAPIOLELib.SysTreeNode)tm.Root;
25                globalTestSetFactory = (TDAPIOLELib.TestSetFactory)tdc.TestSetFactory;
26                l_List = globalTestSetFactory.NewList("");
27                recursiveTreeBuilder(test_folder, r_node);
28            }
29            catch (Exception ex)
30            {
31                Console.WriteLine(ex.ToString());
32            }
33        }
34
35        //递归生成业务树
36        private bool recursiveTreeBuilder(TDAPIOLELib.SysTreeNode folder, TreeNode parent)
37        {
38            TDAPIOLELib.List folders, tests;
39            TDAPIOLELib.TestSetFactory objTestSetFactory;
40            TDAPIOLELib.TestSetFolder objTSFolder;
41            TreeNode n;
42            try
43            {
44                folders = folder.NewList();
45                foreach (TDAPIOLELib.SysTreeNode f in folders)
46                {
47                    TreeNode nodeChild = new TreeNode();
48                    nodeChild.Name = f.Name;
49                    nodeChild.Text = f.Name;
50                    nodeChild.ImageIndex = 0;
51                    Add_TreeNode(parent, nodeChild);
52                    n = parent.Nodes[nodeChild.Name];
53                    recursiveTreeBuilder(f, n);
54                }
55
56                objTSFolder = (TDAPIOLELib.TestSetFolder)folder;
57                if (objTSFolder.NodeID != 0)
58                {
59                    objTestSetFactory = (TDAPIOLELib.TestSetFactory)objTSFolder.TestSetFactory;
60                    tests = objTestSetFactory.NewList("");
61                    foreach (TDAPIOLELib.TestSet testSet in tests)
62                    {
63                        TreeNode nodeChild1 = new TreeNode();
64                        nodeChild1.Name = testSet.ID.ToString();
65                        nodeChild1.Text = testSet.Name;
66                        nodeChild1.ImageIndex = 1;
67                        Add_TreeNode(parent, nodeChild1);
68                        n = parent.Nodes[nodeChild1.Name];
69                        n.Tag = objTSFolder.Path + @"\" + testSet.Name;
70                    }
71                }
72                return true;
73            }
74            catch (Exception ex)
75            {
76                Console.WriteLine(ex.ToString());
77                return false;
78            }
79        }

以下代码用委托的方式生成节点,保证在业务树生成过程中可随时点击并保证界面不出现假死(跟业务树生成无直接关系,可忽略)

 1        delegate void Add_Node(TreeNode parent, TreeNode node);
2        private void Add_TreeNode(TreeNode parent, TreeNode node)
3        {
4            if (this.InvokeRequired)
5            {
6                this.BeginInvoke(new Add_Node(Add_TreeNode), parent, node);
7            }
8            else
9            {
10                parent.Nodes.Add(node);
11            }
12            Thread.Sleep(10);
13        }

生成业务树后,由用户通过业务树选择需要运行的节点,形成待测试列表,就是后续代码中的TestSetList,这部分代码跟QC无关,也不列举了。

重点之二:测试执行

先看一个总体调用RunTestSetPlan,当然也是需要新开线程调用的:

1        Thread td_runTestSetPlan = new Thread(new ThreadStart(this.RunTestSetPlan));
2        td_runTestSetPlan.Start();

调用步骤是先检验validate,然后运行run,最后收集结果monitor:

 1        private void RunTestSetPlan()
2        {
3            try
4            {
5                if (tdc.ProjectConnected == true)
6                {
7                    if (TestSetNameList.Items.Count != 0)
8                    {
9                        for (int i = 0; i < TestSetList.Items.Count; i++)
10                        {
11                            if (validateTestSetID(TestSetList.Items[i].ToString(), i) == true)
12                            {
13                                if (runTestSet(TestSetList.Items[i].ToString(), i) == true)
14                                {
15                                    if (monitorTestSet(TestSetList.Items[i].ToString()) == true)
16                                    {
17                                        QCexecutionStatus.RefreshExecStatusInfo("all", true);                      
18                                    }
19                                }
20                            }
21                        }
22                    }
23                    else
24                        MessageBox.Show("Empty Test Set List.", "Warning");
25                }
26                else
27                    MessageBox.Show("Connection Error, please login again.", "Warning");
28            }
29            catch (Exception ex)
30            {
31                Console.WriteLine(ex.ToString());
32            }
33        }

validate通过QCfilter,使用testSetID去做筛选,取得我们需要的测试集,然后根据判断测试集是否为空来确定测试集是否有效,代码如下:

 1        private bool validateTestSetID(string testSetID, int i)
2        {
3            TDAPIOLELib.TestSetFactory QCtestSetFactory = (TDAPIOLELib.TestSetFactory)tdc.TestSetFactory;
4            TDAPIOLELib.TDFilter QCfilter = (TDAPIOLELib.TDFilter)QCtestSetFactory.Filter;
5            QCfilter["CY_CYCLE_ID"] = testSetID;
6            List QClist = QCfilter.NewList();
7            if (QClist.Count != 0)
8            {
9                return true;
10            }
11            else
12            {
13                return false;
14            }             
15        }

runTest与之前类似,获取首只测试集对象后,通过QCTSTestFactory将测试集下的所有用例形成QClistForTSTest列表,并用QCscheduler执行,代码如下:

 1        private bool runTestSet(string testSetID, int i)
2        {
3            try
4            {
5                TDAPIOLELib.TestSetFactory QCtestSetFactory = (TDAPIOLELib.TestSetFactory)tdc.TestSetFactory;
6                TDAPIOLELib.TDFilter QCfilter = (TDAPIOLELib.TDFilter)QCtestSetFactory.Filter;
7                QCfilter["CY_CYCLE_ID"] = testSetID;
8                List QClist = QCfilter.NewList();
9                TDAPIOLELib.TestSet QCtestSet = (TDAPIOLELib.TestSet)QClist[1];
10                TDAPIOLELib.TestSetFolder QCtestSetFolder = (TDAPIOLELib.TestSetFolder)QCtestSet.TestSetFolder;
11                TDAPIOLELib.TSTestFactory QCTSTestFactory = (TDAPIOLELib.TSTestFactory)QCtestSet.TSTestFactory;
12                QClistForTSTest = QCTSTestFactory.NewList("");
13                try
14                {
15                    string applicationCreationTime = File.GetCreationTime(@"The path of Your application").ToString();
16                    string machineName = System.Net.Dns.GetHostEntry("IP address of test machine").HostName.Split('.')[0];
17                    QCtestSet["CY_USER_01"] = applicationCreationTime;
18                    Thread.Sleep(1000);
19                    QCtestSet["CY_USER_02"] = machineName;
20                    Thread.Sleep(1000);
21                    QCtestSet.ResetTestSet(false);
22                    Thread.Sleep(1000);
23                    QCtestSet.Post();
24                    Thread.Sleep(1000);
25                    QCtestSet.Refresh();
26
27                    Thread.Sleep(10000);
28
29                    QCscheduler = (TDAPIOLELib.TSScheduler)QCtestSet.StartExecution("");
30                    QCscheduler.Run(QClistForTSTest);
31                    Thread.Sleep(5000);
32
33                    return true;
34                }
35                catch (Exception ex)
36                {
37                    Console.WriteLine(ex.ToString());
38                    return false;
39                }
40            }
41            catch (Exception ex)
42            {
43                Console.WriteLine(ex.ToString());
44                return false;
45            }
46        }

Monitor,使用QCTestExecStatus中的QCexecutionStatus作为计数器,逐个判断当前步骤是否跑完,汇总运行结果,代码如下:

 1        private bool monitorTestSet(string testSetID)
2        {
3            try
4            {
5                QCexecutionStatus = (TDAPIOLELib.ExecutionStatus)QCscheduler.ExecutionStatus;
6                QCexecutionStatus.RefreshExecStatusInfo("all", true);
7                TDAPIOLELib.TestExecStatus QCTestExecStatus;
8                int checkStep = 1;
9                while (checkStep <= QCexecutionStatus.Count)
10                {
11                    QCexecutionStatus.RefreshExecStatusInfo("all", true);
12                    QCTestExecStatus = (TDAPIOLELib.TestExecStatus)QCexecutionStatus[checkStep];
13                    if (QCTestExecStatus.Message == "Nothing" || QCTestExecStatus.Message == "Waiting..." || QCTestExecStatus.Message == "Connecting...")
14                    {
15                        Thread.Sleep(10000);
16                        QCexecutionStatus.RefreshExecStatusInfo("all", true);
17                    }
18                    else
19                    {
20                        TDAPIOLELib.TSTest QCtestOfTestSet = (TDAPIOLELib.TSTest)QClistForTSTest[checkStep];
21                        QCtestOfTestSet.Refresh();
22                        switch (QCTestExecStatus.Message)
23                        {
24                            case "Completed":
25                                if (QCtestOfTestSet.Status == "Passed")
26                                    CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution completed -> " + QCtestOfTestSet.Status);
27                                else
28                                    if (QCtestOfTestSet.Status == "Failed")
29                                        CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution completed -> " + QCtestOfTestSet.Status);
30                                    else
31                                        CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution unknown -> " + QCtestOfTestSet.Status);
32                                break;
33                            case "No available hosts":
34                                CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution failed (No available hosts) -> " + QCtestOfTestSet.Status);
35                                break;
36                            case "Cannot get RemoteAgent's ClassID for test type <TestType>":
37                                CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " Cannot get RemoteAgent's ClassID for test type <TestType> -> " + QCtestOfTestSet.Status);
38                                break;
39                            case "Host connected":
40                                CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " execution failed (Host connected) -> " + QCtestOfTestSet.Status);
41                                break;
42                            default:
43                                CurrentStateBar("Step " + checkStep + " " + QCtestOfTestSet.Name + " unhandled case -> " + QCtestOfTestSet.Status);
44                                break;
45                        }
46                        checkStep = checkStep + 1;
47                    }
48                }
49                return true;
50            }
51            catch (Exception ex)
52            {
53                Console.WriteLine(ex.ToString());
54                return false;
55            }
56        }

至此,整个测试运行过程结束。关于收集结果中除了主线程结果刷新之外,还需要有其他线程做结果的收集和展示,否则无法实现动态实时展示,这部分代码与QC无直接关系,也暂时不展示。

可以看出,本文所涉及的内容,对于测试框架来说,也仅仅是一小部分。关于其他部分,以后有时间再分拆开逐一讨论。

QC API全系列揭秘之Test Execution操作(全网首发)的更多相关文章

  1. ASP.NET Web API 2框架揭秘

    ASP.NET Web API 2框架揭秘(.NET领域再现力作顶级专家精讲微软全新轻量级通信平台) 蒋金楠 著   ISBN 978-7-121-23536-8 2014年7月出版 定价:108.0 ...

  2. JAVA帮助文档全系列 JDK1.5 JDK1.6 JDK1.7 官方中英完整版下载

    JAVA帮助文档全系列 JDK1.5 JDK1.6 JDK1.7 官方中英完整版下载JDK(Java Development Kit,Java开发包,Java开发工具)是一个写Java的applet和 ...

  3. HBase编程 API入门系列之create(管理端而言)(8)

    大家,若是看过我前期的这篇博客的话,则 HBase编程 API入门系列之put(客户端而言)(1) 就知道,在这篇博文里,我是在HBase Shell里创建HBase表的. 这里,我带领大家,学习更高 ...

  4. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  5. HBase编程 API入门系列之delete(客户端而言)(3)

    心得,写在前面的话,也许,中间会要多次执行,连接超时,多试试就好了. 前面的基础,如下 HBase编程 API入门系列之put(客户端而言)(1) HBase编程 API入门系列之get(客户端而言) ...

  6. HBase编程 API入门系列之get(客户端而言)(2)

    心得,写在前面的话,也许,中间会要多次执行,连接超时,多试试就好了. 前面是基础,如下 HBase编程 API入门系列之put(客户端而言)(1) package zhouls.bigdata.Hba ...

  7. HBase编程 API入门系列之HTable pool(6)

    HTable是一个比较重的对此,比如加载配置文件,连接ZK,查询meta表等等,高并发的时候影响系统的性能,因此引入了“池”的概念. 引入“HBase里的连接池”的目的是: 为了更高的,提高程序的并发 ...

  8. Html5 学习系列(四)文件操作API

    原文:Html5 学习系列(四)文件操作API 引言 在之前我们操作本地文件都是使用flash.silverlight或者第三方的activeX插件等技术,由于使用了这些技术后就很难进行跨平台.或者跨 ...

  9. 附024.Kubernetes全系列大总结

    Kubernetes全系列总结如下,后期不定期更新.欢迎基于学习.交流目的的转载和分享,禁止任何商业盗用,同时希望能带上原文出处,尊重ITer的成果,也是尊重知识.若发现任何错误或纰漏,留言反馈或右侧 ...

随机推荐

  1. Java基础练习4(内存管理)

    请根据如下程序代码,画出对应的内存管理图(不需要画方法区),并写出输出结果. 1. public class Cell{ int row; int col; public Cell(int row,i ...

  2. nginx漏洞分析与升级修复

    一 .此次漏洞分析 1 nginx HTTP/2漏洞 [nginx-announce] nginx安全公告(CVE-2018-16843,CVE-2018-16844)在nginx HTTP / 2实 ...

  3. Windows Server 2016-Powershell之客户端加域

    将本地计算机添加到域或工作组,可通过Add-Computer命令操作,具体信息如下: 语法: Add-Computer [-DomainName] <String> [-ComputerN ...

  4. 微信小程序开发之多图片上传+服务端接收

    前言: 业务需求,这次需要做一个小程序同时选中三张图片一起上传到服务端,后端使用的.NET WEBAPI接收数据保存. 使用技术: 在这章中将会使用到微信小程序wx.uploadFile(Object ...

  5. SpringBoot2.0 项目异常日志,但不影响运行(待解决)

    第一种: 2019-04-17 01:46:33 [INFO] [org.apache.juli.logging.DirectJDKLog:175] - Error parsing HTTP requ ...

  6. wtf!rds数据同步居然出问题了--菜鸟db的数据修复历程

    由于一次上线操作的数据变更太多,导致执行时间很长! 由于做手动主从关系,所以操作落在了主库上. 由于主从关系不是对整个库的操作,所以在有表新增的地方,添加了dts新的同步关系. db变更完成后,就发布 ...

  7. docker中怎样设置开机启动--随容器的启动而启动服务?

    docker可以说给我们的部署带来极大的方便和可逢凶化吉性!(懂的同学自然懂) 在初步了解之后,我们就能简单使用docker了. 刚开始玩docker时,可以基于系统级别的镜像做定制,比如基于  ce ...

  8. mysql的学习笔记(七)

    1.自定义函数,函数可以返回任意类型的值,同样可接说这些类型的参数. CREATE FUNCTION function_name RETURNS {STRING|INTER|REAL|DECIMAL} ...

  9. 【TCP协议】(1)---TCP协议详解

    TCP协议 本文内容如下:      1)TCP协议概念      2)TCP头部结构和字段介绍      3)TCP流量控制            滑动窗口      4)TCP拥塞控制      ...

  10. RabbitMQ消息队列(一)-RabbitMQ的优劣势及产生背景

    本篇并没有直接讲到技术,例如没有先写个Helloword.我想在选择了解或者学习一门技术之前先要明白为什么要现在这个技术而不是其他的,以免到最后发现自己学错了.同时如果已经确定就是他,最好先要了解下技 ...