一个基于.NET平台的自动化/压力测试系统设计简述
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
1 /******************************************************************************* 2 * Copyright (c) 2015 lijie 3 * All rights reserved. 4 * 5 * 文件名称: 6 * 内容摘要: mycllq@hotmail.com 7 * 8 * 历史记录: 9 * 日 期: 201505016 创建人: lijie8118054@126.com 10 * 描 述: 创建 11 *******************************************************************************/ 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。有兴趣的可以看看下面的实现(贴出的只包含关键部分)
1 class RunCaseCount 2 { 3 /// <summary> 4 /// for count strut 5 /// </summary> 6 struct CaseLoopCountInfo 7 { 8 CaseCell loopNode; 9 int caseRate; 10 11 /// <summary> 12 /// Initialization the CaseLoopCountInfo 13 /// </summary> 14 /// <param name="yourLoopNode">your LoopNode</param> 15 /// <param name="yourCaseRate">your CaseRate</param> 16 public CaseLoopCountInfo(CaseCell yourLoopNode, int yourCaseRate) 17 { 18 loopNode = yourLoopNode; 19 caseRate = yourCaseRate; 20 } 21 22 /// <summary> 23 /// get the LoopNode 24 /// </summary> 25 public CaseCell LoopNode 26 { 27 get { return loopNode; } 28 } 29 30 /// <summary> 31 /// get the CaseRate 32 /// </summary> 33 public int CaseRate 34 { 35 get { return caseRate; } 36 } 37 } 38 39 40 /// <summary> 41 /// get main task case count(just main but not Include the goto case) 42 /// </summary> 43 /// <param name="startNode">start Node</param> 44 /// <returns>count</returns> 45 public static int GetCount(CaseCell startNode) 46 { 47 int nowCount = 0; 48 List<CaseLoopCountInfo> nowLoops = new List<CaseLoopCountInfo>(); 49 while (startNode!=null) 50 { 51 if (startNode.CaseType == CaseType.Case) 52 { 53 nowCount++; 54 } 55 else if (startNode.CaseType == CaseType.Repeat) 56 { 57 if (startNode.IsHasChild) 58 { 59 myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(startNode.CaseXmlNode); 60 nowLoops.Add(new CaseLoopCountInfo(startNode.ChildCells[0], tempProjectLoadInfo.times)); 61 } 62 } 63 else if (startNode.CaseType == CaseType.Project) 64 { 65 if(startNode.IsHasChild) 66 { 67 startNode = startNode.ChildCells[0]; 68 } 69 continue; 70 } 71 startNode = startNode.NextCell; 72 } 73 while (nowLoops.Count!=0) 74 { 75 startNode = nowLoops[nowLoops.Count - 1].LoopNode; 76 int tempRate = nowLoops[nowLoops.Count - 1].CaseRate; 77 nowLoops.Remove(nowLoops[nowLoops.Count - 1]); 78 while (startNode != null) 79 { 80 if (startNode.CaseType == CaseType.Case) 81 { 82 nowCount += tempRate; 83 } 84 else if (startNode.CaseType == CaseType.Repeat) 85 { 86 if (startNode.IsHasChild) 87 { 88 myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(startNode.CaseXmlNode); 89 nowLoops.Add(new CaseLoopCountInfo(startNode.ChildCells[0], tempProjectLoadInfo.times * tempRate)); 90 } 91 } 92 startNode = startNode.NextCell; 93 } 94 } 95 return nowCount; 96 } 97 98 99 } 100 101 102 /// <summary> 103 /// CsaeQueue it will only used in myCaseRunTime 104 /// </summary> 105 class myCsaeQueue 106 { 107 private CaseCell startCaseNode; 108 private CaseCell nowCaseNode; 109 List<myCaseLoop> myCaseLoopList; 110 111 private int queueTotalCount; 112 private int queueNowCount; 113 114 public event delegateLoopChangeEventHandler OnLoopChangeEvent; 115 116 /// <summary> 117 /// myCsaeQueue initialize 118 /// </summary> 119 /// <param name="yourStartCase">your StartCase and make sure it is not null</param> 120 public myCsaeQueue(CaseCell yourStartCase) 121 { 122 queueTotalCount = RunCaseCount.GetCount(yourStartCase); 123 startCaseNode = yourStartCase; 124 nowCaseNode = null; 125 myCaseLoopList = new List<myCaseLoop>(); 126 } 127 128 /// <summary> 129 /// get now CaseCell 130 /// </summary> 131 public CaseCell NowCaseNode 132 { 133 get 134 { 135 if (nowCaseNode != null) 136 { 137 if (myCaseLoopList.Count > 0) 138 { 139 return myCaseLoopList[myCaseLoopList.Count - 1].NowCaseNode; 140 } 141 else 142 { 143 return nowCaseNode; 144 } 145 } 146 else 147 { 148 return startCaseNode; 149 } 150 } 151 } 152 153 /// <summary> 154 /// get the Queue Count Progress(queueTotalCount and queueNowCount) 155 /// </summary> 156 public KeyValuePair<int,int> GetCountProgress 157 { 158 get 159 { 160 return new KeyValuePair<int, int>(queueTotalCount, queueNowCount); 161 } 162 } 163 164 165 /// <summary> 166 /// i will add new CaseLoop and Subscribe 【OnLoopChangeEvent】 167 /// </summary> 168 /// <param name="yourStartCase">your StartCase</param> 169 /// <param name="yourTimes">your Times</param> 170 private void AddCaseLoop(CaseCell yourStartCase, int yourTimes) 171 { 172 myCaseLoopList.Add(new myCaseLoop(yourStartCase, yourTimes)); 173 myCaseLoopList[myCaseLoopList.Count - 1].OnLoopChangeEvent += OnLoopChangeEvent; 174 } 175 176 /// <summary> 177 /// i will remove your CaseLoop and unSubscribe 【OnLoopChangeEvent】 178 /// </summary> 179 /// <param name="yourCaseLoop">yourCaseLoop</param> 180 private void DelCaseLoop(myCaseLoop yourCaseLoop) 181 { 182 yourCaseLoop.OnLoopChangeEvent -= OnLoopChangeEvent; 183 myCaseLoopList.Remove(yourCaseLoop); 184 } 185 186 187 /// <summary> 188 /// i will get the next myTreeTagInfo in my queue 189 /// </summary> 190 /// <returns>the CaseCell you want</returns> 191 public CaseCell nextCase() 192 { 193 194 if(nowCaseNode==null) //起始节点 195 { 196 nowCaseNode = startCaseNode; 197 if (nowCaseNode.CaseType == CaseType.Repeat) 198 { 199 if (nowCaseNode.IsHasChild) 200 { 201 myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(nowCaseNode.CaseXmlNode); 202 AddCaseLoop(nowCaseNode.ChildCells[0], tempProjectLoadInfo.times); 203 } 204 return nextCase(); 205 } 206 else if (nowCaseNode.CaseType == CaseType.Case) 207 { 208 queueNowCount++; 209 return nowCaseNode; 210 } 211 else if (nowCaseNode.CaseType == CaseType.Project) 212 { 213 if (nowCaseNode.IsHasChild) 214 { 215 startCaseNode = nowCaseNode.ChildCells[0]; 216 nowCaseNode = null; 217 return nextCase(); 218 } 219 return null; //空Project 220 } 221 else 222 { 223 return null; //当前设计不会有这种情况 224 } 225 } 226 else 227 { 228 if (myCaseLoopList.Count > 0) 229 { 230 int tempNowListIndex = myCaseLoopList.Count - 1; 231 CaseCell tempNextLoopTreeNode = myCaseLoopList[tempNowListIndex].nextCase(); 232 if (tempNextLoopTreeNode == null) 233 { 234 DelCaseLoop(myCaseLoopList[tempNowListIndex]); 235 return nextCase(); 236 } 237 else 238 { 239 if (tempNextLoopTreeNode.CaseType == CaseType.Repeat) 240 { 241 if (tempNextLoopTreeNode.IsHasChild) 242 { 243 myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(tempNextLoopTreeNode.CaseXmlNode); 244 AddCaseLoop(tempNextLoopTreeNode.ChildCells[0], tempProjectLoadInfo.times); 245 } 246 247 return nextCase(); 248 } 249 else if (tempNextLoopTreeNode.CaseType == CaseType.Case) 250 { 251 queueNowCount++; 252 return tempNextLoopTreeNode; 253 } 254 else 255 { 256 return null; //当前设计不会有这种情况 257 } 258 } 259 } 260 else 261 { 262 if(nowCaseNode.NextCell == null) 263 { 264 return null; //当前 【Queue】 结束 265 } 266 else 267 { 268 nowCaseNode = nowCaseNode.NextCell; 269 if (nowCaseNode.CaseType == CaseType.Repeat) 270 { 271 if (nowCaseNode.IsHasChild) 272 { 273 myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(nowCaseNode.CaseXmlNode); 274 AddCaseLoop(nowCaseNode.ChildCells[0], tempProjectLoadInfo.times); 275 } 276 277 return nextCase(); 278 } 279 else if (nowCaseNode.CaseType == CaseType.Case) 280 { 281 queueNowCount++; 282 return nowCaseNode; 283 } 284 else 285 { 286 return null; //当前设计不会有这种情况 287 } 288 } 289 } 290 } 291 } 292 293 } 294 295 /// <summary> 296 /// CaseLoop it will only used in myCsaeQueue 297 /// </summary> 298 class myCaseLoop 299 { 300 private CaseCell startCaseNode; 301 private CaseCell nowCaseNode; 302 private int totalTimes; 303 private int myTimes; 304 305 public event delegateLoopChangeEventHandler OnLoopChangeEvent; 306 307 /// <summary> 308 /// myCaseLoop initialize 309 /// </summary> 310 /// <param name="yourStartCase">your StartCase and make sure it is not null</param> 311 /// <param name="yourTimes">your Times </param> 312 public myCaseLoop(CaseCell yourStartCase, int yourTimes) 313 { 314 totalTimes = myTimes = yourTimes; 315 startCaseNode = yourStartCase; 316 nowCaseNode = null; 317 } 318 319 /// <summary> 320 /// get now CaseCell 321 /// </summary> 322 public CaseCell NowCaseNode 323 { 324 get 325 { 326 if (nowCaseNode != null) 327 { 328 return nowCaseNode; 329 } 330 else 331 { 332 return startCaseNode; 333 } 334 } 335 } 336 337 /// <summary> 338 /// i will trigger 【OnLoopChangeEvent】 339 /// </summary> 340 /// <param name="yourTarget"></param> 341 private void ReportLoopProgress(CaseCell yourTarget) 342 { 343 if (OnLoopChangeEvent != null) 344 { 345 OnLoopChangeEvent(yourTarget.ParentCell, string.Format("{0}/{1}", totalTimes, totalTimes - myTimes + 1)); 346 } 347 } 348 349 /// <summary> 350 /// i will trigger 【OnLoopChangeEvent】 and this lood is end 351 /// </summary> 352 /// <param name="yourTarget"></param> 353 private void ReportLoopEnd(CaseCell yourTarget) 354 { 355 if (OnLoopChangeEvent != null) 356 { 357 this.OnLoopChangeEvent(yourTarget.ParentCell, ""); 358 } 359 } 360 361 /// <summary> 362 /// i will get the next myTreeTagInfo in my loop 363 /// </summary> 364 /// <returns>the CaseCell you want</returns> 365 public CaseCell nextCase() 366 { 367 if (myTimes > 0) 368 { 369 if (nowCaseNode == null) //起始节点 370 { 371 nowCaseNode = startCaseNode; 372 //report position 373 ReportLoopProgress(nowCaseNode); 374 return nowCaseNode; 375 } 376 else 377 { 378 if (nowCaseNode.NextCell == null) 379 { 380 myTimes--; 381 if (myTimes > 0) 382 { 383 nowCaseNode = startCaseNode; 384 ReportLoopProgress(nowCaseNode); 385 return nowCaseNode; 386 } 387 else 388 { 389 ReportLoopEnd(nowCaseNode); 390 return null; //此处为null,指示当前【Loop】结束 391 } 392 393 } 394 else 395 { 396 nowCaseNode = nowCaseNode.NextCell; 397 return nowCaseNode; //此处caseType可能为case或repeat,该类的拥有者将会分别处理 398 } 399 } 400 } 401 else 402 { 403 return null; 404 } 405 } 406 } 407 408 /// <summary> 409 /// myCaseRunTime - you can get next case here 410 /// </summary> 411 public sealed class myCaseRunTime 412 { 413 414 private List<myCsaeQueue> myCsaeQueueList; 415 private bool isThroughAllCase; 416 417 /// <summary> 418 /// show loop track 419 /// </summary> 420 public event delegateLoopChangeEventHandler OnLoopChangeEvent; 421 /// <summary> 422 /// show Queue track (the frist and last Queue will nor trigger) 423 /// </summary> 424 public event delegateQueueChangeEventHandler OnQueueChangeEvent; 425 426 /// <summary> 427 /// myCaseRunTime initialize 428 /// </summary> 429 public myCaseRunTime() 430 { 431 myCsaeQueueList = new List<myCsaeQueue>(); 432 } 433 434 /// <summary> 435 /// get now CaseRunTime all Progress 436 /// </summary> 437 public List<KeyValuePair<int ,int >> GetNowCountProgress 438 { 439 get 440 { 441 List<KeyValuePair<int, int>> nowCountProgress = new List<KeyValuePair<int, int>>(); 442 foreach (var tempCsaeQueue in myCsaeQueueList) 443 { 444 nowCountProgress.Add(tempCsaeQueue.GetCountProgress); 445 } 446 return nowCountProgress; 447 } 448 } 449 450 /// <summary> 451 /// i will add new CsaeQueue and Subscribe 【OnLoopChangeEvent】 452 /// </summary> 453 /// <param name="yourCsaeQueue">your CsaeQueue that will add</param> 454 private void AddCsaeQueue(myCsaeQueue yourCsaeQueue) 455 { 456 myCsaeQueueList.Add(yourCsaeQueue); 457 yourCsaeQueue.OnLoopChangeEvent += OnLoopChangeEvent; 458 } 459 460 //// <summary> 461 /// i will add new CsaeQueue and Subscribe 【OnLoopChangeEvent】(and will trigger【OnQueueChangeEvent】) 462 /// </summary> 463 /// <param name="yourCsaeQueue">your CsaeQueue that will add</param> 464 /// <param name="yourProjectId">Project Id to OnQueueChangeEvent</param> 465 /// <param name="yourCaseId">Case Id to OnQueueChangeEvent</param> 466 private void AddCsaeQueue(myCsaeQueue yourCsaeQueue, int yourProjectId, int yourCaseId) 467 { 468 ReportQueueAction(myCsaeQueueList[myCsaeQueueList.Count - 1].NowCaseNode, string.Format("▼GoTo Project:{0} Case:{1}", yourProjectId, yourCaseId)); 469 AddCsaeQueue(yourCsaeQueue); 470 ReportQueueAction(myCsaeQueueList[myCsaeQueueList.Count - 1].NowCaseNode, "▲"); 471 } 472 473 /// <summary> 474 /// i will remove the CaseQueue and unSubscribe 【OnLoopChangeEvent】 475 /// </summary> 476 /// <param name="yourCsaeQueue">your CsaeQueue that will rwmove</param> 477 private void DelCsaeQueue(myCsaeQueue yourCsaeQueue) 478 { 479 if (myCsaeQueueList.Count>1) 480 { 481 ReportQueueAction(yourCsaeQueue.NowCaseNode, "▼"); 482 ReportQueueAction(myCsaeQueueList[myCsaeQueueList.Count - 2].NowCaseNode, "▼▲"); 483 } 484 yourCsaeQueue.OnLoopChangeEvent -= OnLoopChangeEvent; 485 myCsaeQueueList.Remove(yourCsaeQueue); 486 } 487 488 489 /// <summary> 490 /// i will report the QueueAction to his user 491 /// </summary> 492 /// <param name="yourTarget">your CaseCell Target</param> 493 /// <param name="yourMessage">your Message</param> 494 private void ReportQueueAction(CaseCell yourTarget, string yourMessage) 495 { 496 if (OnQueueChangeEvent != null) 497 { 498 OnQueueChangeEvent(yourTarget, yourMessage); 499 } 500 } 501 502 503 /// <summary> 504 /// you must readyStart before get nextCase (and here also can reset the StartCase) 505 /// </summary> 506 /// <param name="yourStartCase">your StartCase</param> 507 public void readyStart(CaseCell yourStartCase) 508 { 509 myCsaeQueueList.Clear(); 510 AddCsaeQueue(new myCsaeQueue(yourStartCase)); 511 ReportQueueAction(yourStartCase, "◆"); 512 } 513 514 515 /// <summary> 516 /// you must readyStart before get nextCase (and here also can reset the StartCase) 517 /// </summary> 518 /// <param name="yourStartCase">your StartCase</param> 519 /// <param name="yourIsThrough">it will change the behaviour that is it will go through all case(now it is replaced by [goto])</param> 520 public void readyStart(CaseCell yourStartCase, bool yourIsThrough) 521 { 522 readyStart(yourStartCase); 523 isThroughAllCase = yourIsThrough; 524 } 525 526 /// <summary> 527 /// i will get the next myTreeTagInfo in myCaseRunTime 528 /// </summary> 529 /// <returns>the CaseCell you want</returns> 530 public CaseCell nextCase() 531 { 532 if (myCsaeQueueList.Count > 0) 533 { 534 CaseCell tempTreeNodeCase = myCsaeQueueList[myCsaeQueueList.Count - 1].nextCase(); 535 if(tempTreeNodeCase==null) 536 { 537 DelCsaeQueue(myCsaeQueueList[myCsaeQueueList.Count - 1]); 538 return nextCase(); 539 } 540 else 541 { 542 return tempTreeNodeCase; 543 } 544 } 545 else 546 { 547 return null; 548 } 549 } 550 551 /// <summary> 552 /// here i will jump into other case in myCaseRunTime 553 /// </summary> 554 /// <param name="yourProjectId">your Project Id</param> 555 /// <param name="yourCaseId">your Case Id</param> 556 /// <returns>is success</returns> 557 public bool gotoMyCase(int yourProjectId, int yourCaseId, Dictionary<int, Dictionary<int, CaseCell>> myRunTimeCaseDictionary) 558 { 559 if (myRunTimeCaseDictionary.ContainsKey(yourProjectId)) 560 { 561 if (myRunTimeCaseDictionary[yourProjectId].ContainsKey(yourCaseId)) 562 { 563 AddCsaeQueue(new myCsaeQueue(myRunTimeCaseDictionary[yourProjectId][yourCaseId]), yourProjectId, yourCaseId); 564 return true; 565 } 566 else 567 { 568 ReportQueueAction(myCsaeQueueList[myCsaeQueueList.Count - 1].NowCaseNode, "▼GoTo error"); 569 return false; 570 } 571 } 572 else 573 { 574 return false; 575 } 576 } 577 }
而最终myCaseRunTime也是为CaseActionActuator 服务的,现在来看下CaseActionActuator。
CaseActionActuator相对比较多一点,因为要完成的功能会多一些,跟其他模块的联系也会大一些
这个可能看起来就很乱了,上图的模块主要就是一个Case文件的在系统中的表现,可以理解为一个User,这个User通过Case脚本文件可以执行一套业务,执行过程也是独立的,环境,线程,数据也都是独立的。所以可以创建任意多个这种模块以模拟大量的用户同时操作,当然脚本可以使用不同的脚本文件,也可以使用相同脚本文件(若使用相同脚本文件系统会对当前模块进行深度克隆,克隆的用户共享部分不会影响运行的数据)。该模块还可以选择以Cell对UI控件进行绑定,以达到执行过程中用户界面的友好反馈,当然不同的UI控件的动态效果需要单独的处理(处理由另一个辅助模块myActionActuator完成)
这个模块的图起来乱点,不过code相对清晰,有兴趣可以看下面代码(贴出的是关键部分)
1 /// <summary> 2 /// CASE执行器 3 /// </summary> 4 public class CaseActionActuator:IDisposable,ICloneable 5 { 6 #region Private Class 7 /// <summary> 8 /// 描述执行可能所需要的附加信息(可扩展),可以为null 9 /// </summary> 10 private class ExecutiveAdditionalInfo 11 { 12 private bool isRetry; 13 private int tryTimes; 14 private bool isStoping; 15 private bool isTryCase; 16 17 public ExecutiveAdditionalInfo(bool yourIstry,int yourTryTimes) 18 { 19 isRetry = yourIstry; 20 tryTimes = yourTryTimes; 21 isStoping = false; 22 isTryCase = false; 23 } 24 25 public ExecutiveAdditionalInfo(bool yourStoping) 26 { 27 isRetry = false; 28 isTryCase = false; 29 tryTimes = -98; 30 isStoping = yourStoping; 31 } 32 33 public ExecutiveAdditionalInfo(bool yourStoping, bool yourTryCase) 34 { 35 isRetry = false; 36 isTryCase = yourTryCase; 37 tryTimes = -98; 38 isStoping = yourStoping; 39 } 40 41 public bool IsReTry 42 { 43 get 44 { 45 return isRetry; 46 } 47 set 48 { 49 isRetry = value; 50 } 51 } 52 53 public bool IsTryCase 54 { 55 get 56 { 57 return isTryCase; 58 } 59 } 60 61 public int TryTimes 62 { 63 get 64 { 65 return tryTimes; 66 } 67 set 68 { 69 tryTimes = value; 70 } 71 } 72 73 public bool IsStoping 74 { 75 get 76 { 77 return isStoping; 78 } 79 } 80 } 81 82 /// <summary> 83 /// 描述单次执行所需的基本数据集 84 /// </summary> 85 private class ExecutivebasicData 86 { 87 myRunCaseData<ICaseExecutionContent> runCaseData; 88 TreeNode executiveNode; 89 90 public ExecutivebasicData(myRunCaseData<ICaseExecutionContent> yourCaseData,TreeNode yourExecutiveNode) 91 { 92 runCaseData = yourCaseData; 93 executiveNode = yourExecutiveNode; 94 } 95 } 96 97 #endregion 98 99 /// <summary> 100 /// 克隆Actuator的根 101 /// </summary> 102 private CaseActionActuator rootActuator; 103 104 /// <summary> 105 /// 执行线程同步器 106 /// </summary> 107 private ManualResetEvent myManualResetEvent = new ManualResetEvent(true); 108 109 /// <summary> 110 /// 执行器名称 111 /// </summary> 112 private string myName; 113 114 /// <summary> 115 /// Actuator State 116 /// </summary> 117 private CaseActuatorState runState; 118 119 120 /// <summary> 121 /// case guide diver 122 /// </summary> 123 private myCaseRunTime caseRunTime; 124 125 /// <summary> 126 /// ExecutionDevice List with his name【执行驱动器映射表】 127 /// </summary> 128 private Dictionary<string, ICaseExecutionDevice> myExecutionDeviceList; 129 130 /// <summary> 131 /// Parameter List 132 /// </summary> 133 private Dictionary<string, string> runActuatorParameterList; 134 135 /// <summary> 136 /// StaticData List 137 /// </summary> 138 private Dictionary<string, IRunTimeStaticData> runActuatorStaticDataList; 139 140 /// <summary> 141 /// Execution Result List 142 /// </summary> 143 private List<myExecutionDeviceResult> runExecutionResultList; 144 145 /// <summary> 146 /// RunTimeCaseDictionary 147 /// </summary> 148 private Dictionary<int, Dictionary<int, CaseCell>> runTimeCaseDictionary; 149 150 /// <summary> 151 /// ProjctCollection 152 /// </summary> 153 private ProjctCollection runCellProjctCollection; 154 155 /// <summary> 156 /// Actuator Task Thread 157 /// </summary> 158 private Thread myActuatorTaskThread; 159 private Thread myActuatorTryThread; 160 161 /// <summary> 162 /// the thread not used and do not make it out of control 163 /// </summary> 164 private List<Thread> invalidThreadList; 165 166 private string nowExecutiveData; 167 private string myErrorInfo; 168 169 private int executiveThinkTime; 170 private int caseThinkTime; 171 172 public delegate void delegateGetExecutiveDataEventHandler(string yourTitle, string yourContent); 173 public delegate void delegateGetActionErrorEventHandler(string yourContent); 174 public delegate void delegateGetExecutiveResultEventHandler(string sender, myExecutionDeviceResult yourResult); 175 public delegate void delegateGetActuatorStateEventHandler(string sender, CaseActuatorState yourState); 176 public delegate void delegateActuatorParameterListEventHandler(); 177 178 179 public event delegateGetExecutiveData OnGetExecutiveData; 180 public event delegateGetExecutiveData OnGetActionError; 181 public event delegateGetExecutiveResultEventHandler OnExecutiveResult; 182 public event delegateGetActuatorStateEventHandler OnActuatorStateChanged; 183 public delegateActuatorParameterListEventHandler OnActuatorParameterListChanged; //外部需要访问 event修饰后,会禁止非创建类服务 184 185 186 /// <summary> 187 /// 构造函数 188 /// </summary> 189 public CaseActionActuator() 190 { 191 rootActuator = null; 192 myExecutionDeviceList = new Dictionary<string, ICaseExecutionDevice>(); 193 runActuatorParameterList = new Dictionary<string, string>(); 194 runActuatorStaticDataList = new Dictionary<string, IRunTimeStaticData>(); 195 runExecutionResultList = new List<myExecutionDeviceResult>(); 196 invalidThreadList = new List<Thread>(); 197 myErrorInfo = ""; 198 myName = "Main Actuator"; 199 runState = CaseActuatorState.Stop; 200 executiveThinkTime = 0; 201 } 202 203 /// <summary> 204 /// 构造函数 205 /// </summary> 206 /// <param name="yourName">当前执行器的名称</param> 207 public CaseActionActuator(string yourName) 208 { 209 rootActuator = null; 210 myExecutionDeviceList = new Dictionary<string, ICaseExecutionDevice>(); 211 runActuatorParameterList = new Dictionary<string, string>(); 212 runActuatorStaticDataList = new Dictionary<string, IRunTimeStaticData>(); 213 runExecutionResultList = new List<myExecutionDeviceResult>(); 214 invalidThreadList = new List<Thread>(); 215 myErrorInfo = ""; 216 myName = yourName; 217 runState = CaseActuatorState.Stop; ; 218 } 219 220 /// <summary> 221 /// 克隆 222 /// </summary> 223 /// <returns>克隆对象</returns> 224 public object Clone() 225 { 226 CaseActionActuator cloneActuator = new CaseActionActuator(); 227 cloneActuator.rootActuator = null; 228 cloneActuator.myExecutionDeviceList = myExecutionDeviceList.MyClone(); 229 cloneActuator.runActuatorParameterList = runActuatorParameterList.MyClone<string,string>(); 230 cloneActuator.runActuatorStaticDataList = runActuatorStaticDataList.MyClone(); 231 //cloneActuator.runExecutionResultList = new List<myExecutionDeviceResult>(); 232 cloneActuator.SetCaseRunTime(this.runTimeCaseDictionary, this.runCellProjctCollection); 233 cloneActuator.caseThinkTime = this.caseThinkTime; 234 return cloneActuator; 235 } 236 237 238 239 /// <summary> 240 /// 获取或设置执行器标识名 241 /// </summary> 242 public string MyName 243 { 244 get 245 { 246 return myName; 247 } 248 set 249 { 250 myName = value; 251 } 252 } 253 254 /// <summary> 255 /// 获取或设置执行器的全局思考/等待时间 256 /// </summary> 257 public int ExecutiveThinkTime 258 { 259 get 260 { 261 return executiveThinkTime; 262 } 263 set 264 { 265 executiveThinkTime = value; 266 } 267 } 268 269 /// <summary> 270 /// 获取【CaseActionActuator】运行状态 271 /// </summary> 272 public CaseActuatorState Runstate 273 { 274 get 275 { 276 return runState; 277 } 278 } 279 280 /// <summary> 281 /// 获取当前任务执行进度 282 /// </summary> 283 public List<KeyValuePair<int ,int >> RunProgress 284 { 285 get 286 { 287 if (caseRunTime != null) 288 { 289 return caseRunTime.GetNowCountProgress; 290 } 291 else 292 { 293 return null; 294 } 295 } 296 } 297 298 /// <summary> 299 /// 获取ErrorInfo属性 300 /// </summary> 301 public string ErrorInfo 302 { 303 get 304 { 305 return myErrorInfo; 306 } 307 } 308 309 /// <summary> 310 /// 获取执行过程 311 /// </summary> 312 public string NowExecutiveData 313 { 314 get 315 { 316 return nowExecutiveData; 317 318 } 319 } 320 321 /// <summary> 322 /// 获取当前任务执行结果列表 323 /// </summary> 324 public List<myExecutionDeviceResult> NowExecutionResultList 325 { 326 get 327 { 328 return runExecutionResultList; 329 } 330 } 331 332 /// <summary> 333 /// 获取当前参数化数据列表 334 /// </summary> 335 public Dictionary<string, string> NowParameterList 336 { 337 get 338 { 339 return runActuatorParameterList; 340 } 341 } 342 343 /// <summary> 344 /// 获取当前静态参数化数据列表 345 /// </summary> 346 public Dictionary<string, IRunTimeStaticData> NowStaticDataList 347 { 348 get 349 { 350 return runActuatorStaticDataList; 351 } 352 } 353 354 /// <summary> 355 /// 获取当前执行器列表 356 /// </summary> 357 public Dictionary<string, ICaseExecutionDevice> NowExecutionDeviceList 358 { 359 get 360 { 361 return myExecutionDeviceList; 362 } 363 } 364 365 /// <summary> 366 /// 获取当前CASE列表 367 /// </summary> 368 public Dictionary<int, Dictionary<int, CaseCell>> RunTimeCaseDictionary 369 { 370 get 371 { 372 return runTimeCaseDictionary; 373 } 374 } 375 376 /// <summary> 377 /// 获取当前ProjctCollection 378 /// </summary> 379 public ProjctCollection RunCellProjctCollection 380 { 381 get 382 { 383 return runCellProjctCollection; 384 } 385 } 386 387 /// <summary> 388 /// 获取当前执行器是否填充过数据 389 /// </summary> 390 public bool IsActuatorDataFill 391 { 392 get 393 { 394 return ((runCellProjctCollection != null) && (runCellProjctCollection != null)); 395 } 396 } 397 398 /// <summary> 399 /// i can updata you myRunTimeCaseDictionary() 400 /// </summary> 401 /// <param name="yourCaseDictionary">you myRunTimeCaseDictionary</param> 402 public void UpdataRunTimeCaseDictionary(Dictionary<int, Dictionary<int, CaseCell>> yourCaseDictionary) 403 { 404 runTimeCaseDictionary = yourCaseDictionary; 405 } 406 407 //RunTime Queue队列变化时通知 408 void caseRunTime_OnQueueChangeEvent(CaseCell yourTarget, string yourMessage) 409 { 410 if (yourMessage != "") 411 { 412 if (yourMessage.StartsWith("▲")) 413 { 414 //附加任务起始节点 415 myActionActuator.SetCaseNodeExpand(yourTarget); 416 } 417 else if (yourMessage.StartsWith("◆")) 418 { 419 //主任务起始节点 420 while(yourTarget.CaseType!=CaseType.Case) 421 { 422 if(yourTarget.IsHasChild) 423 { 424 yourTarget=yourTarget.ChildCells[0]; 425 } 426 else 427 { 428 break; 429 } 430 } 431 myActionActuator.SetCaseNodeExpand(yourTarget); 432 } 433 yourMessage = "【" + yourMessage + "】"; 434 } 435 436 myActionActuator.SetCaseNodeLoopChange(yourTarget, yourMessage); 437 } 438 439 //RunTime Queue 中Loop变化时通知 440 void caseRunTime_OnLoopChangeEvent(CaseCell yourTarget, string yourMessage) 441 { 442 if (yourMessage!="") 443 { 444 myActionActuator.SetCaseNodeExpand(yourTarget); 445 myActionActuator.SetCaseNodeLoopRefresh(yourTarget); 446 yourMessage = "【" + yourMessage + "】"; 447 } 448 myActionActuator.SetCaseNodeLoopChange(yourTarget, yourMessage); 449 } 450 451 452 /// <summary> 453 /// i will load your ActionActuator (if your have another rule file ,please override or add a new realize) 454 /// </summary> 455 /// <param name="sourceNode">source Node</param> 456 public void LoadScriptRunTime(XmlNode sourceNode) 457 { 458 if (sourceNode != null) 459 { 460 if (sourceNode.HasChildNodes) 461 { 462 foreach (XmlNode tempNode in sourceNode.ChildNodes) 463 { 464 switch (tempNode.Name) 465 { 466 #region RunTimeParameter 467 case "RunTimeParameter": 468 if (tempNode.HasChildNodes) 469 { 470 foreach (XmlNode tempNodeChild in tempNode.ChildNodes) 471 { 472 if (tempNodeChild.Name == "NewParameter") 473 { 474 if (tempNodeChild.Attributes["name"] != null) 475 { 476 AddRunActuatorParameter(tempNodeChild.Attributes["name"].Value, tempNodeChild.InnerText); 477 } 478 else 479 { 480 SetNowActionError("can not find name in ScriptRunTime - RunTimeParameter"); 481 } 482 } 483 else 484 { 485 SetNowActionError("find unkonw data in ScriptRunTime - RunTimeParameter"); 486 } 487 } 488 } 489 break; 490 #endregion 491 492 #region RunTimeActuator 493 case "RunTimeActuator": 494 if (tempNode.HasChildNodes) 495 { 496 string tempActuatorName = ""; 497 CaseProtocol tempActuatorProtocol = CaseProtocol.unknownProtocol; 498 foreach (XmlNode tempNodeChild in tempNode.ChildNodes) 499 { 500 if (tempNodeChild.Name == "NewActuator") 501 { 502 if (tempNodeChild.Attributes["name"] != null && tempNodeChild.Attributes["protocol"] != null) 503 { 504 tempActuatorName = tempNodeChild.Attributes["name"].Value; 505 try 506 { 507 tempActuatorProtocol = (CaseProtocol)Enum.Parse(typeof(CaseProtocol), tempNodeChild.Attributes["protocol"].Value); 508 } 509 catch 510 { 511 tempActuatorProtocol = CaseProtocol.unknownProtocol; 512 SetNowActionError("find unknown Protocol in ScriptRunTime - RunTimeActuator"); 513 } 514 switch (tempActuatorProtocol) 515 { 516 case CaseProtocol.vanelife_http: 517 myConnectForVanelife_http ConnectInfo = new myConnectForVanelife_http(tempActuatorProtocol, CaseTool.getXmlInnerVaule(tempNodeChild, "dev_key"), CaseTool.getXmlInnerVaule(tempNodeChild, "dev_secret"), CaseTool.getXmlInnerVaule(tempNodeChild, "default_url")); 518 AddExecutionDevice(tempActuatorName, ConnectInfo); 519 break; 520 case CaseProtocol.http: 521 myConnectForHttp ConnectInfo_http = new myConnectForHttp(tempActuatorProtocol, CaseTool.getXmlInnerVaule(tempNodeChild, "default_url")); 522 AddExecutionDevice(tempActuatorName, ConnectInfo_http); 523 break; 524 default: 525 SetNowActionError("find nonsupport Protocol in ScriptRunTime "); 526 break; 527 } 528 } 529 else 530 { 531 SetNowActionError("can not find name or protocol in ScriptRunTime - RunTimeActuator"); 532 } 533 } 534 else 535 { 536 SetNowActionError("find unkonw data in ScriptRunTime - RunTimeActuator"); 537 } 538 } 539 } 540 break; 541 #endregion 542 543 #region RunTimeStaticData 544 case "RunTimeStaticData": 545 if (tempNode.HasChildNodes) 546 { 547 foreach (XmlNode tempNodeChild in tempNode.ChildNodes) 548 { 549 if (tempNodeChild.Name == "NewStaticData") 550 { 551 if (tempNodeChild.Attributes["name"] != null && tempNodeChild.Attributes["type"] != null) 552 { 553 CaseStaticDataType tempType; 554 string tempName = tempNodeChild.Attributes["name"].Value; 555 string tempTypeStr = tempNodeChild.Attributes["type"].Value; 556 string tempVaule = tempNodeChild.InnerText; 557 try 558 { 559 tempType = (CaseStaticDataType)Enum.Parse(typeof(CaseStaticDataType), "staticData_"+tempTypeStr); 560 } 561 catch 562 { 563 SetNowActionError("find unknown type in RunTimeStaticData - ScriptRunTime"); 564 continue; 565 } 566 switch (tempType) 567 { 568 case CaseStaticDataType.staticData_index: 569 myStaticDataIndex tempStaticDataIndex; 570 string tempTypeError; 571 if (myCaseDataTypeEngine.getIndexStaticData(out tempStaticDataIndex, out tempTypeError, tempVaule)) 572 { 573 runActuatorStaticDataList.myAdd(tempName, tempStaticDataIndex); 574 } 575 else 576 { 577 runActuatorStaticDataList.myAdd(tempName, tempStaticDataIndex); 578 SetNowActionError(tempVaule); 579 } 580 break; 581 case CaseStaticDataType.staticData_random: 582 myStaticDataRandomStr tempStaticDataRandomStr; 583 if(myCaseDataTypeEngine.getRandomStaticData(out tempStaticDataRandomStr,out tempTypeError,tempVaule)) 584 { 585 runActuatorStaticDataList.myAdd(tempName, tempStaticDataRandomStr); 586 } 587 else 588 { 589 runActuatorStaticDataList.myAdd(tempName, tempStaticDataRandomStr); 590 SetNowActionError(tempVaule); 591 } 592 break; 593 case CaseStaticDataType.staticData_time: 594 myStaticDataNowTime tempStaticDataNowTime; 595 myCaseDataTypeEngine.getTimeStaticData(out tempStaticDataNowTime, tempVaule); 596 runActuatorStaticDataList.myAdd(tempName, tempStaticDataNowTime); 597 break; 598 default: 599 SetNowActionError("find nonsupport Protocol in RunTimeStaticData - ScriptRunTime "); 600 break; 601 } 602 603 } 604 else 605 { 606 SetNowActionError("can not find name or type in RunTimeStaticData - ScriptRunTime"); 607 } 608 } 609 else 610 { 611 SetNowActionError("find unkonw data in RunTimeStaticData - ScriptRunTime"); 612 } 613 } 614 } 615 break; 616 #endregion 617 618 default: 619 SetNowActionError("find unkonw data in ScriptRunTime"); 620 break; 621 } 622 } 623 } 624 else 625 { 626 SetNowActionError("Error Source Node"); 627 } 628 } 629 } 630 631 /// <summary> 632 /// 连接这些器 633 /// </summary> 634 public void ConnectExecutionDevice() 635 { 636 SetNowExecutiveData("CaseExecutionDevice connecting ......"); 637 foreach (KeyValuePair<string, ICaseExecutionDevice> tempKvp in myExecutionDeviceList) 638 { 639 if (tempKvp.Value.executionDeviceConnect()) 640 { 641 SetNowExecutiveData(string.Format("【RunTimeActuator】:{0} 连接成功", tempKvp.Key)); 642 } 643 else 644 { 645 SetNowActionError(tempKvp.Key + "connect fail"); 646 } 647 } 648 SetNowExecutiveData("Connect complete"); 649 } 650 651 /// <summary> 652 /// 为执行器断开连接 653 /// </summary> 654 public void DisconnectExecutionDevice() 655 { 656 foreach (KeyValuePair<string, ICaseExecutionDevice> tempKvp in myExecutionDeviceList) 657 { 658 tempKvp.Value.executionDeviceClose(); 659 } 660 } 661 662 /// <summary> 663 /// 创建任务 664 /// </summary> 665 private void CreateNewActuatorTask() 666 { 667 //Thread myThreadTest = new Thread(new ThreadStart(ExecutiveActuatorTask),10240); 668 if (myActuatorTaskThread!=null) 669 { 670 if(myActuatorTaskThread.IsAlive) 671 { 672 invalidThreadList.Add(myActuatorTaskThread); 673 ClearInvalidThreadList(); 674 SetNowActionError("Forced to terminate the residual task"); 675 } 676 } 677 myActuatorTaskThread = new Thread(new ThreadStart(ExecutiveActuatorTask)); 678 myActuatorTaskThread.Name = myName + "_ExecutiveActuatorTask"; 679 myActuatorTaskThread.Priority = ThreadPriority.Normal; 680 myActuatorTaskThread.IsBackground = true; 681 myActuatorTaskThread.Start(); 682 } 683 684 /// <summary> 685 /// 执行任务 686 /// </summary> 687 private void ExecutiveActuatorTask() 688 { 689 ConnectExecutionDevice(); 690 CaseCell nowExecutiveNode = null; 691 myRunCaseData<ICaseExecutionContent> nowRunCaseData = null; 692 ExecutiveAdditionalInfo nowAdditionalInfo; 693 694 while((nowExecutiveNode=caseRunTime.nextCase())!=null) 695 { 696 if ((nowRunCaseData = nowExecutiveNode.CaseRunData) != null) 697 { 698 nowAdditionalInfo = null; 699 ExecutiveAnCases(nowRunCaseData, nowExecutiveNode,ref nowAdditionalInfo); 700 701 while (nowAdditionalInfo != null) 702 { 703 //Stoping 704 if (nowAdditionalInfo.IsStoping) 705 { 706 SetNowExecutiveData("操作者主动终止任务"); 707 goto EndTask; 708 } 709 //ReTry 710 if(nowAdditionalInfo.IsReTry) 711 { 712 nowAdditionalInfo.IsReTry = false; 713 ExecutiveAnCases(nowRunCaseData, nowExecutiveNode,ref nowAdditionalInfo); 714 } 715 else 716 { 717 break; 718 } 719 } 720 } 721 else 722 { 723 //没有执行数据请处理 724 SetNowActionError("严重异常,未找到合法执行数据"); 725 726 } 727 } 728 EndTask: 729 DisconnectExecutionDevice(); 730 SetRunState(CaseActuatorState.Stop); 731 SetNowExecutiveData("任务已结束"); 732 } 733 734 /// <summary> 735 /// 创建一个定项执行任务 736 /// </summary> 737 /// <param name="yourTryNode"></param> 738 private void CreateNewActuatorTry(CaseCell yourTryNode) 739 { 740 if (myActuatorTryThread != null) 741 { 742 if (myActuatorTryThread.IsAlive) 743 { 744 invalidThreadList.Add(myActuatorTryThread); 745 ClearInvalidThreadList(); 746 SetNowActionError("Forced to terminate the residual task"); 747 } 748 } 749 myActuatorTryThread = new Thread(new ParameterizedThreadStart(ExecutiveActuatorTry)); 750 myActuatorTryThread.Name = myName + "_ExecutiveActuatorTry"; 751 myActuatorTryThread.Priority = ThreadPriority.Normal; 752 myActuatorTryThread.IsBackground = true; 753 myActuatorTryThread.Start(yourTryNode); 754 } 755 756 /// <summary> 757 /// 执行一次定项测试 758 /// </summary> 759 private void ExecutiveActuatorTry(object yourTryNode) 760 { 761 ConnectExecutionDevice(); 762 CaseCell nowExecutiveNode = (CaseCell)yourTryNode; 763 myRunCaseData<ICaseExecutionContent> nowRunCaseData = nowExecutiveNode.CaseRunData; 764 ExecutiveAdditionalInfo nowAdditionalInfo; 765 766 if (nowRunCaseData != null) 767 { 768 nowAdditionalInfo = new ExecutiveAdditionalInfo(false, true); 769 ExecutiveAnCases(nowRunCaseData, nowExecutiveNode, ref nowAdditionalInfo); 770 } 771 else 772 { 773 //没有执行数据请处理 774 SetNowActionError("严重异常,未找到合法执行数据"); 775 } 776 777 DisconnectExecutionDevice(); 778 SetRunState(CaseActuatorState.Stop); 779 SetNowExecutiveData("定项执行完成"); 780 } 781 782 /// <summary> 783 /// 执行指定的Case 784 /// </summary> 785 /// <param name="nowRunCaseData">myRunCaseData</param> 786 /// <param name="nowExecutiveNode">now CaseCell</param> 787 private void ExecutiveAnCases(myRunCaseData<ICaseExecutionContent> nowRunCaseData, CaseCell nowExecutiveNode, ref ExecutiveAdditionalInfo nowAdditionalInfo) 788 { 789 bool tempIsBreakError = false; 790 myExecutionDeviceResult executionResult; 791 792 if(runState==CaseActuatorState.Pause) 793 { 794 myActionActuator.SetCaseNodePause(nowExecutiveNode); 795 } 796 myManualResetEvent.WaitOne(); 797 if (runState == CaseActuatorState.Stoping) 798 { 799 nowAdditionalInfo = new ExecutiveAdditionalInfo(true); 800 myActionActuator.SetCaseNodeStop(nowExecutiveNode); 801 return; 802 } 803 804 if (nowRunCaseData.errorMessages == null) 805 { 806 if (myExecutionDeviceList.ContainsKey(nowRunCaseData.testContent.myCaseActuator)) 807 { 808 var nowDevice = myExecutionDeviceList[nowRunCaseData.testContent.myCaseActuator]; 809 ExecutionDeviceRunLink: 810 if (nowDevice.isDeviceConnect) 811 { 812 //nowDevice.executionDeviceRun() 813 myActionActuator.SetCaseNodeRunning(nowExecutiveNode); 814 executionResult = nowDevice.executionDeviceRun(nowRunCaseData.testContent, OnGetExecutiveData, myName,runActuatorParameterList, runActuatorStaticDataList, nowRunCaseData.id); 815 HandleCaseExecutiveResul(nowRunCaseData, nowExecutiveNode, executionResult,ref nowAdditionalInfo); 816 817 } 818 else 819 { 820 //Device没有连接 821 SetNowExecutiveData(string.Format("【ID:{0}】 {1}连接中断,尝试连接中···", nowRunCaseData.id, nowRunCaseData.testContent.myCaseActuator)); 822 if (nowDevice.executionDeviceConnect()) 823 { 824 //nowDevice.executionDeviceRun() 825 goto ExecutionDeviceRunLink; 826 } 827 else 828 { 829 SetNowExecutiveData(string.Format("【ID:{0}】 {1}连接失败", nowRunCaseData.id, nowRunCaseData.testContent.myCaseActuator)); 830 myActionActuator.SetCaseNodeConnectInterrupt(nowExecutiveNode); 831 832 tempIsBreakError = true; 833 executionResult = new myExecutionDeviceResult(nowRunCaseData.id, "CaseActuator连接失败"); 834 } 835 } 836 } 837 else 838 { 839 //testContent没有找到合适的myCaseActuator 840 SetNowExecutiveData(string.Format("【ID:{0}】 未找到指定CaseActuator", nowRunCaseData.id)); 841 myActionActuator.SetCaseNodeNoActuator(nowExecutiveNode); 842 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 843 844 tempIsBreakError = true; 845 executionResult = new myExecutionDeviceResult(nowRunCaseData.id, "未找到指定CaseActuator"); 846 } 847 } 848 else 849 { 850 //nowRunCaseData有错误 851 SetNowActionError(string.Format("【ID:{0}】 执行数据脚本存在错误", nowRunCaseData.id)); 852 myActionActuator.SetCaseNodeAbnormal(nowExecutiveNode); 853 854 tempIsBreakError = true; 855 executionResult = new myExecutionDeviceResult(nowRunCaseData.id, "执行数据脚本存在错误" + nowRunCaseData.errorMessages); 856 } 857 858 //AddExecutionResult 859 AddExecutionResult(executionResult); 860 861 //Sleep 862 if (!tempIsBreakError) 863 { 864 int tempSleepTime = executiveThinkTime + caseThinkTime; 865 if(tempSleepTime>0) 866 { 867 SetNowExecutiveData(string.Format("sleep {0} ms···", tempSleepTime)); 868 Thread.Sleep(tempSleepTime); 869 } 870 } 871 872 //nowExecutiveNode.TreeView.Invoke(new delegateBasicAnonymous(() => { })); 873 //nowExecutiveNode.TreeView.Invoke(new delegateBasicAnonymous(() => nowExecutiveNode.BackColor = System.Drawing.Color.LightSkyBlue)); 874 875 } 876 877 878 /// <summary> 879 /// 处理断言,及结果封装.用于【ExecutiveActuatorTask】 880 /// </summary> 881 /// <param name="yourRunData">确保其不为null</param> 882 /// <param name="yourExecutionResult">确保其不为null</param> 883 /// <param name="nowAdditionalInfo"></param> 884 private void HandleCaseExecutiveResul(myRunCaseData<ICaseExecutionContent> yourRunData, CaseCell nowExecutiveNode, myExecutionDeviceResult yourExecutionResult, ref ExecutiveAdditionalInfo nowAdditionalInfo) 885 { 886 string tempError; 887 yourExecutionResult.caseId = yourRunData.id; 888 if (yourExecutionResult.additionalEroor != null) 889 { 890 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 891 } 892 yourExecutionResult.expectMethod = yourRunData.caseExpectInfo.myExpectType; 893 yourExecutionResult.expectContent = yourRunData.caseExpectInfo.myExpectContent.getTargetContentData(runActuatorParameterList, runActuatorStaticDataList, out tempError); 894 if (tempError != null) 895 { 896 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 897 yourExecutionResult.additionalEroor = yourExecutionResult.additionalEroor.myAddValue(tempError); 898 } 899 if (CaseTool.CheckBackData(yourExecutionResult.backContent, yourExecutionResult.expectContent, yourRunData.caseExpectInfo.myExpectType)) 900 { 901 yourExecutionResult.result = CaseResult.Pass; 902 myActionActuator.SetCaseNodePass(nowExecutiveNode); 903 } 904 else 905 { 906 yourExecutionResult.result = CaseResult.Fail; 907 myActionActuator.SetCaseNodeFial(nowExecutiveNode); 908 } 909 910 #region ParameterSaves 911 if(yourRunData.caseAttribute.myParameterSaves!=null) 912 { 913 foreach (ParameterSave tempParameterSave in yourRunData.caseAttribute.myParameterSaves) 914 { 915 string tempPickVaule = null; 916 switch (tempParameterSave.parameterFunction) 917 { 918 case PickOutFunction.pick_json: 919 tempPickVaule = CaseTool.PickJsonParameter(tempParameterSave.parameterFindVaule, yourExecutionResult.backContent); 920 break; 921 case PickOutFunction.pick_str: 922 string tempFindVaule; 923 int tempLen; 924 CaseTool.GetStrPickData(tempParameterSave.parameterFindVaule,out tempFindVaule,out tempLen); 925 if (tempFindVaule!=null) 926 { 927 tempPickVaule = CaseTool.PickStrParameter(tempFindVaule, tempLen , yourExecutionResult.backContent); 928 } 929 else 930 { 931 tempError = string.Format("【ID:{0}】ParameterSave 脚本数据不合法", yourRunData.id); 932 yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError); 933 SetNowActionError(tempError); 934 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 935 } 936 937 break; 938 case PickOutFunction.pick_xml: 939 tempPickVaule = CaseTool.PickXmlParameter(tempParameterSave.parameterFindVaule, yourExecutionResult.backContent); 940 break; 941 default: 942 tempError = string.Format("【ID:{0}】 ParameterSave 暂不支持该数据提取方式", yourRunData.id); 943 yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError); 944 SetNowActionError(tempError); 945 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 946 break; 947 } 948 if(tempPickVaule!=null) 949 { 950 SetNowActionError(string.Format("【ID:{0}】 ParameterSave 在执行结果中未找到指定参数", yourRunData.id)); 951 } 952 else 953 { 954 AddRunActuatorParameter(tempParameterSave.parameterName, tempPickVaule); 955 } 956 } 957 } 958 #endregion 959 960 #region actions 961 if (yourRunData.actions != null) 962 { 963 if (yourRunData.actions.Keys.Contains(yourExecutionResult.result)) 964 { 965 switch (yourRunData.actions[yourExecutionResult.result].caseAction) 966 { 967 case CaseAction.action_alarm: 968 if (yourRunData.actions[yourExecutionResult.result].addInfo != null) 969 { 970 VoiceService.Speak(yourRunData.actions[yourExecutionResult.result].addInfo); 971 SetNowExecutiveData("【action_alarm】"); 972 } 973 else 974 { 975 VoiceService.Beep(); 976 } 977 break; 978 case CaseAction.action_continue: 979 //do nothing 980 break; 981 case CaseAction.action_goto: 982 if (nowAdditionalInfo != null) 983 { 984 //定项不执行goto 985 if(nowAdditionalInfo.IsTryCase) 986 { 987 break; 988 } 989 } 990 if (yourRunData.actions[yourExecutionResult.result].addInfo == null) 991 { 992 tempError = string.Format("【ID:{0}】 CaseAction Case数据中部没有发现目的ID", yourRunData.id); 993 yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError); 994 SetNowActionError(tempError); 995 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 996 } 997 else 998 { 999 int tempCaseID; 1000 int tempProjectID; 1001 if (CaseTool.getTargetCaseID(yourRunData.actions[yourExecutionResult.result].addInfo, out tempProjectID, out tempCaseID)) 1002 { 1003 if (caseRunTime.gotoMyCase(tempProjectID, tempCaseID, runTimeCaseDictionary )) 1004 { 1005 SetNowExecutiveData("【action_goto】"); 1006 yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(string.Format("【action_goto】触发,已经跳转到Project:{0} Case:{1}", tempProjectID, tempCaseID)); 1007 } 1008 else 1009 { 1010 tempError = string.Format("【ID:{0}】action_goto跳转任务未成功", yourRunData.id); 1011 yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError); 1012 SetNowActionError(tempError); 1013 } 1014 } 1015 else 1016 { 1017 tempError = string.Format("【ID:{0}】 CaseAction 目标跳转Case不合法", yourRunData.id); 1018 yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError); 1019 SetNowActionError(tempError); 1020 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 1021 } 1022 } 1023 break; 1024 case CaseAction.action_retry: 1025 if (nowAdditionalInfo != null) 1026 { 1027 //定项不执行goto 1028 if (nowAdditionalInfo.IsTryCase) 1029 { 1030 break; 1031 } 1032 } 1033 if (yourRunData.actions[yourExecutionResult.result].addInfo != null) 1034 { 1035 try 1036 { 1037 int tempTryTimes = int.Parse(yourRunData.actions[yourExecutionResult.result].addInfo); 1038 if (tempTryTimes > 0) 1039 { 1040 if (nowAdditionalInfo == null) 1041 { 1042 nowAdditionalInfo = new ExecutiveAdditionalInfo(true, tempTryTimes); 1043 if (nowAdditionalInfo.TryTimes > 0) 1044 { 1045 SetNowExecutiveData("【action_retry】将被触发"); 1046 } 1047 else 1048 { 1049 nowAdditionalInfo.IsReTry = false; 1050 } 1051 } 1052 else 1053 { 1054 nowAdditionalInfo.TryTimes--; 1055 yourExecutionResult.additionalRemark += string.Format("retry: {0}/{1}", tempTryTimes, tempTryTimes - nowAdditionalInfo.TryTimes); 1056 if( nowAdditionalInfo.TryTimes > 0) 1057 { 1058 nowAdditionalInfo.IsReTry = true; 1059 } 1060 } 1061 } 1062 1063 } 1064 catch 1065 { 1066 tempError = string.Format("【ID:{0}】 retry 解析错误", yourRunData.id); 1067 yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError); 1068 SetNowActionError(tempError); 1069 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 1070 } 1071 } 1072 else 1073 { 1074 if (nowAdditionalInfo == null) 1075 { 1076 nowAdditionalInfo = new ExecutiveAdditionalInfo(true, -99); 1077 } 1078 else 1079 { 1080 yourExecutionResult.additionalRemark += "【action_retry】 always"; 1081 nowAdditionalInfo.IsReTry = true; 1082 } 1083 } 1084 break; 1085 case CaseAction.action_stop: 1086 PauseCaseScript(); 1087 break; 1088 case CaseAction.action_unknow: 1089 tempError = string.Format("【ID:{0}】 CaseAction 未能解析", yourRunData.id); 1090 yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError); 1091 SetNowActionError(tempError); 1092 myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); 1093 break; 1094 default: 1095 //do nothing 1096 break; 1097 1098 } 1099 } 1100 } 1101 #endregion 1102 1103 #region Sleep 1104 if(yourRunData.caseAttribute.attributeDelay>0) 1105 { 1106 caseThinkTime = yourRunData.caseAttribute.attributeDelay; 1107 } 1108 else 1109 { 1110 if(caseThinkTime!=0) 1111 { 1112 caseThinkTime = 0; 1113 } 1114 } 1115 #endregion 1116 1117 } 1118 1119 /// <summary> 1120 /// 重置ErrorInfo 1121 /// </summary> 1122 public void ResetErrorInfo() 1123 { 1124 myErrorInfo = ""; 1125 } 1126 1127 /// <summary> 1128 /// 重置NowExecutiveData 1129 /// </summary> 1130 public void ResetNowExecutiveData() 1131 { 1132 nowExecutiveData = ""; 1133 } 1134 1135 /// <summary> 1136 /// 触发【OnGetExecutiveData】 1137 /// </summary> 1138 /// <param name="yourContent"></param> 1139 private void SetNowExecutiveData(string yourContent) 1140 { 1141 if (OnGetExecutiveData != null) 1142 { 1143 this.OnGetExecutiveData(myName, yourContent); 1144 } 1145 } 1146 1147 /// <summary> 1148 /// 设置nowExecutiveData 及触发【OnGetExecutiveData】 1149 /// </summary> 1150 /// <param name="yourContent"></param> 1151 private void SetAndSaveNowExecutiveData(string yourContent) 1152 { 1153 nowExecutiveData = yourContent; 1154 if (OnGetExecutiveData != null) 1155 { 1156 this.OnGetExecutiveData(myName, yourContent); 1157 } 1158 } 1159 1160 /// <summary> 1161 /// 触发【OnGetActionError】 1162 /// </summary> 1163 /// <param name="yourContent">Action Error Content</param> 1164 private void SetNowActionError(string yourContent) 1165 { 1166 if (OnGetActionError != null) 1167 { 1168 this.OnGetActionError(myName, yourContent); 1169 } 1170 } 1171 1172 /// <summary> 1173 /// 设置 myErrorInfo 并 触发【OnGetActionError】(若不想触发OnGetActionError请直接操作myErrorInfo) 1174 /// </summary> 1175 /// <param name="yourContent">Action Error Content</param> 1176 private void SetAndSaveNowActionError(string yourContent) 1177 { 1178 myErrorInfo = yourContent; 1179 if (OnGetActionError != null) 1180 { 1181 this.OnGetActionError(myName, myErrorInfo); 1182 } 1183 } 1184 1185 /// <summary> 1186 /// 设置 runState 并 触发【OnActuatorStateChanged】 1187 /// </summary> 1188 /// <param name="yourStae"></param> 1189 private void SetRunState(CaseActuatorState yourStae) 1190 { 1191 runState = yourStae; 1192 if(OnActuatorStateChanged!=null) 1193 { 1194 this.OnActuatorStateChanged(myName, yourStae); 1195 } 1196 } 1197 1198 /// <summary> 1199 /// 添加执行结果到结果集并触发【OnExecutiveResult】 1200 /// </summary> 1201 /// <param name="yourExecutionResult">your ExecutionResult</param> 1202 private void AddExecutionResult(myExecutionDeviceResult yourExecutionResult) 1203 { 1204 runExecutionResultList.Add(yourExecutionResult); 1205 if (OnExecutiveResult != null) 1206 { 1207 this.OnExecutiveResult(myName, yourExecutionResult); 1208 } 1209 } 1210 1211 /// <summary> 1212 /// 添加ExecutionDevice 1213 /// </summary> 1214 /// <param name="yourDeviceConnectInfo"></param> 1215 /// <returns></returns> 1216 private bool AddExecutionDevice(string yourDeviceName, IConnectExecutiveData yourDeviceConnectInfo) 1217 { 1218 switch (yourDeviceConnectInfo.myCaseProtocol) 1219 { 1220 case CaseProtocol.vanelife_http: 1221 myExecutionDeviceList.myAdd(yourDeviceName, new CaseProtocolExecutionForVanelife_http((myConnectForVanelife_http)yourDeviceConnectInfo)); 1222 break; 1223 case CaseProtocol.http: 1224 myExecutionDeviceList.myAdd(yourDeviceName, new CaseProtocolExecutionForHttp((myConnectForHttp)yourDeviceConnectInfo)); 1225 break; 1226 default: 1227 SetNowActionError(yourDeviceName + " is an nonsupport Protocol"); 1228 break; 1229 } 1230 return true; 1231 } 1232 1233 /// <summary> 1234 /// 添加或修改【runActuatorParameterList】 1235 /// </summary> 1236 /// <param name="yourParameterName"> Parameter Name</param> 1237 /// <param name="yourParameterVaule">Parameter Vaule</param> 1238 public void AddRunActuatorParameter(string yourParameterName, string yourParameterVaule) 1239 { 1240 runActuatorParameterList.myAdd(yourParameterName, yourParameterVaule); 1241 if (OnActuatorParameterListChanged!=null) 1242 { 1243 this.OnActuatorParameterListChanged(); 1244 } 1245 } 1246 1247 /// <summary> 1248 /// 设置 【case guide diver】 1249 /// </summary> 1250 /// <param name="yourCaseDictionary">your Case ID list</param> 1251 public void SetCaseRunTime(Dictionary<int, Dictionary<int, CaseCell>> yourCaseDictionary, ProjctCollection yourProjctCollection) 1252 { 1253 if (yourCaseDictionary != null && yourProjctCollection!=null) 1254 { 1255 runTimeCaseDictionary = yourCaseDictionary; 1256 runCellProjctCollection = yourProjctCollection; 1257 caseRunTime = new myCaseRunTime(); 1258 caseRunTime.OnLoopChangeEvent += caseRunTime_OnLoopChangeEvent; 1259 caseRunTime.OnQueueChangeEvent += caseRunTime_OnQueueChangeEvent; 1260 } 1261 else 1262 { 1263 SetNowActionError("your CaseDictionary or ProjctCollection is null"); 1264 } 1265 } 1266 1267 /// <summary> 1268 /// 获取执行器是否可以执行 1269 /// </summary> 1270 /// <returns>is ok</returns> 1271 private bool IsActionActuatorCanRun(CaseCell yourStartNode) 1272 { 1273 if (runCellProjctCollection == null) 1274 { 1275 SetAndSaveNowActionError("your CellProjctCollection is null"); 1276 return false; 1277 } 1278 if (runTimeCaseDictionary==null) 1279 { 1280 SetAndSaveNowActionError("your RunTimeCaseDictionary is null"); 1281 return false; 1282 } 1283 if (caseRunTime == null) 1284 { 1285 SetAndSaveNowActionError("your CaseRuntime is null"); 1286 return false; 1287 } 1288 if (myExecutionDeviceList.Count == 0) 1289 { 1290 SetAndSaveNowActionError("can not find any ExecutionDevice"); 1291 return false; 1292 } 1293 if (yourStartNode==null) 1294 { 1295 SetAndSaveNowActionError("your StartNode is null"); 1296 return false; 1297 } 1298 return true; 1299 } 1300 1301 /// <summary> 1302 /// 执行项目任务 1303 /// </summary> 1304 /// <param name="yourStartNode">起始节点</param> 1305 /// <returns>Success</returns> 1306 public bool RunCaseScript(CaseCell yourStartNode) 1307 { 1308 switch (runState) 1309 { 1310 case CaseActuatorState.Running: 1311 SetAndSaveNowActionError("当前任务还未结束"); 1312 return false; 1313 case CaseActuatorState.Stoping: 1314 SetAndSaveNowActionError("当前任务正在终止中"); 1315 return false; 1316 case CaseActuatorState.Pause: 1317 SetRunState(CaseActuatorState.Running); 1318 myManualResetEvent.Set(); 1319 SetNowExecutiveData("任务恢复"); 1320 return true; 1321 case CaseActuatorState.Stop: 1322 if (yourStartNode == null) 1323 { 1324 SetAndSaveNowActionError("未发现任何可用节点"); 1325 return false; 1326 } 1327 else if (!IsActionActuatorCanRun(yourStartNode)) 1328 { 1329 return false; 1330 } 1331 caseRunTime.readyStart(yourStartNode); 1332 runExecutionResultList.Clear(); 1333 SetRunState(CaseActuatorState.Running); 1334 myManualResetEvent.Set(); 1335 CreateNewActuatorTask(); 1336 SetNowExecutiveData("任务开始"); 1337 return true; 1338 case CaseActuatorState.Trying: 1339 SetAndSaveNowActionError("存在未还未结束指定项任务"); 1340 return false; 1341 default: 1342 return false; 1343 } 1344 1345 } 1346 1347 /// <summary> 1348 /// 暂停当前项目任务(可恢复) 1349 /// </summary> 1350 /// <returns>Success</returns> 1351 public bool PauseCaseScript() 1352 { 1353 if (runState == CaseActuatorState.Running) 1354 { 1355 myManualResetEvent.Reset(); 1356 SetRunState(CaseActuatorState.Pause); 1357 SetNowExecutiveData("任务已暂停"); 1358 return true; 1359 } 1360 else 1361 { 1362 SetAndSaveNowActionError("未发现处于运行状态中的任务"); 1363 return false; 1364 } 1365 } 1366 1367 /// <summary> 1368 /// 停止项目(不可恢复) 1369 /// </summary> 1370 /// <returns></returns> 1371 public bool StopCaseScript() 1372 { 1373 if (runState == CaseActuatorState.Running) 1374 { 1375 SetRunState(CaseActuatorState.Stoping); 1376 SetNowExecutiveData("正在终止任务"); 1377 return true; 1378 } 1379 else if (runState == CaseActuatorState.Pause) 1380 { 1381 myManualResetEvent.Set(); 1382 SetRunState(CaseActuatorState.Stoping); 1383 SetNowExecutiveData("正在终止任务"); 1384 return true; 1385 } 1386 else if (runState == CaseActuatorState.Stoping) 1387 { 1388 SetAndSaveNowActionError("正在终止任务"); 1389 return false; 1390 } 1391 else 1392 { 1393 SetAndSaveNowActionError("当前项目已经停止"); 1394 return false; 1395 } 1396 } 1397 1398 /// <summary> 1399 /// 单步执行项目成员(必须在Pause状态,即时不在Pause状态,也会先进行Pause) 1400 /// </summary> 1401 /// <param name="yourNode">当前项目(如果项目已经开始可以为null)</param> 1402 /// <returns>Success</returns> 1403 public bool TryNextCaseScript(CaseCell yourNode) 1404 { 1405 if (runState == CaseActuatorState.Running) 1406 { 1407 PauseCaseScript(); 1408 myManualResetEvent.Set(); 1409 SetNowExecutiveData("单步执行>"); 1410 myManualResetEvent.Reset(); 1411 return true; 1412 1413 } 1414 else if (runState == CaseActuatorState.Stop) 1415 { 1416 if (RunCaseScript(yourNode)) 1417 { 1418 PauseCaseScript(); 1419 SetNowExecutiveData("单步执行>"); 1420 myManualResetEvent.Set(); 1421 myManualResetEvent.Reset(); 1422 return true; 1423 } 1424 else 1425 { 1426 SetAndSaveNowActionError("无法进行单步执行"); 1427 return false; 1428 } 1429 } 1430 else if (runState == CaseActuatorState.Pause) 1431 { 1432 SetNowExecutiveData("单步执行>"); 1433 myManualResetEvent.Set(); 1434 myManualResetEvent.Reset(); 1435 return true; 1436 } 1437 else if (runState == CaseActuatorState.Running) 1438 { 1439 SetAndSaveNowActionError("正在结束项目,无法进行单步执行"); 1440 return false; 1441 } 1442 else if (runState == CaseActuatorState.Trying) 1443 { 1444 SetAndSaveNowActionError("存在正在执行指定项任务"); 1445 return false; 1446 } 1447 return false; 1448 } 1449 1450 /// <summary> 1451 /// 定项执行指定项(一直执行同一条CASE,仅在项目停止后可以使用,且goto及retry在这种任务中无效) 1452 /// </summary> 1453 /// <param name="yourNode">定项 数据</param> 1454 /// <returns>is Success</returns> 1455 public bool TryNowCaseScript(CaseCell yourNode) 1456 { 1457 if (runState == CaseActuatorState.Stop) 1458 { 1459 SetRunState(CaseActuatorState.Trying); 1460 myManualResetEvent.Set(); 1461 CreateNewActuatorTry(yourNode); 1462 return true; 1463 } 1464 else if (runState == CaseActuatorState.Trying) 1465 { 1466 SetAndSaveNowActionError("上一个指定项任务还未结束"); 1467 return false; 1468 } 1469 else 1470 { 1471 SetAndSaveNowActionError("要进行定向执行前,必须先停止任务"); 1472 return false; 1473 } 1474 } 1475 1476 /// <summary> 1477 /// 强制关闭所有正在执行的任务(谨慎调用) 1478 /// </summary> 1479 public void KillAll() 1480 { 1481 if (myActuatorTaskThread != null) 1482 { 1483 if (myActuatorTaskThread.IsAlive) 1484 { 1485 myActuatorTaskThread.Abort(); 1486 DisconnectExecutionDevice(); 1487 SetRunState(CaseActuatorState.Stop); 1488 } 1489 } 1490 if (myActuatorTryThread != null) 1491 { 1492 if (myActuatorTryThread.IsAlive) 1493 { 1494 myActuatorTryThread.Abort(); 1495 DisconnectExecutionDevice(); 1496 SetRunState(CaseActuatorState.Stop); 1497 } 1498 } 1499 1500 ClearInvalidThreadList(); 1501 } 1502 1503 /// <summary> 1504 /// 清理无效线程列表 1505 /// </summary> 1506 private void ClearInvalidThreadList() 1507 { 1508 for (int i = invalidThreadList.Count - 1; i >= 0; i--) 1509 { 1510 ErrorLog.PutInLog(string.Format("fing InvalidThread Name:{0} State:{1}" , invalidThreadList[i].Name ,invalidThreadList[i].ThreadState.ToString())); 1511 if (invalidThreadList[i].IsAlive) 1512 { 1513 invalidThreadList[i].Abort(); 1514 } 1515 else 1516 { 1517 invalidThreadList.Remove(invalidThreadList[i]); 1518 } 1519 } 1520 for (int i = invalidThreadList.Count - 1; i >= 0; i--) 1521 { 1522 if (!invalidThreadList[i].IsAlive) 1523 { 1524 invalidThreadList.Remove(invalidThreadList[i]); 1525 } 1526 } 1527 } 1528 1529 /// <summary> 1530 /// 实现【IDisposable】强烈建议结束前调用(即时当前可用使用disconnectExecutionDevice替代) 1531 /// </summary> 1532 public void Dispose() 1533 { 1534 KillAll(); 1535 DisconnectExecutionDevice(); 1536 myExecutionDeviceList.Clear(); 1537 runActuatorParameterList.Clear(); 1538 runActuatorStaticDataList.Clear(); 1539 runTimeCaseDictionary = null; 1540 runCellProjctCollection = null; 1541 if (caseRunTime!=null) 1542 { 1543 caseRunTime.OnLoopChangeEvent -= caseRunTime_OnLoopChangeEvent; 1544 caseRunTime.OnQueueChangeEvent -= caseRunTime_OnQueueChangeEvent; 1545 caseRunTime = null; 1546 } 1547 1548 } 1549 1550 }
同时CaseActionActuator还包含一个比较重要的部分myCaseProtocolEngine,该部分是协议引擎,实际上只要PC上可以使用的协议通过简单的修改都可以加入到系统
对于协议引擎必须继承规定好的接口实现,然后就能将协议加入系统,具体方法见【http://www.cnblogs.com/lulianqi/p/4773268.html】
这个模块还要许多其他部分比如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绑定的效果(这个不是主界面,整体的显示效果可以看下【http://www.cnblogs.com/lulianqi/p/4773146.html】)
当然还有前面提到的另外一个重要功能连接远程测试主机,主要负责连接分布式部署在其他PC上的执行模块(就是后面要讲的RemoteService),同时可以控制及配置远程主机上的User(还包括连接的维持)。连接功能由RemoteClient组件来完成,它在前面提到的AutoTest.myTool 下,简单看下它的实现
基本结构如下图
这个其实实现起来比较简单,因为借助WCF框架,很多工作框架已经做好了,不过一些通道的选择配置以及是否适合双工通信可能不是最优的,后面还需要调整,可以看下下面的code
1 class ExecuteServiceCallback : IExecuteServiceCallback 2 { 3 4 public ExecuteServiceCallback() 5 { 6 7 } 8 9 public delegate void RunnerStateChangeEventHandler(ExecuteServiceCallback sender, RemoteRunnerInfo remoteRunnerInfo); 10 11 public event RunnerStateChangeEventHandler OnRunnerStateChange; 12 13 public void ReportState(RemoteRunnerInfo remoteRunnerInfo) 14 { 15 if (OnRunnerStateChange!=null) 16 { 17 this.OnRunnerStateChange(this, remoteRunnerInfo); 18 } 19 } 20 } 21 22 public class RemoteClient:IDisposable 23 { 24 25 /// <summary> 26 /// 描述当前Client连接状态 27 /// </summary> 28 public enum RemoteClientState 29 { 30 Connecting=0, //正在连接 31 Connected=1, //连接成功 32 Break = 2, //连接中断,并且正在进行重连 33 Lost = 3 //连接中断,且不会重新连接(必须触发重连) 34 } 35 36 ExecuteServiceClient executeServiceClient = null; 37 InstanceContext instanceContext = null; 38 //EndpointAddress myEp = new EndpointAddress("http://localhost:8087/SelService"); 39 EndpointAddress myEp = null; 40 41 private RemoteRunner showWindow; 42 private bool isLive = false; 43 private RemoteClientState clientState = RemoteClientState.Lost; 44 private int reCounectTime=5; 45 46 private Thread clientLife; 47 48 49 public delegate void RunnerStateChangeEventHandler(RemoteClient sender, RemoteRunnerInfo remoteRunnerInfo); 50 public delegate void AllRunnerInforEventHandler(RemoteClient sender, RemoteRunnerInfo remoteRunnerInfo); 51 public delegate void ClientStateChangeEventHandler(RemoteClient sender, RemoteClientState nowSate); 52 public delegate void ClientErrorEventHandler(RemoteClient sender, string errorInfo); 53 54 public event RunnerStateChangeEventHandler OnRunnerStateChange; 55 public event AllRunnerInforEventHandler OnPutAllRunnerInfor; 56 public event ClientStateChangeEventHandler OnClientStateChange; 57 public event ClientErrorEventHandler OnClientErrorInfor; 58 59 60 61 public RemoteClient(EndpointAddress yourEp, RemoteRunner yourWindow ) 62 { 63 myEp = yourEp; 64 showWindow = yourWindow; 65 } 66 67 public override string ToString() 68 { 69 if (myEp != null) 70 { 71 return myEp.Uri.Host + ":" + myEp.Uri.Port; 72 } 73 else 74 { 75 return "Null Ep"; 76 } 77 } 78 79 /// <summary> 80 /// 获取或设置RemoteClient 的基础地址 81 /// </summary> 82 public EndpointAddress ClientEp 83 { 84 get { return myEp;} 85 set { myEp = value; } 86 } 87 88 /// <summary> 89 /// 获取或设置ShowWindow 90 /// </summary> 91 public RemoteRunner ShowWindow 92 { 93 get { return showWindow; } 94 set { showWindow = value; } 95 } 96 97 /// <summary> 98 /// 获取当前Client连接状态(自维护状态,该状态同时提示生命线程运行情况) 99 /// </summary> 100 public RemoteClientState ClientState 101 { 102 get { return clientState; } 103 } 104 105 /// <summary> 106 /// 获取当前executeServiceClient通道状态 107 /// </summary> 108 public CommunicationState ExecuteServiceClientState 109 { 110 get 111 { 112 if (executeServiceClient==null) 113 { 114 return CommunicationState.Closed; 115 } 116 return executeServiceClient.State; 117 } 118 } 119 120 /// <summary> 121 /// 获取或设置断线重连次数限制 122 /// </summary> 123 public int ReCounectTime 124 { 125 get { return reCounectTime; } 126 set { reCounectTime = value; } 127 } 128 129 /// <summary> 130 /// 报告当前Client所有Runner状态 131 /// </summary> 132 /// <param name="remoteRunnerInfo"></param> 133 private void PutAllRunnerInfor(RemoteRunnerInfo remoteRunnerInfo) 134 { 135 if (OnPutAllRunnerInfor != null) 136 { 137 this.OnPutAllRunnerInfor(this, remoteRunnerInfo); 138 } 139 if (showWindow != null) 140 { 141 showWindow.RefreshRemoteRunnerView(remoteRunnerInfo); 142 } 143 } 144 145 /// <summary> 146 /// 改变连接状态 147 /// </summary> 148 private void SetClientState(RemoteClientState nowState) 149 { 150 clientState = nowState; 151 if (OnClientStateChange != null) 152 { 153 this.OnClientStateChange(this, nowState); 154 } 155 } 156 157 /// <summary> 158 /// 向订阅者报告错误信息 159 /// </summary> 160 /// <param name="errorInfo">错误信息</param> 161 private void SetClientErrorInfo(string errorInfo) 162 { 163 if(errorInfo!=null && OnClientErrorInfor!=null) 164 { 165 this.OnClientErrorInfor(this, errorInfo); 166 } 167 } 168 169 /// <summary> 170 /// 创建一个【ExecuteServiceClient】实例 171 /// </summary> 172 /// <returns>【ExecuteServiceClient】实例</returns> 173 private ExecuteServiceClient RestartClient() 174 { 175 if (instanceContext==null) 176 { 177 //InstanceContext 178 ExecuteServiceCallback executeServiceCallback = new ExecuteServiceCallback(); 179 executeServiceCallback.OnRunnerStateChange += executeServiceCallback_OnRunnerStateChange; 180 instanceContext = new InstanceContext(executeServiceCallback); 181 //Binding 182 System.ServiceModel.Channels.Binding binding = new WSDualHttpBinding(); 183 ((System.ServiceModel.WSDualHttpBinding)(binding)).Security.Mode = WSDualHttpSecurityMode.None; 184 ((System.ServiceModel.WSDualHttpBinding)(binding)).Security.Message.ClientCredentialType = MessageCredentialType.UserName; 185 binding.SendTimeout = new TimeSpan(0, 0, 10); 186 binding.OpenTimeout = new TimeSpan(0, 0, 10); 187 binding.ReceiveTimeout = new TimeSpan(0, 0, 10); 188 189 System.ServiceModel.Channels.Binding tcpBinding = new NetTcpBinding(); 190 ((System.ServiceModel.NetTcpBinding)(tcpBinding)).Security.Mode = SecurityMode.None; 191 ((System.ServiceModel.NetTcpBinding)(tcpBinding)).Security.Message.ClientCredentialType = MessageCredentialType.UserName; 192 tcpBinding.SendTimeout = new TimeSpan(0, 0, 10); 193 tcpBinding.OpenTimeout = new TimeSpan(0, 0, 10); 194 tcpBinding.ReceiveTimeout = new TimeSpan(0, 0, 10); 195 196 executeServiceClient = new ExecuteServiceClient(instanceContext, binding, myEp); 197 //executeServiceClient = new ExecuteServiceClient(instanceContext, new WSDualHttpBinding(), myEp); 198 199 instanceContext.Closed += instanceContext_Closed; 200 instanceContext.Opened += instanceContext_Opened; 201 return executeServiceClient; 202 } 203 else 204 { 205 instanceContext.Closed -= instanceContext_Closed; 206 instanceContext.Opened -= instanceContext_Opened; 207 instanceContext = null; 208 return RestartClient(); 209 } 210 } 211 212 #region RestartClient通信 213 214 /// <summary> 215 /// 处理收到的双工回调(仅报告有变化的Runner) 216 /// </summary> 217 void executeServiceCallback_OnRunnerStateChange(ExecuteServiceCallback sender, RemoteRunnerInfo remoteRunnerInfo) 218 { 219 if(OnRunnerStateChange!=null) 220 { 221 this.OnRunnerStateChange(this, remoteRunnerInfo); 222 } 223 if (remoteRunnerInfo == null) 224 { 225 if (showWindow != null) 226 { 227 showWindow.ShowError("Null Data" + "\r\n"); 228 } 229 return; 230 } 231 if (remoteRunnerInfo.RunnerStateList != null) 232 { 233 if (showWindow != null) 234 { 235 showWindow.UpdataRemoteRunnerView(remoteRunnerInfo); 236 } 237 } 238 } 239 240 /// <summary> 241 /// 获取当前Client所有Runner状态 242 /// </summary> 243 public void GetAllRunnerInfor() 244 { 245 if (ExecuteServiceClientState == CommunicationState.Opened) 246 { 247 try 248 { 249 RemoteRunnerInfo nowRemoteRunnerInfo = executeServiceClient.GetAllRunnerSate(); 250 if (nowRemoteRunnerInfo != null) 251 { 252 PutAllRunnerInfor(nowRemoteRunnerInfo); 253 } 254 } 255 catch (Exception ex) 256 { 257 SetClientErrorInfo(ex.Message); 258 } 259 } 260 else 261 { 262 SetClientErrorInfo("连接未打开"); 263 } 264 } 265 266 /// <summary> 267 /// 获取当前Client所有Runner状态(提供内部使用,不会捕捉错误) 268 /// </summary> 269 private void GetAllRunnerInforEx() 270 { 271 RemoteRunnerInfo nowRemoteRunnerInfo = executeServiceClient.GetAllRunnerSate(); 272 if (nowRemoteRunnerInfo != null) 273 { 274 PutAllRunnerInfor(nowRemoteRunnerInfo); 275 } 276 } 277 278 /// <summary> 279 /// 启动指定执行器 280 /// </summary> 281 /// <param name="runnerList">执行器列表</param> 282 public void StartRunner(List<int> runnerList) 283 { 284 if (ExecuteServiceClientState == CommunicationState.Opened) 285 { 286 try 287 { 288 executeServiceClient.StartRunner(runnerList.ToArray()); 289 } 290 catch (Exception ex) 291 { 292 SetClientErrorInfo(ex.Message); 293 } 294 } 295 else 296 { 297 SetClientErrorInfo("连接未打开"); 298 } 299 } 300 301 /// <summary> 302 /// 暂停指定执行器 303 /// </summary> 304 /// <param name="runnerList">执行器列表</param> 305 public void PauseRunner(List<int> runnerList) 306 { 307 if (ExecuteServiceClientState == CommunicationState.Opened) 308 { 309 try 310 { 311 executeServiceClient.PauseRunner(runnerList.ToArray()); 312 } 313 catch (Exception ex) 314 { 315 SetClientErrorInfo(ex.Message); 316 } 317 } 318 else 319 { 320 SetClientErrorInfo("连接未打开"); 321 } 322 } 323 324 /// <summary> 325 /// 停止指定执行器 326 /// </summary> 327 /// <param name="runnerList">执行器列表</param> 328 public void StopRunner(List<int> runnerList) 329 { 330 if (ExecuteServiceClientState == CommunicationState.Opened) 331 { 332 try 333 { 334 executeServiceClient.StopRunner(runnerList.ToArray()); 335 } 336 catch(Exception ex) 337 { 338 SetClientErrorInfo(ex.Message); 339 } 340 } 341 else 342 { 343 SetClientErrorInfo("连接未打开"); 344 } 345 } 346 347 #endregion 348 349 350 private void instanceContext_Closed(object sender, EventArgs e) 351 { 352 353 } 354 355 void instanceContext_Opened(object sender, EventArgs e) 356 { 357 358 } 359 360 /// <summary> 361 /// 启动当前Client 362 /// </summary> 363 public void StartClient() 364 { 365 //Thread myThreadTest = new Thread(new ThreadStart(ExecutiveActuatorTask),10240); 366 if (clientLife != null) 367 { 368 if (clientLife.IsAlive) 369 { 370 if (showWindow != null) 371 { 372 showWindow.ShowError("IsAlive"); 373 } 374 } 375 else 376 { 377 clientLife = null; 378 StartClient(); 379 } 380 } 381 else 382 { 383 clientLife = new Thread(new ParameterizedThreadStart(ClientAliveTask)); 384 clientLife.Name = "ClientLife" + myEp.ToString(); 385 clientLife.Priority = ThreadPriority.Normal; 386 clientLife.IsBackground = true; 387 isLive = true; 388 clientLife.Start(executeServiceClient); 389 } 390 } 391 392 /// <summary> 393 /// 停止当前Client 394 /// </summary> 395 public void StopClient() 396 { 397 isLive = false; 398 } 399 400 401 /// <summary> 402 /// 保持连接的心跳及重新连接的线程任务 403 /// </summary> 404 private void ClientAliveTask(object yourExecuteClient) 405 { 406 ExecuteServiceClient executeClient = (ExecuteServiceClient)yourExecuteClient; 407 int counectTime = reCounectTime; 408 409 ReConnect: 410 executeClient = RestartClient(); 411 try 412 { 413 SetClientState(RemoteClientState.Connecting); 414 GetAllRunnerInforEx(); 415 SetClientState(RemoteClientState.Connected); 416 } 417 catch (Exception ex) 418 { 419 MyCommonTool.ErrorLog.PutInLogEx(ex); 420 if (counectTime > 0 && isLive) 421 { 422 counectTime--; 423 SetClientState(RemoteClientState.Break); 424 Thread.Sleep(2000); 425 goto ReConnect; 426 } 427 else 428 { 429 StopClient(); 430 } 431 } 432 433 while (isLive) 434 { 435 try 436 { 437 executeClient.ExecuteServiceBeat(); 438 } 439 catch (Exception ex) 440 { 441 SetClientState(RemoteClientState.Break); 442 Thread.Sleep(2000); 443 MyCommonTool.ErrorLog.PutInLogEx(ex); 444 counectTime = reCounectTime; 445 goto ReConnect; 446 } 447 Thread.Sleep(10000); 448 } 449 450 SetClientState(RemoteClientState.Lost); 451 } 452 453 public void Dispose() 454 { 455 if (clientLife != null) 456 { 457 if (clientLife.IsAlive) 458 { 459 clientLife.Abort(); 460 } 461 } 462 executeServiceClient = null; 463 instanceContext = null; 464 } 465 } 466 }
分布式部署服务模块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组件的部分功能
系统代码打包下载 AutoTest_V5.3_CodeOnly.rar