AutoTest系统设计概述#
AutoTest是一个基于.NET平台实现的自动化/压力测试的系统,可独立运行于windows平台下,支持分布式部署,不需要其他配置或编译器的支持。(本质是一个基于协议的测试工具),前面还有一篇对其功能的简单介绍【AutoTest简介】
AutoTest用于发布的部分有2个部分,主程序【AutoTest.exe】及分布式部署程序【RemoteService.exe】(用于将将测试业务分布式部署到远程主机)
而在内部又被设计为多个组成部分,最终完成对自定义脚本文件的解析并按脚本要求的模式去执行。
如上图,简单介绍下
执行逻辑层主要由3部分组成
- MyControl.dll 该部分主要为表示层提供专门为业务定制的UI控件等用于显示数据的组件
- myCommonTool.dll 该部分为这个解决方案即这个系统提供通用的工具方法及组件如日志系统(指系统本身的日子系统,测试过程中的日志或记录由专门的记录采集模块完成)
- CaseExecutiveActuator.dll 该部分为这个测试平台提供最核心的逻辑处理部分,如脚本的解析,脚本的选择,脚本的执行等(对于系统来说添加任意的其他的协议支持也仅需要修改这个库中的部分内容)
表示层暂时是由2部分组成
- AutoTest.exe 测试平台显示界面,运行于windows环境下,赋值数据及结果的呈现,同时负责与操作者者进行交换
- RemoteService.exe 分布式部署程序,负责在远程主机开启服务实现测试任务的分布式部署,分布式远程服务基于WCF框架理论上支持跨internet的分布式部署
最下面的2个模块是用于系统内部模块的单元测试,本身与系统运行无关,所以就不赘述了
此外还有一个重要的组成部分-脚本文件,这里使用的脚本依托于XML文件,规则上基本是独立定义的。脚本文件控制所有Case的执行方式,内容,甚至是执行路径,还包括对断言的额外处理。脚本文件被解释器(CaseExecutiveActuator的一部分)转换为系统/工具直接可以使用的数据结构,并最终被执行。(脚本规则相对独立,将不会在此篇中涉及)
核心执行模块【CaseExecutiveActuator】#
CaseExecutiveActuator模块主要由2部分组成,脚本处理部分(算法)及脚本内容的存储部分(数据)。
脚本对外的存在形式是XML文件,这个XML也是最终的脚本文件。系统解析这个XML文件(对CASE内容,或测试业务的修改包括新建也只是对XML脚本文件的修改而),然后对其进行进一步处理(当然使用合适的数据结构对其进行存储是第一步)。脚本的存储比较简单,先看这部分,其实就是实现了一个类似Tree的数据结构来存储整个脚本逻辑,然后每个单独的Case都存储在单独的Cell里(Cell为该自定义数据结构的基础单元,于单个Case对应)。
Cell的结构如下(简单的实现了形如右边图片的的数据结构)
其实很容易看出来这个结构跟TreeView的结构十分类似,其实最初脚本数据的存储是直接借助于TreeView的,不过为了将业务跟UI完全分离以便未来向其他平台移植时不受UI框架的影响,还是自行实现了这样的数据结构。Cell结构本身十分简单,有3个主要的结构指针
childCellList 指向子Cell
nextCell 指向下一个Cell
parentCell 指向父Cell
还有2个数据源
caseXmlNode 指向元素的脚本数据,保留原始数据的地址是为了方便AutoTest对脚本的直接修改(这里使用的XML,如果希望相关脚本文件类型需要修改此处及脚本解析部分)
caseRunData 已经解析完成的单Case数据(实际加载脚步时对所有脚本解析一次,把结果存储在这里,后面的执行即直接取这里的数据以提高性能)
另外包含2个辅助元素
caseType 指示该Cell即Case的实类型,可以辅助处理异常Cell的数据。
uiTag 可选的指针,用于数据绑定,名字也提示了主要用于绑定UI(实际可用绑定任何类型的数据)
该机构还提供一些其他功能如Cell的容器及索引器等有兴趣的可以看下下面的Code
12
13 namespace CaseExecutiveActuator.Cell
14 {
15 //using CaseCell = TreeNode;//可让2类完全等价
16 public class CaseCell
17 {
18 List<CaseCell> childCellList;
19
20 private CaseType caseType;
21 private XmlNode caseXmlNode;
22 private myRunCaseData<ICaseExecutionContent> caseRunData;
23 private object uiTag;
24
25 private CaseCell nextCell;
26 private CaseCell parentCell;
27
28
29 public CaseCell()
30 {
31
32 }
33
34 /// <summary>
35 /// CaseCell构造函数
36 /// </summary>
37 /// <param name="yourCaseType">CaseType</param>
38 /// <param name="yourXmlNode">CaseCell脚本原始信息</param>
39 /// <param name="yourCaseRunData">CaseCell脚本解析后的信息</param>
40 public CaseCell(CaseType yourCaseType, XmlNode yourXmlNode ,myRunCaseData<ICaseExecutionContent> yourCaseRunData)
41 {
42 caseType = yourCaseType;
43 caseXmlNode = yourXmlNode;
44 caseRunData = yourCaseRunData;
45 }
46
47 /// <summary>
48 /// 获取或设置CaseCell脚本解析后的信息
49 /// </summary>
50 public myRunCaseData<ICaseExecutionContent> CaseRunData
51 {
52 get { return caseRunData; }
53 set { caseRunData = value; }
54 }
55
56 /// <summary>
57 /// 获取或设置CaseCell脚本原始信息
58 /// </summary>
59 public XmlNode CaseXmlNode
60 {
61 get { return caseXmlNode; }
62 set { caseXmlNode = value; }
63 }
64
65 /// <summary>
66 /// 获取或设置UiTag,可以用于UI控件与cell的绑定
67 /// </summary>
68 public object UiTag
69 {
70 get { return uiTag; }
71 set { uiTag = value; }
72 }
73
74 /// <summary>
75 /// 获取当前Cell类型
76 /// </summary>
77 public CaseType CaseType
78 {
79 get { return caseType; }
80 }
81
82 /// <summary>
83 /// 获取下一个Cell,如果没有返回null
84 /// </summary>
85 public CaseCell NextCell
86 {
87 get { return nextCell; }
88 }
89
90 /// <summary>
91 /// 获取当前Cell的父Cell,如果没有返回null
92 /// </summary>
93 public CaseCell ParentCell
94 {
95 get { return parentCell; }
96 }
97
98 /// <summary>
99 /// 获取当前Cell的ChildCells列表
100 /// </summary>
101 public List<CaseCell> ChildCells
102 {
103 get { return childCellList; }
104 }
105
106 /// <summary>
107 /// 获取一个值标识当前Cell是否有NextCell
108 /// </summary>
109 public bool IsHasNextCell
110 {
111 get { return nextCell != null; }
112 }
113
114 /// <summary>
115 /// 获取一个值标识当前Cell是否有parentCell
116 /// </summary>
117 public bool IsHasParent
118 {
119 get
120 {
121 if (parentCell != null)
122 {
123 return true;
124 }
125 return false;
126 }
127 }
128
129 /// <summary>
130 /// 获取一个值标识当前Cell是否有ChildCell
131 /// </summary>
132 public bool IsHasChild
133 {
134 get
135 {
136 if (childCellList != null)
137 {
138 if (childCellList.Count != 0)
139 {
140 return true;
141 }
142 }
143 return false;
144 }
145 }
146
147 /// <summary>
148 /// 设置下一个Cell
149 /// </summary>
150 /// <param name="yourCaseCell">下一个Cell</param>
151 public void SetNextCell(CaseCell yourCaseCell)
152 {
153 nextCell = yourCaseCell;
154 }
155
156 /// <summary>
157 /// 设置ParentCell
158 /// </summary>
159 /// <param name="yourCaseCell">ParentCell</param>
160 public void SetParentCell(CaseCell yourCaseCell)
161 {
162 parentCell = yourCaseCell;
163 }
164
165 /// <summary>
166 /// 向当前Cell中插入子Cell
167 /// </summary>
168 /// <param name="yourCaseCell">子Cell</param>
169 public void Add(CaseCell yourCaseCell)
170 {
171 if (childCellList == null)
172 {
173 childCellList = new List<CaseCell>();
174 }
175 yourCaseCell.SetParentCell(this);
176 childCellList.Add(yourCaseCell);
177 if(childCellList.Count>1)
178 {
179 childCellList[childCellList.Count-2].SetNextCell(yourCaseCell);
180 }
181 }
182
183 //一个tag存放ui指针/引用
184 //实现一个Nodes.Count计算每层数目,或返回是否有子结构
185 //Nodes[0]索引或实现NodeStart,返回层中第一个CaseCell
186 //实现一个NextNode返回层中的下一个CaseCell
187 }
188
189 public class ProjctCollection
190 {
191 List<CaseCell> myProjectChilds;
192
193 public List<CaseCell> ProjectCells
194 {
195 get { return myProjectChilds; }
196 }
197
198 public void Add(CaseCell yourCaseCell)
199 {
200 if (myProjectChilds == null)
201 {
202 myProjectChilds = new List<CaseCell>();
203 }
204 myProjectChilds.Add(yourCaseCell);
205 }
206
207 public CaseCell this[int indexP, int indexC]
208 {
209 get
210 {
211 if(myProjectChilds.Count>indexP)
212 {
213 if (myProjectChilds[indexP].IsHasChild)
214 {
215 if (myProjectChilds[indexP].ChildCells.Count > indexC)
216 {
217 return myProjectChilds[indexP].ChildCells[indexC];
218 }
219 }
220 }
221 return null;
222 }
223 }
224
225
226 }
227 }
执行的实体CaseExecutiveActuator本身会较多点,介绍的也会粗略些,不过大体也是可以简单的分成2个部分。可以很容易的想到假如我们得了脚本文件,那么有2个问题:第一就是怎么知道选择哪一个case,当前case执行完成后执行哪一个,第二个问题就是case中包含的业务如何执行。CaseExecutiveActuator也正是分成了这2部分
- myCaseRunTime 负责指引Case路径,Cell的寻找,跳转及序列的指示 都是由它完成的
- CaseActionActuator 负责Case的具体执行,包括对各种协议的执行,及对结果的分析及储存,断言的处理,附加动作如语音提示,重试等执行相关的部分就由它处理
先来看比较简单的CaseRunTime
实际上于CaseRunTime 紧密相关的还有另外2个组件myCaseLoop,myCsaeQueue(他们实际上也仅被CaseRunTime 使用)
通过名字其实大概可以猜到他们的功能
- myCaseLoop 控制一组Case(可以是由多个Case组成的业务)的循环执行,当然支持循环里无限嵌套其他的循环
- myCsaeQueue 控制一个逻辑项目的Case坐标(当前执行的Case),基础的Case会放在一个个逻辑项目中,每个逻辑下项目可以标识一类业务的集合,而当前执行Case是可以在这些逻辑项目中来回跳转的,所以它负责多个逻辑项目的寻址
- myCaseRunTime 借助前面的CaseLoop 和 CsaeQueue,完成整个Case的移动轨迹(这个轨迹可能因执行结果不同而有不同的变化,取决于脚本文件如何写)
- RunCaseCount 一个辅助类,帮助统计脚本中实际将要执行的Case数量(会考虑起始点及循环等因素)
单独看看myCaseRunTime
如上图可以看到myCaseRunTime实际上是包含了一个myCsaeQueue列表的,不过逻辑的核心是nextCase。有兴趣的可以看看下面的实现(贴出的只包含关键部分)
View Code
而最终myCaseRunTime也是为CaseActionActuator 服务的,现在来看下CaseActionActuator。
CaseActionActuator相对比较多一点,因为要完成的功能会多一些,跟其他模块的联系也会大一些
这个可能看起来就很乱了,上图的模块主要就是一个Case文件的在系统中的表现,可以理解为一个User,这个User通过Case脚本文件可以执行一套业务,执行过程也是独立的,环境,线程,数据也都是独立的。所以可以创建任意多个这种模块以模拟大量的用户同时操作,当然脚本可以使用不同的脚本文件,也可以使用相同脚本文件(若使用相同脚本文件系统会对当前模块进行深度克隆,克隆的用户共享部分不会影响运行的数据)。该模块还可以选择以Cell对UI控件进行绑定,以达到执行过程中用户界面的友好反馈,当然不同的UI控件的动态效果需要单独的处理(处理由另一个辅助模块myActionActuator完成)
这个模块的图起来乱点,不过code相对清晰,有兴趣可以看下面代码(贴出的是关键部分)
View Code
同时CaseActionActuator还包含一个比较重要的部分myCaseProtocolEngine,该部分是协议引擎,实际上只要PC上可以使用的协议通过简单的修改都可以加入到系统
对于协议引擎必须继承规定好的接口实现,然后就能将协议加入系统。
这个模块还要许多其他部分比如Case文件的解析,及其他可以自定义的内容这里就不继续讲下去了,有兴趣的可以下载完整代码。
辅助工具模块MyCommonTool#
MyCommonTool其实包含的东西也比较多不过功能都相对独立,见下图
可以看到这个模块完全是由一个个独立的类组成,相互之间的联系非常少。有很多子模块提供不同的服务通过命名也大概可以看出来VoiceService提供语音服务,myWebTool提供web服务,myEncryption提供加密服务等等,它被系统的很多模块使用。由于类比较多而又没有什么逻辑上的联系,这里同样不继续讲下去了。需要了解的可以下载完整的代码
辅助UI显示模块MyControl#
这个部分主要与UI的形式与呈现有关,当然包括了很多定制的数据的banding,在AutoTest及RemoteService都会用到里面的东西。不过针对不同的界面框架这部分是不能重用的。所以也不用怎么看,稍微贴张图吧。
主程序界面AutoTest#
对于AutoTest本身已经没有太多执行相关的逻辑处理了(都由CaseExecutiveActuator来处理),其主要处理case的呈现,及执行过程中的动态修改,还包括报告的格式化输出,同时还要2个重要功能组织多个User(执行体)跟连接远程测试主机。
先稍微过下界面相关的内容,如下图
- AutoTest 这当然是主体
- AutoTest.myDialogWindow 这里包含UI系统下的除主体外的其他UI窗口(右边的图展开了该项,可以看下)
- AutoTest.myControl 同样是个UI控件的定制集合,不过这里的控件仅在AutoTest适合使用
- AutoTest.myTool 工具类,为AutoTest 的行为提供便利
- AutoTest.RemoteServiceReference 分布式远程访问模块,这个模块是引用WCF自动生成的,用于连接远程主机(这里WCF使用的自承载方式,程序运行时并不需要去配置什么其他的内容)
现在来看刚刚提到的一个重要的功能组织多个User,AutoTest可以创建任意多个虚拟执行体User,他们存储在CaseRunner中,同时CaseRunner还提供一些对User的基本的控制功能,如下图
而这个部分其实放在逻辑层更加合适,不过之前设计经验不足。把CaseRunner于UI控件十分深的banding在一起了。绑定效果如下图
这个就是大量CaseRunner绑定的效果(这个不是主界面,整体的显示效果可以看下
当然还有前面提到的另外一个重要功能连接远程测试主机,主要负责连接分布式部署在其他PC上的执行模块(就是后面要讲的RemoteService),同时可以控制及配置远程主机上的User(还包括连接的维持)。连接功能由RemoteClient组件来完成,它在前面提到的AutoTest.myTool 下,简单看下它的实现
基本结构如下图
这个其实实现起来比较简单,因为借助WCF框架,很多工作框架已经做好了,不过一些通道的选择配置以及是否适合双工通信可能不是最优的,后面还需要调整,可以看下下面的code
View Code
分布式部署服务模块RemoteService#
这个部分与AutoTest整体结构是相似的,在执行逻辑上也是同样依靠逻辑层的3个主要模块。而内部基于WCF单独实现了服务部分,服务内容主要包括对状态及业务返回数据进行过滤重组然后上报给连接的控制机,还有接收及处理控制机的控制或配置等命令请求。
下面看一下自身的结构,如下图
可以看到结构上与AutoTest确实是大体一致的,主要看下跟分布式部署相关的2个组件
- ExecuteService 直接提供服务说需要的部分(是WCF的必要部分)
- ServerHost 对服务连接的抽象再封装,所有对服务的操作,包括获取服务数据都通过这个组件,下层应用不直接使用ExecuteService服务(从结构关系图中也可以看出来ExecuteService只被ServerHost调用)
其他部分与远程服务没有太大关系的就不介绍了
ExecuteService 按要求实现IExecuteService接口,结构跟逻辑都比较清晰(现在这部分的设计十分简单,后期对服务内容的控制也只需要改动这个地方就可以了),直接看下下面的结构图
ServerHost 其实就是对ExecuteService再一层的封装,加入了许多与执行数据相关的逻辑,以方便应用层面的直接调用,如下图
如上图,订阅了多个回调委托来获取数据,在内部进行处理(MessageTransferChannel_RunnerCommandCallback,MessageTransferChannel_GetAllRemoteRunnerInfoCallback),同时也定义了许多委托抛出数据给应用使用,同时还需要介绍应用的命令处理后转发给ExecuteService,当然还有一个重要的功能,服务的启动跟维持也在这里完成。下面是这个类code的具体实现
1 class ServerHost
2 {
3 Uri baseAddress = new Uri("http://localhost:8087/SelService");//初始默认值在运行时由设置值来决定
4 ServiceHost baseHost = null;
5
6 public delegate void ServerHostMessageEventHandler(string sender, string message);
7 public event ServerHostMessageEventHandler OnServerHostMessage;
8
9 public delegate List<CaseRunner> BackNowRunnerListEventHandler();
10 public event BackNowRunnerListEventHandler OnBackNowRunnerList;
11
12 //public delegate void ServerHostCommandEventHandler(RunnerCommand command, List<int> runners);
13 //public event ServerHostCommandEventHandler OnServerHostCommand;
14
15 /// <summary>
16 /// 获取或设置当前BaseAddress
17 /// </summary>
18 public Uri BaseAddress
19 {
20 get { return baseAddress; }
21 set { baseAddress = value; }
22 }
23
24 /// <summary>
25 /// 获取当前BaseHost
26 /// </summary>
27 public ServiceHost BaseHost
28 {
29 get { return baseHost; }
30 }
31
32 /// <summary>
33 /// 获取BaseHost当前连接状态
34 /// </summary>
35 public CommunicationState BaseHostState
36 {
37 get
38 {
39 if (baseHost == null)
40 {
41 return CommunicationState.Closed;
42 }
43 else
44 {
45 return baseHost.State;
46 }
47 }
48 }
49
50 /// <summary>
51 /// 初始化ServerHost
52 /// </summary>
53 /// <param name="yourBaseAddress"></param>
54 public ServerHost(Uri yourBaseAddress)
55 {
56 baseAddress = yourBaseAddress;
57 MessageTransferChannel.MessageCallback += new Action<object, string>((a, b) => AddInfo(b));
58
59 MessageTransferChannel.OnRunnerCommand += MessageTransferChannel_RunnerCommandCallback;
60 MessageTransferChannel.OnGetAllRemoteRunnerInfo += MessageTransferChannel_GetAllRemoteRunnerInfoCallback;
61 }
62
63 /// <summary>
64 /// 处理ExecuteService的远程指令【runner状态获取】
65 /// </summary>
66 RemoteRunnerInfo MessageTransferChannel_GetAllRemoteRunnerInfoCallback()
67 {
68 List<CaseRunner> caseRunnerList = CaseRunnerList;
69 if (caseRunnerList == null)
70 {
71 return new RemoteRunnerInfo();
72 }
73 return GetRunnerInfo(caseRunnerList, true);
74 }
75
76 /// <summary>
77 /// 处理ExecuteService的远程指令【runner控制】
78 /// </summary>
79 void MessageTransferChannel_RunnerCommandCallback(ExecuteService sender, RunnerCommand command, List<int> runners)
80 {
81 List<CaseRunner> caseRunnerList=CaseRunnerList;
82 if(caseRunnerList==null)
83 {
84 return;
85 }
86 RunnerCommandExecute(caseRunnerList, command, runners);
87 }
88
89 /// <summary>
90 /// 触发更新CaseRunner状态双工回调(局部更新)
91 /// </summary>
92 /// <param name="caseRunnerList">CaseRunner 列表</param>
93 public void SendStateCallBack(List<CaseRunner> caseRunnerList)
94 {
95 SendStateInfo(caseRunnerList, false);
96 }
97
98 /// <summary>
99 /// 触发更新CaseRunner状态双工回调(全部更新)
100 /// </summary>
101 /// <param name="caseRunnerList">CaseRunner 列表</param>
102 public void SendAllStateCallBack(List<CaseRunner> caseRunnerList)
103 {
104 SendStateInfo(caseRunnerList, true);
105 }
106
107 /// <summary>
108 /// 像执行行体请求CaseRunner 最新列表
109 /// </summary>
110 private List<CaseRunner> CaseRunnerList
111 {
112 get
113 {
114 if (OnBackNowRunnerList() != null)
115 {
116 return OnBackNowRunnerList();
117 }
118 return null;
119 }
120 }
121
122 /// <summary>
123 /// 输出提示信息
124 /// </summary>
125 /// <param name="message"></param>
126 private void AddInfo(string message)
127 {
128 if(OnServerHostMessage!=null)
129 {
130 this.OnServerHostMessage("baseHost", message);
131 }
132 }
133
134 /// <summary>
135 /// 启动BaseHost
136 /// </summary>
137 public void OpenBaseHost()
138 {
139 if (baseHost == null)
140 {
141 baseHost = new ServiceHost(typeof(ExecuteService), baseAddress);
142
143 ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
144 smb.HttpGetEnabled = true;
145 smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
146
147 baseHost.Description.Behaviors.Add(smb);
148 //baseHost.AddServiceEndpoint(typeof(IExecuteService), new BasicHttpBinding(), "");
149 //baseHost.AddServiceEndpoint(typeof(IExecuteService), new WSDualHttpBinding(), "");
150
151 System.ServiceModel.Channels.Binding binding = new WSDualHttpBinding();
152 ((System.ServiceModel.WSDualHttpBinding)(binding)).Security.Mode = WSDualHttpSecurityMode.None;
153 ((System.ServiceModel.WSDualHttpBinding)(binding)).Security.Message.ClientCredentialType = MessageCredentialType.UserName;
154
155 System.ServiceModel.Channels.Binding tcpBinding = new NetTcpBinding();
156 ((System.ServiceModel.NetTcpBinding)(tcpBinding)).Security.Mode = SecurityMode.None;
157 ((System.ServiceModel.NetTcpBinding)(tcpBinding)).Security.Message.ClientCredentialType = MessageCredentialType.UserName;
158
159 //测试开安全双工只能在本机使用
160 baseHost.AddServiceEndpoint(typeof(IExecuteService), binding, "");
161
162 baseHost.Opening += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Opening"));
163 baseHost.Opened += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Opened"));
164 baseHost.Closed += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Closed"));
165 baseHost.Closing += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Closing"));
166 baseHost.Faulted += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Faulted"));
167
168
169 Thread openBaseThread = new Thread(new ThreadStart(BaseHostOpen));
170 openBaseThread.IsBackground = true;
171 openBaseThread.Start();
172
173 }
174 else
175 {
176 if (baseHost.State == CommunicationState.Opened)
177 {
178 AddInfo("服务已经开启");
179 }
180 else if (baseHost.State == CommunicationState.Opening)
181 {
182 AddInfo("服务正在开启");
183 }
184 else
185 {
186 baseHost.Abort();
187 baseHost = null;
188 OpenBaseHost();
189 }
190 }
191 }
192
193 private void BaseHostOpen()
194 {
195 try
196 {
197 baseHost.Open();
198 }
199 catch (Exception ex)
200 {
201 AddInfo(ex.Message);
202 }
203 }
204
205 /// <summary>
206 /// 关闭BaseHost
207 /// </summary>
208 public void CloseBaseHost()
209 {
210 if (baseHost == null)
211 {
212 AddInfo("未发现服务");
213 }
214 else
215 {
216 if (baseHost.State != CommunicationState.Closed)
217 {
218 AddInfo(baseAddress.ToString() + "服务关闭");
219 baseHost.Close();
220 }
221 else
222 {
223 AddInfo(baseAddress.ToString() + "服务已经关闭");
224 }
225 }
226 }
227
228
229 /// <summary>
230 /// 执行远程命令
231 /// </summary>
232 /// <param name="caseRunnerList">CaseRunner 列表</param>
233 /// <param name="command">命令类型</param>
234 /// <param name="Runners">受影响Runner</param>
235 private void RunnerCommandExecute(List<CaseRunner> caseRunnerList, RunnerCommand command, List<int> Runners)
236 {
237 switch (command)
238 {
239 case RunnerCommand.Start:
240 if (Runners != null)
241 {
242 if (Runners.Count > 0)
243 {
244 foreach (int tempRunnerIndex in Runners)
245 {
246 if (caseRunnerList.Count >= tempRunnerIndex)
247 {
248 caseRunnerList[tempRunnerIndex].RunQuiet();
249 }
250 else
251 {
252 AddInfo("tempRunnerIndex error");
253 }
254 }
255 }
256 }
257 else
258 {
259 AddInfo("Runners is null");
260 }
261 break;
262 case RunnerCommand.Stop:
263 if (Runners != null)
264 {
265 if (Runners.Count > 0)
266 {
267 foreach (int tempRunnerIndex in Runners)
268 {
269 if (caseRunnerList.Count >= tempRunnerIndex)
270 {
271 caseRunnerList[tempRunnerIndex].StopQuiet();
272 }
273 else
274 {
275 AddInfo("tempRunnerIndex error");
276 }
277 }
278 }
279 }
280 else
281 {
282 AddInfo("Runners is null");
283 }
284 break;
285 case RunnerCommand.Pause:
286 if (Runners != null)
287 {
288 if (Runners.Count > 0)
289 {
290 foreach (int tempRunnerIndex in Runners)
291 {
292 if (caseRunnerList.Count >= tempRunnerIndex)
293 {
294 caseRunnerList[tempRunnerIndex].PauseQuiet();
295 }
296 else
297 {
298 AddInfo("tempRunnerIndex error");
299 }
300 }
301 }
302 }
303 else
304 {
305 AddInfo("Runners is null");
306 }
307 break;
308 default:
309 break;
310 }
311 }
312
313 /// <summary>
314 /// 触发更新CaseRunner状态双工回调
315 /// </summary>
316 /// <param name="caseRunnerList">CaseRunner 列表</param>
317 /// <param name="isAll">是否全部更新</param>
318 private void SendStateInfo(List<CaseRunner> caseRunnerList,bool isAll)
319 {
320 if (BaseHostState != CommunicationState.Opened)
321 {
322 return;
323 }
324 if (caseRunnerList.Count > 0)
325 {
326 RemoteRunnerInfo remoteRunnerInfo = GetRunnerInfo(caseRunnerList,isAll);
327 if (remoteRunnerInfo.RunnerStateList != null)
328 {
329 if (remoteRunnerInfo.RunnerStateList.Count > 0)
330 {
331 if (MessageTransferChannel.OnRunnerInfoCallback != null)
332 {
333 MessageTransferChannel.OnRunnerInfoCallback(remoteRunnerInfo);
334 }
335 }
336 }
337 }
338 }
339
340 /// <summary>
341 /// 获取List<CaseRunner>中的更新信息
342 /// </summary>
343 /// <param name="runnerList">CaseRunner 列表</param>
344 /// <param name="isUpdataAll">T表示全部更新,F标识局部更新</param>
345 /// <returns>更新信息</returns>
346 private RemoteRunnerInfo GetRunnerInfo(List<CaseRunner> runnerList, bool isUpdataAll)
347 {
348 RemoteRunnerInfo remoteRunnerInfo = new RemoteRunnerInfo();
349 if (runnerList == null)
350 {
351 return null;
352 }
353 foreach (CaseRunner tempRunner in runnerList)
354 {
355 if (tempRunner.IsNeedUpdata || isUpdataAll)
356 {
357 tempRunner.IsNeedUpdata = false;
358
359 RunnerState tempRunnerState = new RunnerState();
360 if (tempRunner.RunerActuator.NowExecutionResultList != null)
361 {
362 if (tempRunner.RunerActuator.NowExecutionResultList.Count > 0)
363 {
364 myExecutionDeviceResult tempResult = tempRunner.RunerActuator.NowExecutionResultList[tempRunner.RunerActuator.NowExecutionResultList.Count - 1];
365 tempRunnerState.RunnerID = runnerList.IndexOf(tempRunner);
366 tempRunnerState.RunnerName = tempRunner.RunnerName;
367 tempRunnerState.NowCell = tempResult.caseId.ToString();
368 tempRunnerState.CellResult = tempResult.result.ToString();
369 tempRunnerState.State = tempRunner.RunnerState.ToString();
370 tempRunnerState.Time = tempResult.spanTime;
371 tempRunnerState.RunDetails = tempResult.backContent;
372 tempRunnerState.RunnerProgress = tempRunner.RunerActuator.RunProgress;
373 remoteRunnerInfo.AddRunnerState(tempRunnerState);
374 }
375 else if (isUpdataAll)
376 {
377 tempRunnerState.RunnerID = runnerList.IndexOf(tempRunner);
378 tempRunnerState.RunnerName = tempRunner.RunnerName;
379 tempRunnerState.NowCell = "";
380 tempRunnerState.CellResult = "";
381 tempRunnerState.State = tempRunner.RunnerState.ToString();
382 tempRunnerState.Time = "";
383 tempRunnerState.RunDetails = "";
384 tempRunnerState.RunnerProgress = tempRunner.RunerActuator.RunProgress;
385 remoteRunnerInfo.AddRunnerState(tempRunnerState);
386 }
387 else
388 {
389 tempRunnerState.RunnerID = runnerList.IndexOf(tempRunner);
390 tempRunnerState.RunnerName = tempRunner.RunnerName;
391 tempRunnerState.State = tempRunner.RunnerState.ToString();
392 tempRunnerState.RunnerProgress = tempRunner.RunerActuator.RunProgress;
393 remoteRunnerInfo.AddRunnerState(tempRunnerState);
394 }
395 }
396 }
397 }
398 return remoteRunnerInfo;
399 }
400
401 /// <summary>
402 /// 获取List<CaseRunner>中的更新信息(局部更新)
403 /// </summary>
404 /// <param name="runnerList">CaseRunner 列表</param>
405 /// <returns>更新信息</returns>
406 private RemoteRunnerInfo GetRunnerInfo(List<CaseRunner> runnerList)
407 {
408 return GetRunnerInfo(runnerList, false);
409 }
410
411 }
最后放几个动画,简单演示下Auto组件的部分功能