QC API全系列揭秘之Test Execution操作(全网首发)

一、QC简介:

Quality Center存在至今已经走过了10多个年头,名字从一开始的TD,到后来的QC,再到现在的ALM。所属公司从开始的Mercury到现在的HP,核心一直没变,变的只有名字。随着Mercury最核心的高层、架构师和专家的离开,现在每每的升级都带来诸多失望,再也没有当初使用Mercury工具的时候那样心潮澎湃,看看QC,看看QTP,不多言语。如果能够坚持做好的话,现在哪有TestLink、哪有禅道什么事。然而,QC框架的设计核心,拿到现在来看,依然是测试管理框架的主流。QC设计思路简单清晰,从测试需求到测试用例,再到执行测试用例、提交缺陷、跟踪缺陷,整个过程清晰且易于理解,时至今日,依然被广泛沿用。

二、写作目的:

        写此系列的目的,不是为了情怀,而是为了将QC接口的调用方式整理成册。一来是为了通过对QC接口调用的理解,去更深入的理解测试框架的概念(当然仅仅包含小部分);二来是为了通过二次开发,解决QC使用过程中的诸多不便。

三、解决问题:

QC当前存在以下问题:

1、需求或用例的导入导出依然不完善。(网上资料大部分需要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无直接关系,也暂时不展示。

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

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值