一个基于.NET平台的自动化/压力测试系统设计简述

AutoTest系统设计概述

AutoTest是一个基于.NET平台实现的自动化/压力测试的系统,可独立运行于windows平台下,支持分布式部署,不需要其他配置或编译器的支持。(本质是一个基于协议的测试工具),前面还有一篇对其功能的简单介绍【AutoTest简介

AutoTest用于发布的部分有2个部分,主程序【AutoTest.exe】及分布式部署程序【RemoteService.exe】(用于将将测试业务分布式部署到远程主机)

而在内部又被设计为多个组成部分,最终完成对自定义脚本文件的解析并按脚本要求的模式去执行。

一个基于.NET平台的自动化/压力测试系统设计简述

如上图,简单介绍下

执行逻辑层主要由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的结构如下(简单的实现了形如右边图片的的数据结构)

一个基于.NET平台的自动化/压力测试系统设计简述                          一个基于.NET平台的自动化/压力测试系统设计简述

其实很容易看出来这个结构跟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

 /*******************************************************************************
* Copyright (c) 2015 lijie
* All rights reserved.
*
* 文件名称:
* 内容摘要: mycllq@hotmail.com
*
* 历史记录:
* 日 期: 201505016 创建人: lijie8118054@126.com
* 描 述: 创建
*******************************************************************************/ namespace CaseExecutiveActuator.Cell
{
//using CaseCell = TreeNode;//可让2类完全等价
public class CaseCell
{
List<CaseCell> childCellList; private CaseType caseType;
private XmlNode caseXmlNode;
private myRunCaseData<ICaseExecutionContent> caseRunData;
private object uiTag; private CaseCell nextCell;
private CaseCell parentCell; public CaseCell()
{ } /// <summary>
/// CaseCell构造函数
/// </summary>
/// <param name="yourCaseType">CaseType</param>
/// <param name="yourXmlNode">CaseCell脚本原始信息</param>
/// <param name="yourCaseRunData">CaseCell脚本解析后的信息</param>
public CaseCell(CaseType yourCaseType, XmlNode yourXmlNode ,myRunCaseData<ICaseExecutionContent> yourCaseRunData)
{
caseType = yourCaseType;
caseXmlNode = yourXmlNode;
caseRunData = yourCaseRunData;
} /// <summary>
/// 获取或设置CaseCell脚本解析后的信息
/// </summary>
public myRunCaseData<ICaseExecutionContent> CaseRunData
{
get { return caseRunData; }
set { caseRunData = value; }
} /// <summary>
/// 获取或设置CaseCell脚本原始信息
/// </summary>
public XmlNode CaseXmlNode
{
get { return caseXmlNode; }
set { caseXmlNode = value; }
} /// <summary>
/// 获取或设置UiTag,可以用于UI控件与cell的绑定
/// </summary>
public object UiTag
{
get { return uiTag; }
set { uiTag = value; }
} /// <summary>
/// 获取当前Cell类型
/// </summary>
public CaseType CaseType
{
get { return caseType; }
} /// <summary>
/// 获取下一个Cell,如果没有返回null
/// </summary>
public CaseCell NextCell
{
get { return nextCell; }
} /// <summary>
/// 获取当前Cell的父Cell,如果没有返回null
/// </summary>
public CaseCell ParentCell
{
get { return parentCell; }
} /// <summary>
/// 获取当前Cell的ChildCells列表
/// </summary>
public List<CaseCell> ChildCells
{
get { return childCellList; }
} /// <summary>
/// 获取一个值标识当前Cell是否有NextCell
/// </summary>
public bool IsHasNextCell
{
get { return nextCell != null; }
} /// <summary>
/// 获取一个值标识当前Cell是否有parentCell
/// </summary>
public bool IsHasParent
{
get
{
if (parentCell != null)
{
return true;
}
return false;
}
} /// <summary>
/// 获取一个值标识当前Cell是否有ChildCell
/// </summary>
public bool IsHasChild
{
get
{
if (childCellList != null)
{
if (childCellList.Count != )
{
return true;
}
}
return false;
}
} /// <summary>
/// 设置下一个Cell
/// </summary>
/// <param name="yourCaseCell">下一个Cell</param>
public void SetNextCell(CaseCell yourCaseCell)
{
nextCell = yourCaseCell;
} /// <summary>
/// 设置ParentCell
/// </summary>
/// <param name="yourCaseCell">ParentCell</param>
public void SetParentCell(CaseCell yourCaseCell)
{
parentCell = yourCaseCell;
} /// <summary>
/// 向当前Cell中插入子Cell
/// </summary>
/// <param name="yourCaseCell">子Cell</param>
public void Add(CaseCell yourCaseCell)
{
if (childCellList == null)
{
childCellList = new List<CaseCell>();
}
yourCaseCell.SetParentCell(this);
childCellList.Add(yourCaseCell);
if(childCellList.Count>)
{
childCellList[childCellList.Count-].SetNextCell(yourCaseCell);
}
} //一个tag存放ui指针/引用
//实现一个Nodes.Count计算每层数目,或返回是否有子结构
//Nodes[0]索引或实现NodeStart,返回层中第一个CaseCell
//实现一个NextNode返回层中的下一个CaseCell
} public class ProjctCollection
{
List<CaseCell> myProjectChilds; public List<CaseCell> ProjectCells
{
get { return myProjectChilds; }
} public void Add(CaseCell yourCaseCell)
{
if (myProjectChilds == null)
{
myProjectChilds = new List<CaseCell>();
}
myProjectChilds.Add(yourCaseCell);
} public CaseCell this[int indexP, int indexC]
{
get
{
if(myProjectChilds.Count>indexP)
{
if (myProjectChilds[indexP].IsHasChild)
{
if (myProjectChilds[indexP].ChildCells.Count > indexC)
{
return myProjectChilds[indexP].ChildCells[indexC];
}
}
}
return null;
}
} }
}

执行的实体CaseExecutiveActuator本身会较多点,介绍的也会粗略些,不过大体也是可以简单的分成2个部分。可以很容易的想到假如我们得了脚本文件,那么有2个问题:第一就是怎么知道选择哪一个case,当前case执行完成后执行哪一个,第二个问题就是case中包含的业务如何执行。CaseExecutiveActuator也正是分成了这2部分

  • myCaseRunTime                                  负责指引Case路径,Cell的寻找,跳转及序列的指示 都是由它完成的
  • CaseActionActuator                             负责Case的具体执行,包括对各种协议的执行,及对结果的分析及储存,断言的处理,附加动作如语音提示,重试等执行相关的部分就由它处理

先来看比较简单的CaseRunTime

一个基于.NET平台的自动化/压力测试系统设计简述

实际上于CaseRunTime  紧密相关的还有另外2个组件myCaseLoop,myCsaeQueue(他们实际上也仅被CaseRunTime 使用)

通过名字其实大概可以猜到他们的功能

  • myCaseLoop                                 控制一组Case(可以是由多个Case组成的业务)的循环执行,当然支持循环里无限嵌套其他的循环
  • myCsaeQueue                          控制一个逻辑项目的Case坐标(当前执行的Case),基础的Case会放在一个个逻辑项目中,每个逻辑下项目可以标识一类业务的集合,而当前执行Case是可以在这些逻辑项目中来回跳转的,所以它负责多个逻辑项目的寻址
  • myCaseRunTime                           借助前面的CaseLoop 和  CsaeQueue,完成整个Case的移动轨迹(这个轨迹可能因执行结果不同而有不同的变化,取决于脚本文件如何写)
  • RunCaseCount                              一个辅助类,帮助统计脚本中实际将要执行的Case数量(会考虑起始点及循环等因素)

单独看看myCaseRunTime

一个基于.NET平台的自动化/压力测试系统设计简述

如上图可以看到myCaseRunTime实际上是包含了一个myCsaeQueue列表的,不过逻辑的核心是nextCase。有兴趣的可以看看下面的实现(贴出的只包含关键部分)

  class RunCaseCount
{
/// <summary>
/// for count strut
/// </summary>
struct CaseLoopCountInfo
{
CaseCell loopNode;
int caseRate; /// <summary>
/// Initialization the CaseLoopCountInfo
/// </summary>
/// <param name="yourLoopNode">your LoopNode</param>
/// <param name="yourCaseRate">your CaseRate</param>
public CaseLoopCountInfo(CaseCell yourLoopNode, int yourCaseRate)
{
loopNode = yourLoopNode;
caseRate = yourCaseRate;
} /// <summary>
/// get the LoopNode
/// </summary>
public CaseCell LoopNode
{
get { return loopNode; }
} /// <summary>
/// get the CaseRate
/// </summary>
public int CaseRate
{
get { return caseRate; }
}
} /// <summary>
/// get main task case count(just main but not Include the goto case)
/// </summary>
/// <param name="startNode">start Node</param>
/// <returns>count</returns>
public static int GetCount(CaseCell startNode)
{
int nowCount = ;
List<CaseLoopCountInfo> nowLoops = new List<CaseLoopCountInfo>();
while (startNode!=null)
{
if (startNode.CaseType == CaseType.Case)
{
nowCount++;
}
else if (startNode.CaseType == CaseType.Repeat)
{
if (startNode.IsHasChild)
{
myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(startNode.CaseXmlNode);
nowLoops.Add(new CaseLoopCountInfo(startNode.ChildCells[], tempProjectLoadInfo.times));
}
}
else if (startNode.CaseType == CaseType.Project)
{
if(startNode.IsHasChild)
{
startNode = startNode.ChildCells[];
}
continue;
}
startNode = startNode.NextCell;
}
while (nowLoops.Count!=)
{
startNode = nowLoops[nowLoops.Count - ].LoopNode;
int tempRate = nowLoops[nowLoops.Count - ].CaseRate;
nowLoops.Remove(nowLoops[nowLoops.Count - ]);
while (startNode != null)
{
if (startNode.CaseType == CaseType.Case)
{
nowCount += tempRate;
}
else if (startNode.CaseType == CaseType.Repeat)
{
if (startNode.IsHasChild)
{
myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(startNode.CaseXmlNode);
nowLoops.Add(new CaseLoopCountInfo(startNode.ChildCells[], tempProjectLoadInfo.times * tempRate));
}
}
startNode = startNode.NextCell;
}
}
return nowCount;
} } /// <summary>
/// CsaeQueue it will only used in myCaseRunTime
/// </summary>
class myCsaeQueue
{
private CaseCell startCaseNode;
private CaseCell nowCaseNode;
List<myCaseLoop> myCaseLoopList; private int queueTotalCount;
private int queueNowCount; public event delegateLoopChangeEventHandler OnLoopChangeEvent; /// <summary>
/// myCsaeQueue initialize
/// </summary>
/// <param name="yourStartCase">your StartCase and make sure it is not null</param>
public myCsaeQueue(CaseCell yourStartCase)
{
queueTotalCount = RunCaseCount.GetCount(yourStartCase);
startCaseNode = yourStartCase;
nowCaseNode = null;
myCaseLoopList = new List<myCaseLoop>();
} /// <summary>
/// get now CaseCell
/// </summary>
public CaseCell NowCaseNode
{
get
{
if (nowCaseNode != null)
{
if (myCaseLoopList.Count > )
{
return myCaseLoopList[myCaseLoopList.Count - ].NowCaseNode;
}
else
{
return nowCaseNode;
}
}
else
{
return startCaseNode;
}
}
} /// <summary>
/// get the Queue Count Progress(queueTotalCount and queueNowCount)
/// </summary>
public KeyValuePair<int,int> GetCountProgress
{
get
{
return new KeyValuePair<int, int>(queueTotalCount, queueNowCount);
}
} /// <summary>
/// i will add new CaseLoop and Subscribe 【OnLoopChangeEvent】
/// </summary>
/// <param name="yourStartCase">your StartCase</param>
/// <param name="yourTimes">your Times</param>
private void AddCaseLoop(CaseCell yourStartCase, int yourTimes)
{
myCaseLoopList.Add(new myCaseLoop(yourStartCase, yourTimes));
myCaseLoopList[myCaseLoopList.Count - ].OnLoopChangeEvent += OnLoopChangeEvent;
} /// <summary>
/// i will remove your CaseLoop and unSubscribe 【OnLoopChangeEvent】
/// </summary>
/// <param name="yourCaseLoop">yourCaseLoop</param>
private void DelCaseLoop(myCaseLoop yourCaseLoop)
{
yourCaseLoop.OnLoopChangeEvent -= OnLoopChangeEvent;
myCaseLoopList.Remove(yourCaseLoop);
} /// <summary>
/// i will get the next myTreeTagInfo in my queue
/// </summary>
/// <returns>the CaseCell you want</returns>
public CaseCell nextCase()
{ if(nowCaseNode==null) //起始节点
{
nowCaseNode = startCaseNode;
if (nowCaseNode.CaseType == CaseType.Repeat)
{
if (nowCaseNode.IsHasChild)
{
myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(nowCaseNode.CaseXmlNode);
AddCaseLoop(nowCaseNode.ChildCells[], tempProjectLoadInfo.times);
}
return nextCase();
}
else if (nowCaseNode.CaseType == CaseType.Case)
{
queueNowCount++;
return nowCaseNode;
}
else if (nowCaseNode.CaseType == CaseType.Project)
{
if (nowCaseNode.IsHasChild)
{
startCaseNode = nowCaseNode.ChildCells[];
nowCaseNode = null;
return nextCase();
}
return null; //空Project
}
else
{
return null; //当前设计不会有这种情况
}
}
else
{
if (myCaseLoopList.Count > )
{
int tempNowListIndex = myCaseLoopList.Count - ;
CaseCell tempNextLoopTreeNode = myCaseLoopList[tempNowListIndex].nextCase();
if (tempNextLoopTreeNode == null)
{
DelCaseLoop(myCaseLoopList[tempNowListIndex]);
return nextCase();
}
else
{
if (tempNextLoopTreeNode.CaseType == CaseType.Repeat)
{
if (tempNextLoopTreeNode.IsHasChild)
{
myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(tempNextLoopTreeNode.CaseXmlNode);
AddCaseLoop(tempNextLoopTreeNode.ChildCells[], tempProjectLoadInfo.times);
} return nextCase();
}
else if (tempNextLoopTreeNode.CaseType == CaseType.Case)
{
queueNowCount++;
return tempNextLoopTreeNode;
}
else
{
return null; //当前设计不会有这种情况
}
}
}
else
{
if(nowCaseNode.NextCell == null)
{
return null; //当前 【Queue】 结束
}
else
{
nowCaseNode = nowCaseNode.NextCell;
if (nowCaseNode.CaseType == CaseType.Repeat)
{
if (nowCaseNode.IsHasChild)
{
myCaseLaodInfo tempProjectLoadInfo = myCaseScriptAnalysisEngine.getCaseLoadInfo(nowCaseNode.CaseXmlNode);
AddCaseLoop(nowCaseNode.ChildCells[], tempProjectLoadInfo.times);
} return nextCase();
}
else if (nowCaseNode.CaseType == CaseType.Case)
{
queueNowCount++;
return nowCaseNode;
}
else
{
return null; //当前设计不会有这种情况
}
}
}
}
} } /// <summary>
/// CaseLoop it will only used in myCsaeQueue
/// </summary>
class myCaseLoop
{
private CaseCell startCaseNode;
private CaseCell nowCaseNode;
private int totalTimes;
private int myTimes; public event delegateLoopChangeEventHandler OnLoopChangeEvent; /// <summary>
/// myCaseLoop initialize
/// </summary>
/// <param name="yourStartCase">your StartCase and make sure it is not null</param>
/// <param name="yourTimes">your Times </param>
public myCaseLoop(CaseCell yourStartCase, int yourTimes)
{
totalTimes = myTimes = yourTimes;
startCaseNode = yourStartCase;
nowCaseNode = null;
} /// <summary>
/// get now CaseCell
/// </summary>
public CaseCell NowCaseNode
{
get
{
if (nowCaseNode != null)
{
return nowCaseNode;
}
else
{
return startCaseNode;
}
}
} /// <summary>
/// i will trigger 【OnLoopChangeEvent】
/// </summary>
/// <param name="yourTarget"></param>
private void ReportLoopProgress(CaseCell yourTarget)
{
if (OnLoopChangeEvent != null)
{
OnLoopChangeEvent(yourTarget.ParentCell, string.Format("{0}/{1}", totalTimes, totalTimes - myTimes + ));
}
} /// <summary>
/// i will trigger 【OnLoopChangeEvent】 and this lood is end
/// </summary>
/// <param name="yourTarget"></param>
private void ReportLoopEnd(CaseCell yourTarget)
{
if (OnLoopChangeEvent != null)
{
this.OnLoopChangeEvent(yourTarget.ParentCell, "");
}
} /// <summary>
/// i will get the next myTreeTagInfo in my loop
/// </summary>
/// <returns>the CaseCell you want</returns>
public CaseCell nextCase()
{
if (myTimes > )
{
if (nowCaseNode == null) //起始节点
{
nowCaseNode = startCaseNode;
//report position
ReportLoopProgress(nowCaseNode);
return nowCaseNode;
}
else
{
if (nowCaseNode.NextCell == null)
{
myTimes--;
if (myTimes > )
{
nowCaseNode = startCaseNode;
ReportLoopProgress(nowCaseNode);
return nowCaseNode;
}
else
{
ReportLoopEnd(nowCaseNode);
return null; //此处为null,指示当前【Loop】结束
} }
else
{
nowCaseNode = nowCaseNode.NextCell;
return nowCaseNode; //此处caseType可能为case或repeat,该类的拥有者将会分别处理
}
}
}
else
{
return null;
}
}
} /// <summary>
/// myCaseRunTime - you can get next case here
/// </summary>
public sealed class myCaseRunTime
{ private List<myCsaeQueue> myCsaeQueueList;
private bool isThroughAllCase; /// <summary>
/// show loop track
/// </summary>
public event delegateLoopChangeEventHandler OnLoopChangeEvent;
/// <summary>
/// show Queue track (the frist and last Queue will nor trigger)
/// </summary>
public event delegateQueueChangeEventHandler OnQueueChangeEvent; /// <summary>
/// myCaseRunTime initialize
/// </summary>
public myCaseRunTime()
{
myCsaeQueueList = new List<myCsaeQueue>();
} /// <summary>
/// get now CaseRunTime all Progress
/// </summary>
public List<KeyValuePair<int ,int >> GetNowCountProgress
{
get
{
List<KeyValuePair<int, int>> nowCountProgress = new List<KeyValuePair<int, int>>();
foreach (var tempCsaeQueue in myCsaeQueueList)
{
nowCountProgress.Add(tempCsaeQueue.GetCountProgress);
}
return nowCountProgress;
}
} /// <summary>
/// i will add new CsaeQueue and Subscribe 【OnLoopChangeEvent】
/// </summary>
/// <param name="yourCsaeQueue">your CsaeQueue that will add</param>
private void AddCsaeQueue(myCsaeQueue yourCsaeQueue)
{
myCsaeQueueList.Add(yourCsaeQueue);
yourCsaeQueue.OnLoopChangeEvent += OnLoopChangeEvent;
} //// <summary>
/// i will add new CsaeQueue and Subscribe 【OnLoopChangeEvent】(and will trigger【OnQueueChangeEvent】)
/// </summary>
/// <param name="yourCsaeQueue">your CsaeQueue that will add</param>
/// <param name="yourProjectId">Project Id to OnQueueChangeEvent</param>
/// <param name="yourCaseId">Case Id to OnQueueChangeEvent</param>
private void AddCsaeQueue(myCsaeQueue yourCsaeQueue, int yourProjectId, int yourCaseId)
{
ReportQueueAction(myCsaeQueueList[myCsaeQueueList.Count - ].NowCaseNode, string.Format("▼GoTo Project:{0} Case:{1}", yourProjectId, yourCaseId));
AddCsaeQueue(yourCsaeQueue);
ReportQueueAction(myCsaeQueueList[myCsaeQueueList.Count - ].NowCaseNode, "▲");
} /// <summary>
/// i will remove the CaseQueue and unSubscribe 【OnLoopChangeEvent】
/// </summary>
/// <param name="yourCsaeQueue">your CsaeQueue that will rwmove</param>
private void DelCsaeQueue(myCsaeQueue yourCsaeQueue)
{
if (myCsaeQueueList.Count>)
{
ReportQueueAction(yourCsaeQueue.NowCaseNode, "▼");
ReportQueueAction(myCsaeQueueList[myCsaeQueueList.Count - ].NowCaseNode, "▼▲");
}
yourCsaeQueue.OnLoopChangeEvent -= OnLoopChangeEvent;
myCsaeQueueList.Remove(yourCsaeQueue);
} /// <summary>
/// i will report the QueueAction to his user
/// </summary>
/// <param name="yourTarget">your CaseCell Target</param>
/// <param name="yourMessage">your Message</param>
private void ReportQueueAction(CaseCell yourTarget, string yourMessage)
{
if (OnQueueChangeEvent != null)
{
OnQueueChangeEvent(yourTarget, yourMessage);
}
} /// <summary>
/// you must readyStart before get nextCase (and here also can reset the StartCase)
/// </summary>
/// <param name="yourStartCase">your StartCase</param>
public void readyStart(CaseCell yourStartCase)
{
myCsaeQueueList.Clear();
AddCsaeQueue(new myCsaeQueue(yourStartCase));
ReportQueueAction(yourStartCase, "◆");
} /// <summary>
/// you must readyStart before get nextCase (and here also can reset the StartCase)
/// </summary>
/// <param name="yourStartCase">your StartCase</param>
/// <param name="yourIsThrough">it will change the behaviour that is it will go through all case(now it is replaced by [goto])</param>
public void readyStart(CaseCell yourStartCase, bool yourIsThrough)
{
readyStart(yourStartCase);
isThroughAllCase = yourIsThrough;
} /// <summary>
/// i will get the next myTreeTagInfo in myCaseRunTime
/// </summary>
/// <returns>the CaseCell you want</returns>
public CaseCell nextCase()
{
if (myCsaeQueueList.Count > )
{
CaseCell tempTreeNodeCase = myCsaeQueueList[myCsaeQueueList.Count - ].nextCase();
if(tempTreeNodeCase==null)
{
DelCsaeQueue(myCsaeQueueList[myCsaeQueueList.Count - ]);
return nextCase();
}
else
{
return tempTreeNodeCase;
}
}
else
{
return null;
}
} /// <summary>
/// here i will jump into other case in myCaseRunTime
/// </summary>
/// <param name="yourProjectId">your Project Id</param>
/// <param name="yourCaseId">your Case Id</param>
/// <returns>is success</returns>
public bool gotoMyCase(int yourProjectId, int yourCaseId, Dictionary<int, Dictionary<int, CaseCell>> myRunTimeCaseDictionary)
{
if (myRunTimeCaseDictionary.ContainsKey(yourProjectId))
{
if (myRunTimeCaseDictionary[yourProjectId].ContainsKey(yourCaseId))
{
AddCsaeQueue(new myCsaeQueue(myRunTimeCaseDictionary[yourProjectId][yourCaseId]), yourProjectId, yourCaseId);
return true;
}
else
{
ReportQueueAction(myCsaeQueueList[myCsaeQueueList.Count - ].NowCaseNode, "▼GoTo error");
return false;
}
}
else
{
return false;
}
}
}

而最终myCaseRunTime也是为CaseActionActuator 服务的,现在来看下CaseActionActuator。

CaseActionActuator相对比较多一点,因为要完成的功能会多一些,跟其他模块的联系也会大一些

一个基于.NET平台的自动化/压力测试系统设计简述

这个可能看起来就很乱了,上图的模块主要就是一个Case文件的在系统中的表现,可以理解为一个User,这个User通过Case脚本文件可以执行一套业务,执行过程也是独立的,环境,线程,数据也都是独立的。所以可以创建任意多个这种模块以模拟大量的用户同时操作,当然脚本可以使用不同的脚本文件,也可以使用相同脚本文件(若使用相同脚本文件系统会对当前模块进行深度克隆,克隆的用户共享部分不会影响运行的数据)。该模块还可以选择以Cell对UI控件进行绑定,以达到执行过程中用户界面的友好反馈,当然不同的UI控件的动态效果需要单独的处理(处理由另一个辅助模块myActionActuator完成)

这个模块的图起来乱点,不过code相对清晰,有兴趣可以看下面代码(贴出的是关键部分)

 /// <summary>
/// CASE执行器
/// </summary>
public class CaseActionActuator:IDisposable,ICloneable
{
#region Private Class
/// <summary>
/// 描述执行可能所需要的附加信息(可扩展),可以为null
/// </summary>
private class ExecutiveAdditionalInfo
{
private bool isRetry;
private int tryTimes;
private bool isStoping;
private bool isTryCase; public ExecutiveAdditionalInfo(bool yourIstry,int yourTryTimes)
{
isRetry = yourIstry;
tryTimes = yourTryTimes;
isStoping = false;
isTryCase = false;
} public ExecutiveAdditionalInfo(bool yourStoping)
{
isRetry = false;
isTryCase = false;
tryTimes = -;
isStoping = yourStoping;
} public ExecutiveAdditionalInfo(bool yourStoping, bool yourTryCase)
{
isRetry = false;
isTryCase = yourTryCase;
tryTimes = -;
isStoping = yourStoping;
} public bool IsReTry
{
get
{
return isRetry;
}
set
{
isRetry = value;
}
} public bool IsTryCase
{
get
{
return isTryCase;
}
} public int TryTimes
{
get
{
return tryTimes;
}
set
{
tryTimes = value;
}
} public bool IsStoping
{
get
{
return isStoping;
}
}
} /// <summary>
/// 描述单次执行所需的基本数据集
/// </summary>
private class ExecutivebasicData
{
myRunCaseData<ICaseExecutionContent> runCaseData;
TreeNode executiveNode; public ExecutivebasicData(myRunCaseData<ICaseExecutionContent> yourCaseData,TreeNode yourExecutiveNode)
{
runCaseData = yourCaseData;
executiveNode = yourExecutiveNode;
}
} #endregion /// <summary>
/// 克隆Actuator的根
/// </summary>
private CaseActionActuator rootActuator; /// <summary>
/// 执行线程同步器
/// </summary>
private ManualResetEvent myManualResetEvent = new ManualResetEvent(true); /// <summary>
/// 执行器名称
/// </summary>
private string myName; /// <summary>
/// Actuator State
/// </summary>
private CaseActuatorState runState; /// <summary>
/// case guide diver
/// </summary>
private myCaseRunTime caseRunTime; /// <summary>
/// ExecutionDevice List with his name【执行驱动器映射表】
/// </summary>
private Dictionary<string, ICaseExecutionDevice> myExecutionDeviceList; /// <summary>
/// Parameter List
/// </summary>
private Dictionary<string, string> runActuatorParameterList; /// <summary>
/// StaticData List
/// </summary>
private Dictionary<string, IRunTimeStaticData> runActuatorStaticDataList; /// <summary>
/// Execution Result List
/// </summary>
private List<myExecutionDeviceResult> runExecutionResultList; /// <summary>
/// RunTimeCaseDictionary
/// </summary>
private Dictionary<int, Dictionary<int, CaseCell>> runTimeCaseDictionary; /// <summary>
/// ProjctCollection
/// </summary>
private ProjctCollection runCellProjctCollection; /// <summary>
/// Actuator Task Thread
/// </summary>
private Thread myActuatorTaskThread;
private Thread myActuatorTryThread; /// <summary>
/// the thread not used and do not make it out of control
/// </summary>
private List<Thread> invalidThreadList; private string nowExecutiveData;
private string myErrorInfo; private int executiveThinkTime;
private int caseThinkTime; public delegate void delegateGetExecutiveDataEventHandler(string yourTitle, string yourContent);
public delegate void delegateGetActionErrorEventHandler(string yourContent);
public delegate void delegateGetExecutiveResultEventHandler(string sender, myExecutionDeviceResult yourResult);
public delegate void delegateGetActuatorStateEventHandler(string sender, CaseActuatorState yourState);
public delegate void delegateActuatorParameterListEventHandler(); public event delegateGetExecutiveData OnGetExecutiveData;
public event delegateGetExecutiveData OnGetActionError;
public event delegateGetExecutiveResultEventHandler OnExecutiveResult;
public event delegateGetActuatorStateEventHandler OnActuatorStateChanged;
public delegateActuatorParameterListEventHandler OnActuatorParameterListChanged; //外部需要访问 event修饰后,会禁止非创建类服务 /// <summary>
/// 构造函数
/// </summary>
public CaseActionActuator()
{
rootActuator = null;
myExecutionDeviceList = new Dictionary<string, ICaseExecutionDevice>();
runActuatorParameterList = new Dictionary<string, string>();
runActuatorStaticDataList = new Dictionary<string, IRunTimeStaticData>();
runExecutionResultList = new List<myExecutionDeviceResult>();
invalidThreadList = new List<Thread>();
myErrorInfo = "";
myName = "Main Actuator";
runState = CaseActuatorState.Stop;
executiveThinkTime = ;
} /// <summary>
/// 构造函数
/// </summary>
/// <param name="yourName">当前执行器的名称</param>
public CaseActionActuator(string yourName)
{
rootActuator = null;
myExecutionDeviceList = new Dictionary<string, ICaseExecutionDevice>();
runActuatorParameterList = new Dictionary<string, string>();
runActuatorStaticDataList = new Dictionary<string, IRunTimeStaticData>();
runExecutionResultList = new List<myExecutionDeviceResult>();
invalidThreadList = new List<Thread>();
myErrorInfo = "";
myName = yourName;
runState = CaseActuatorState.Stop; ;
} /// <summary>
/// 克隆
/// </summary>
/// <returns>克隆对象</returns>
public object Clone()
{
CaseActionActuator cloneActuator = new CaseActionActuator();
cloneActuator.rootActuator = null;
cloneActuator.myExecutionDeviceList = myExecutionDeviceList.MyClone();
cloneActuator.runActuatorParameterList = runActuatorParameterList.MyClone<string,string>();
cloneActuator.runActuatorStaticDataList = runActuatorStaticDataList.MyClone();
//cloneActuator.runExecutionResultList = new List<myExecutionDeviceResult>();
cloneActuator.SetCaseRunTime(this.runTimeCaseDictionary, this.runCellProjctCollection);
cloneActuator.caseThinkTime = this.caseThinkTime;
return cloneActuator;
} /// <summary>
/// 获取或设置执行器标识名
/// </summary>
public string MyName
{
get
{
return myName;
}
set
{
myName = value;
}
} /// <summary>
/// 获取或设置执行器的全局思考/等待时间
/// </summary>
public int ExecutiveThinkTime
{
get
{
return executiveThinkTime;
}
set
{
executiveThinkTime = value;
}
} /// <summary>
/// 获取【CaseActionActuator】运行状态
/// </summary>
public CaseActuatorState Runstate
{
get
{
return runState;
}
} /// <summary>
/// 获取当前任务执行进度
/// </summary>
public List<KeyValuePair<int ,int >> RunProgress
{
get
{
if (caseRunTime != null)
{
return caseRunTime.GetNowCountProgress;
}
else
{
return null;
}
}
} /// <summary>
/// 获取ErrorInfo属性
/// </summary>
public string ErrorInfo
{
get
{
return myErrorInfo;
}
} /// <summary>
/// 获取执行过程
/// </summary>
public string NowExecutiveData
{
get
{
return nowExecutiveData; }
} /// <summary>
/// 获取当前任务执行结果列表
/// </summary>
public List<myExecutionDeviceResult> NowExecutionResultList
{
get
{
return runExecutionResultList;
}
} /// <summary>
/// 获取当前参数化数据列表
/// </summary>
public Dictionary<string, string> NowParameterList
{
get
{
return runActuatorParameterList;
}
} /// <summary>
/// 获取当前静态参数化数据列表
/// </summary>
public Dictionary<string, IRunTimeStaticData> NowStaticDataList
{
get
{
return runActuatorStaticDataList;
}
} /// <summary>
/// 获取当前执行器列表
/// </summary>
public Dictionary<string, ICaseExecutionDevice> NowExecutionDeviceList
{
get
{
return myExecutionDeviceList;
}
} /// <summary>
/// 获取当前CASE列表
/// </summary>
public Dictionary<int, Dictionary<int, CaseCell>> RunTimeCaseDictionary
{
get
{
return runTimeCaseDictionary;
}
} /// <summary>
/// 获取当前ProjctCollection
/// </summary>
public ProjctCollection RunCellProjctCollection
{
get
{
return runCellProjctCollection;
}
} /// <summary>
/// 获取当前执行器是否填充过数据
/// </summary>
public bool IsActuatorDataFill
{
get
{
return ((runCellProjctCollection != null) && (runCellProjctCollection != null));
}
} /// <summary>
/// i can updata you myRunTimeCaseDictionary()
/// </summary>
/// <param name="yourCaseDictionary">you myRunTimeCaseDictionary</param>
public void UpdataRunTimeCaseDictionary(Dictionary<int, Dictionary<int, CaseCell>> yourCaseDictionary)
{
runTimeCaseDictionary = yourCaseDictionary;
} //RunTime Queue队列变化时通知
void caseRunTime_OnQueueChangeEvent(CaseCell yourTarget, string yourMessage)
{
if (yourMessage != "")
{
if (yourMessage.StartsWith("▲"))
{
//附加任务起始节点
myActionActuator.SetCaseNodeExpand(yourTarget);
}
else if (yourMessage.StartsWith("◆"))
{
//主任务起始节点
while(yourTarget.CaseType!=CaseType.Case)
{
if(yourTarget.IsHasChild)
{
yourTarget=yourTarget.ChildCells[];
}
else
{
break;
}
}
myActionActuator.SetCaseNodeExpand(yourTarget);
}
yourMessage = "【" + yourMessage + "】";
} myActionActuator.SetCaseNodeLoopChange(yourTarget, yourMessage);
} //RunTime Queue 中Loop变化时通知
void caseRunTime_OnLoopChangeEvent(CaseCell yourTarget, string yourMessage)
{
if (yourMessage!="")
{
myActionActuator.SetCaseNodeExpand(yourTarget);
myActionActuator.SetCaseNodeLoopRefresh(yourTarget);
yourMessage = "【" + yourMessage + "】";
}
myActionActuator.SetCaseNodeLoopChange(yourTarget, yourMessage);
} /// <summary>
/// i will load your ActionActuator (if your have another rule file ,please override or add a new realize)
/// </summary>
/// <param name="sourceNode">source Node</param>
public void LoadScriptRunTime(XmlNode sourceNode)
{
if (sourceNode != null)
{
if (sourceNode.HasChildNodes)
{
foreach (XmlNode tempNode in sourceNode.ChildNodes)
{
switch (tempNode.Name)
{
#region RunTimeParameter
case "RunTimeParameter":
if (tempNode.HasChildNodes)
{
foreach (XmlNode tempNodeChild in tempNode.ChildNodes)
{
if (tempNodeChild.Name == "NewParameter")
{
if (tempNodeChild.Attributes["name"] != null)
{
AddRunActuatorParameter(tempNodeChild.Attributes["name"].Value, tempNodeChild.InnerText);
}
else
{
SetNowActionError("can not find name in ScriptRunTime - RunTimeParameter");
}
}
else
{
SetNowActionError("find unkonw data in ScriptRunTime - RunTimeParameter");
}
}
}
break;
#endregion #region RunTimeActuator
case "RunTimeActuator":
if (tempNode.HasChildNodes)
{
string tempActuatorName = "";
CaseProtocol tempActuatorProtocol = CaseProtocol.unknownProtocol;
foreach (XmlNode tempNodeChild in tempNode.ChildNodes)
{
if (tempNodeChild.Name == "NewActuator")
{
if (tempNodeChild.Attributes["name"] != null && tempNodeChild.Attributes["protocol"] != null)
{
tempActuatorName = tempNodeChild.Attributes["name"].Value;
try
{
tempActuatorProtocol = (CaseProtocol)Enum.Parse(typeof(CaseProtocol), tempNodeChild.Attributes["protocol"].Value);
}
catch
{
tempActuatorProtocol = CaseProtocol.unknownProtocol;
SetNowActionError("find unknown Protocol in ScriptRunTime - RunTimeActuator");
}
switch (tempActuatorProtocol)
{
case CaseProtocol.vanelife_http:
myConnectForVanelife_http ConnectInfo = new myConnectForVanelife_http(tempActuatorProtocol, CaseTool.getXmlInnerVaule(tempNodeChild, "dev_key"), CaseTool.getXmlInnerVaule(tempNodeChild, "dev_secret"), CaseTool.getXmlInnerVaule(tempNodeChild, "default_url"));
AddExecutionDevice(tempActuatorName, ConnectInfo);
break;
case CaseProtocol.http:
myConnectForHttp ConnectInfo_http = new myConnectForHttp(tempActuatorProtocol, CaseTool.getXmlInnerVaule(tempNodeChild, "default_url"));
AddExecutionDevice(tempActuatorName, ConnectInfo_http);
break;
default:
SetNowActionError("find nonsupport Protocol in ScriptRunTime ");
break;
}
}
else
{
SetNowActionError("can not find name or protocol in ScriptRunTime - RunTimeActuator");
}
}
else
{
SetNowActionError("find unkonw data in ScriptRunTime - RunTimeActuator");
}
}
}
break;
#endregion #region RunTimeStaticData
case "RunTimeStaticData":
if (tempNode.HasChildNodes)
{
foreach (XmlNode tempNodeChild in tempNode.ChildNodes)
{
if (tempNodeChild.Name == "NewStaticData")
{
if (tempNodeChild.Attributes["name"] != null && tempNodeChild.Attributes["type"] != null)
{
CaseStaticDataType tempType;
string tempName = tempNodeChild.Attributes["name"].Value;
string tempTypeStr = tempNodeChild.Attributes["type"].Value;
string tempVaule = tempNodeChild.InnerText;
try
{
tempType = (CaseStaticDataType)Enum.Parse(typeof(CaseStaticDataType), "staticData_"+tempTypeStr);
}
catch
{
SetNowActionError("find unknown type in RunTimeStaticData - ScriptRunTime");
continue;
}
switch (tempType)
{
case CaseStaticDataType.staticData_index:
myStaticDataIndex tempStaticDataIndex;
string tempTypeError;
if (myCaseDataTypeEngine.getIndexStaticData(out tempStaticDataIndex, out tempTypeError, tempVaule))
{
runActuatorStaticDataList.myAdd(tempName, tempStaticDataIndex);
}
else
{
runActuatorStaticDataList.myAdd(tempName, tempStaticDataIndex);
SetNowActionError(tempVaule);
}
break;
case CaseStaticDataType.staticData_random:
myStaticDataRandomStr tempStaticDataRandomStr;
if(myCaseDataTypeEngine.getRandomStaticData(out tempStaticDataRandomStr,out tempTypeError,tempVaule))
{
runActuatorStaticDataList.myAdd(tempName, tempStaticDataRandomStr);
}
else
{
runActuatorStaticDataList.myAdd(tempName, tempStaticDataRandomStr);
SetNowActionError(tempVaule);
}
break;
case CaseStaticDataType.staticData_time:
myStaticDataNowTime tempStaticDataNowTime;
myCaseDataTypeEngine.getTimeStaticData(out tempStaticDataNowTime, tempVaule);
runActuatorStaticDataList.myAdd(tempName, tempStaticDataNowTime);
break;
default:
SetNowActionError("find nonsupport Protocol in RunTimeStaticData - ScriptRunTime ");
break;
} }
else
{
SetNowActionError("can not find name or type in RunTimeStaticData - ScriptRunTime");
}
}
else
{
SetNowActionError("find unkonw data in RunTimeStaticData - ScriptRunTime");
}
}
}
break;
#endregion default:
SetNowActionError("find unkonw data in ScriptRunTime");
break;
}
}
}
else
{
SetNowActionError("Error Source Node");
}
}
} /// <summary>
/// 连接这些器
/// </summary>
public void ConnectExecutionDevice()
{
SetNowExecutiveData("CaseExecutionDevice connecting ......");
foreach (KeyValuePair<string, ICaseExecutionDevice> tempKvp in myExecutionDeviceList)
{
if (tempKvp.Value.executionDeviceConnect())
{
SetNowExecutiveData(string.Format("【RunTimeActuator】:{0} 连接成功", tempKvp.Key));
}
else
{
SetNowActionError(tempKvp.Key + "connect fail");
}
}
SetNowExecutiveData("Connect complete");
} /// <summary>
/// 为执行器断开连接
/// </summary>
public void DisconnectExecutionDevice()
{
foreach (KeyValuePair<string, ICaseExecutionDevice> tempKvp in myExecutionDeviceList)
{
tempKvp.Value.executionDeviceClose();
}
} /// <summary>
/// 创建任务
/// </summary>
private void CreateNewActuatorTask()
{
//Thread myThreadTest = new Thread(new ThreadStart(ExecutiveActuatorTask),10240);
if (myActuatorTaskThread!=null)
{
if(myActuatorTaskThread.IsAlive)
{
invalidThreadList.Add(myActuatorTaskThread);
ClearInvalidThreadList();
SetNowActionError("Forced to terminate the residual task");
}
}
myActuatorTaskThread = new Thread(new ThreadStart(ExecutiveActuatorTask));
myActuatorTaskThread.Name = myName + "_ExecutiveActuatorTask";
myActuatorTaskThread.Priority = ThreadPriority.Normal;
myActuatorTaskThread.IsBackground = true;
myActuatorTaskThread.Start();
} /// <summary>
/// 执行任务
/// </summary>
private void ExecutiveActuatorTask()
{
ConnectExecutionDevice();
CaseCell nowExecutiveNode = null;
myRunCaseData<ICaseExecutionContent> nowRunCaseData = null;
ExecutiveAdditionalInfo nowAdditionalInfo; while((nowExecutiveNode=caseRunTime.nextCase())!=null)
{
if ((nowRunCaseData = nowExecutiveNode.CaseRunData) != null)
{
nowAdditionalInfo = null;
ExecutiveAnCases(nowRunCaseData, nowExecutiveNode,ref nowAdditionalInfo); while (nowAdditionalInfo != null)
{
//Stoping
if (nowAdditionalInfo.IsStoping)
{
SetNowExecutiveData("操作者主动终止任务");
goto EndTask;
}
//ReTry
if(nowAdditionalInfo.IsReTry)
{
nowAdditionalInfo.IsReTry = false;
ExecutiveAnCases(nowRunCaseData, nowExecutiveNode,ref nowAdditionalInfo);
}
else
{
break;
}
}
}
else
{
//没有执行数据请处理
SetNowActionError("严重异常,未找到合法执行数据"); }
}
EndTask:
DisconnectExecutionDevice();
SetRunState(CaseActuatorState.Stop);
SetNowExecutiveData("任务已结束");
} /// <summary>
/// 创建一个定项执行任务
/// </summary>
/// <param name="yourTryNode"></param>
private void CreateNewActuatorTry(CaseCell yourTryNode)
{
if (myActuatorTryThread != null)
{
if (myActuatorTryThread.IsAlive)
{
invalidThreadList.Add(myActuatorTryThread);
ClearInvalidThreadList();
SetNowActionError("Forced to terminate the residual task");
}
}
myActuatorTryThread = new Thread(new ParameterizedThreadStart(ExecutiveActuatorTry));
myActuatorTryThread.Name = myName + "_ExecutiveActuatorTry";
myActuatorTryThread.Priority = ThreadPriority.Normal;
myActuatorTryThread.IsBackground = true;
myActuatorTryThread.Start(yourTryNode);
} /// <summary>
/// 执行一次定项测试
/// </summary>
private void ExecutiveActuatorTry(object yourTryNode)
{
ConnectExecutionDevice();
CaseCell nowExecutiveNode = (CaseCell)yourTryNode;
myRunCaseData<ICaseExecutionContent> nowRunCaseData = nowExecutiveNode.CaseRunData;
ExecutiveAdditionalInfo nowAdditionalInfo; if (nowRunCaseData != null)
{
nowAdditionalInfo = new ExecutiveAdditionalInfo(false, true);
ExecutiveAnCases(nowRunCaseData, nowExecutiveNode, ref nowAdditionalInfo);
}
else
{
//没有执行数据请处理
SetNowActionError("严重异常,未找到合法执行数据");
} DisconnectExecutionDevice();
SetRunState(CaseActuatorState.Stop);
SetNowExecutiveData("定项执行完成");
} /// <summary>
/// 执行指定的Case
/// </summary>
/// <param name="nowRunCaseData">myRunCaseData</param>
/// <param name="nowExecutiveNode">now CaseCell</param>
private void ExecutiveAnCases(myRunCaseData<ICaseExecutionContent> nowRunCaseData, CaseCell nowExecutiveNode, ref ExecutiveAdditionalInfo nowAdditionalInfo)
{
bool tempIsBreakError = false;
myExecutionDeviceResult executionResult; if(runState==CaseActuatorState.Pause)
{
myActionActuator.SetCaseNodePause(nowExecutiveNode);
}
myManualResetEvent.WaitOne();
if (runState == CaseActuatorState.Stoping)
{
nowAdditionalInfo = new ExecutiveAdditionalInfo(true);
myActionActuator.SetCaseNodeStop(nowExecutiveNode);
return;
} if (nowRunCaseData.errorMessages == null)
{
if (myExecutionDeviceList.ContainsKey(nowRunCaseData.testContent.myCaseActuator))
{
var nowDevice = myExecutionDeviceList[nowRunCaseData.testContent.myCaseActuator];
ExecutionDeviceRunLink:
if (nowDevice.isDeviceConnect)
{
//nowDevice.executionDeviceRun()
myActionActuator.SetCaseNodeRunning(nowExecutiveNode);
executionResult = nowDevice.executionDeviceRun(nowRunCaseData.testContent, OnGetExecutiveData, myName,runActuatorParameterList, runActuatorStaticDataList, nowRunCaseData.id);
HandleCaseExecutiveResul(nowRunCaseData, nowExecutiveNode, executionResult,ref nowAdditionalInfo); }
else
{
//Device没有连接
SetNowExecutiveData(string.Format("【ID:{0}】 {1}连接中断,尝试连接中···", nowRunCaseData.id, nowRunCaseData.testContent.myCaseActuator));
if (nowDevice.executionDeviceConnect())
{
//nowDevice.executionDeviceRun()
goto ExecutionDeviceRunLink;
}
else
{
SetNowExecutiveData(string.Format("【ID:{0}】 {1}连接失败", nowRunCaseData.id, nowRunCaseData.testContent.myCaseActuator));
myActionActuator.SetCaseNodeConnectInterrupt(nowExecutiveNode); tempIsBreakError = true;
executionResult = new myExecutionDeviceResult(nowRunCaseData.id, "CaseActuator连接失败");
}
}
}
else
{
//testContent没有找到合适的myCaseActuator
SetNowExecutiveData(string.Format("【ID:{0}】 未找到指定CaseActuator", nowRunCaseData.id));
myActionActuator.SetCaseNodeNoActuator(nowExecutiveNode);
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode); tempIsBreakError = true;
executionResult = new myExecutionDeviceResult(nowRunCaseData.id, "未找到指定CaseActuator");
}
}
else
{
//nowRunCaseData有错误
SetNowActionError(string.Format("【ID:{0}】 执行数据脚本存在错误", nowRunCaseData.id));
myActionActuator.SetCaseNodeAbnormal(nowExecutiveNode); tempIsBreakError = true;
executionResult = new myExecutionDeviceResult(nowRunCaseData.id, "执行数据脚本存在错误" + nowRunCaseData.errorMessages);
} //AddExecutionResult
AddExecutionResult(executionResult); //Sleep
if (!tempIsBreakError)
{
int tempSleepTime = executiveThinkTime + caseThinkTime;
if(tempSleepTime>)
{
SetNowExecutiveData(string.Format("sleep {0} ms···", tempSleepTime));
Thread.Sleep(tempSleepTime);
}
} //nowExecutiveNode.TreeView.Invoke(new delegateBasicAnonymous(() => { }));
//nowExecutiveNode.TreeView.Invoke(new delegateBasicAnonymous(() => nowExecutiveNode.BackColor = System.Drawing.Color.LightSkyBlue)); } /// <summary>
/// 处理断言,及结果封装.用于【ExecutiveActuatorTask】
/// </summary>
/// <param name="yourRunData">确保其不为null</param>
/// <param name="yourExecutionResult">确保其不为null</param>
/// <param name="nowAdditionalInfo"></param>
private void HandleCaseExecutiveResul(myRunCaseData<ICaseExecutionContent> yourRunData, CaseCell nowExecutiveNode, myExecutionDeviceResult yourExecutionResult, ref ExecutiveAdditionalInfo nowAdditionalInfo)
{
string tempError;
yourExecutionResult.caseId = yourRunData.id;
if (yourExecutionResult.additionalEroor != null)
{
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode);
}
yourExecutionResult.expectMethod = yourRunData.caseExpectInfo.myExpectType;
yourExecutionResult.expectContent = yourRunData.caseExpectInfo.myExpectContent.getTargetContentData(runActuatorParameterList, runActuatorStaticDataList, out tempError);
if (tempError != null)
{
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode);
yourExecutionResult.additionalEroor = yourExecutionResult.additionalEroor.myAddValue(tempError);
}
if (CaseTool.CheckBackData(yourExecutionResult.backContent, yourExecutionResult.expectContent, yourRunData.caseExpectInfo.myExpectType))
{
yourExecutionResult.result = CaseResult.Pass;
myActionActuator.SetCaseNodePass(nowExecutiveNode);
}
else
{
yourExecutionResult.result = CaseResult.Fail;
myActionActuator.SetCaseNodeFial(nowExecutiveNode);
} #region ParameterSaves
if(yourRunData.caseAttribute.myParameterSaves!=null)
{
foreach (ParameterSave tempParameterSave in yourRunData.caseAttribute.myParameterSaves)
{
string tempPickVaule = null;
switch (tempParameterSave.parameterFunction)
{
case PickOutFunction.pick_json:
tempPickVaule = CaseTool.PickJsonParameter(tempParameterSave.parameterFindVaule, yourExecutionResult.backContent);
break;
case PickOutFunction.pick_str:
string tempFindVaule;
int tempLen;
CaseTool.GetStrPickData(tempParameterSave.parameterFindVaule,out tempFindVaule,out tempLen);
if (tempFindVaule!=null)
{
tempPickVaule = CaseTool.PickStrParameter(tempFindVaule, tempLen , yourExecutionResult.backContent);
}
else
{
tempError = string.Format("【ID:{0}】ParameterSave 脚本数据不合法", yourRunData.id);
yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError);
SetNowActionError(tempError);
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode);
} break;
case PickOutFunction.pick_xml:
tempPickVaule = CaseTool.PickXmlParameter(tempParameterSave.parameterFindVaule, yourExecutionResult.backContent);
break;
default:
tempError = string.Format("【ID:{0}】 ParameterSave 暂不支持该数据提取方式", yourRunData.id);
yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError);
SetNowActionError(tempError);
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode);
break;
}
if(tempPickVaule!=null)
{
SetNowActionError(string.Format("【ID:{0}】 ParameterSave 在执行结果中未找到指定参数", yourRunData.id));
}
else
{
AddRunActuatorParameter(tempParameterSave.parameterName, tempPickVaule);
}
}
}
#endregion #region actions
if (yourRunData.actions != null)
{
if (yourRunData.actions.Keys.Contains(yourExecutionResult.result))
{
switch (yourRunData.actions[yourExecutionResult.result].caseAction)
{
case CaseAction.action_alarm:
if (yourRunData.actions[yourExecutionResult.result].addInfo != null)
{
VoiceService.Speak(yourRunData.actions[yourExecutionResult.result].addInfo);
SetNowExecutiveData("【action_alarm】");
}
else
{
VoiceService.Beep();
}
break;
case CaseAction.action_continue:
//do nothing
break;
case CaseAction.action_goto:
if (nowAdditionalInfo != null)
{
//定项不执行goto
if(nowAdditionalInfo.IsTryCase)
{
break;
}
}
if (yourRunData.actions[yourExecutionResult.result].addInfo == null)
{
tempError = string.Format("【ID:{0}】 CaseAction Case数据中部没有发现目的ID", yourRunData.id);
yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError);
SetNowActionError(tempError);
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode);
}
else
{
int tempCaseID;
int tempProjectID;
if (CaseTool.getTargetCaseID(yourRunData.actions[yourExecutionResult.result].addInfo, out tempProjectID, out tempCaseID))
{
if (caseRunTime.gotoMyCase(tempProjectID, tempCaseID, runTimeCaseDictionary ))
{
SetNowExecutiveData("【action_goto】");
yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(string.Format("【action_goto】触发,已经跳转到Project:{0} Case:{1}", tempProjectID, tempCaseID));
}
else
{
tempError = string.Format("【ID:{0}】action_goto跳转任务未成功", yourRunData.id);
yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError);
SetNowActionError(tempError);
}
}
else
{
tempError = string.Format("【ID:{0}】 CaseAction 目标跳转Case不合法", yourRunData.id);
yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError);
SetNowActionError(tempError);
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode);
}
}
break;
case CaseAction.action_retry:
if (nowAdditionalInfo != null)
{
//定项不执行goto
if (nowAdditionalInfo.IsTryCase)
{
break;
}
}
if (yourRunData.actions[yourExecutionResult.result].addInfo != null)
{
try
{
int tempTryTimes = int.Parse(yourRunData.actions[yourExecutionResult.result].addInfo);
if (tempTryTimes > )
{
if (nowAdditionalInfo == null)
{
nowAdditionalInfo = new ExecutiveAdditionalInfo(true, tempTryTimes);
if (nowAdditionalInfo.TryTimes > )
{
SetNowExecutiveData("【action_retry】将被触发");
}
else
{
nowAdditionalInfo.IsReTry = false;
}
}
else
{
nowAdditionalInfo.TryTimes--;
yourExecutionResult.additionalRemark += string.Format("retry: {0}/{1}", tempTryTimes, tempTryTimes - nowAdditionalInfo.TryTimes);
if( nowAdditionalInfo.TryTimes > )
{
nowAdditionalInfo.IsReTry = true;
}
}
} }
catch
{
tempError = string.Format("【ID:{0}】 retry 解析错误", yourRunData.id);
yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError);
SetNowActionError(tempError);
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode);
}
}
else
{
if (nowAdditionalInfo == null)
{
nowAdditionalInfo = new ExecutiveAdditionalInfo(true, -);
}
else
{
yourExecutionResult.additionalRemark += "【action_retry】 always";
nowAdditionalInfo.IsReTry = true;
}
}
break;
case CaseAction.action_stop:
PauseCaseScript();
break;
case CaseAction.action_unknow:
tempError = string.Format("【ID:{0}】 CaseAction 未能解析", yourRunData.id);
yourExecutionResult.additionalRemark = yourExecutionResult.additionalRemark.myAddValue(tempError);
SetNowActionError(tempError);
myActionActuator.SetCaseNodeContentWarning(nowExecutiveNode);
break;
default:
//do nothing
break; }
}
}
#endregion #region Sleep
if(yourRunData.caseAttribute.attributeDelay>)
{
caseThinkTime = yourRunData.caseAttribute.attributeDelay;
}
else
{
if(caseThinkTime!=)
{
caseThinkTime = ;
}
}
#endregion } /// <summary>
/// 重置ErrorInfo
/// </summary>
public void ResetErrorInfo()
{
myErrorInfo = "";
} /// <summary>
/// 重置NowExecutiveData
/// </summary>
public void ResetNowExecutiveData()
{
nowExecutiveData = "";
} /// <summary>
/// 触发【OnGetExecutiveData】
/// </summary>
/// <param name="yourContent"></param>
private void SetNowExecutiveData(string yourContent)
{
if (OnGetExecutiveData != null)
{
this.OnGetExecutiveData(myName, yourContent);
}
} /// <summary>
/// 设置nowExecutiveData 及触发【OnGetExecutiveData】
/// </summary>
/// <param name="yourContent"></param>
private void SetAndSaveNowExecutiveData(string yourContent)
{
nowExecutiveData = yourContent;
if (OnGetExecutiveData != null)
{
this.OnGetExecutiveData(myName, yourContent);
}
} /// <summary>
/// 触发【OnGetActionError】
/// </summary>
/// <param name="yourContent">Action Error Content</param>
private void SetNowActionError(string yourContent)
{
if (OnGetActionError != null)
{
this.OnGetActionError(myName, yourContent);
}
} /// <summary>
/// 设置 myErrorInfo 并 触发【OnGetActionError】(若不想触发OnGetActionError请直接操作myErrorInfo)
/// </summary>
/// <param name="yourContent">Action Error Content</param>
private void SetAndSaveNowActionError(string yourContent)
{
myErrorInfo = yourContent;
if (OnGetActionError != null)
{
this.OnGetActionError(myName, myErrorInfo);
}
} /// <summary>
/// 设置 runState 并 触发【OnActuatorStateChanged】
/// </summary>
/// <param name="yourStae"></param>
private void SetRunState(CaseActuatorState yourStae)
{
runState = yourStae;
if(OnActuatorStateChanged!=null)
{
this.OnActuatorStateChanged(myName, yourStae);
}
} /// <summary>
/// 添加执行结果到结果集并触发【OnExecutiveResult】
/// </summary>
/// <param name="yourExecutionResult">your ExecutionResult</param>
private void AddExecutionResult(myExecutionDeviceResult yourExecutionResult)
{
runExecutionResultList.Add(yourExecutionResult);
if (OnExecutiveResult != null)
{
this.OnExecutiveResult(myName, yourExecutionResult);
}
} /// <summary>
/// 添加ExecutionDevice
/// </summary>
/// <param name="yourDeviceConnectInfo"></param>
/// <returns></returns>
private bool AddExecutionDevice(string yourDeviceName, IConnectExecutiveData yourDeviceConnectInfo)
{
switch (yourDeviceConnectInfo.myCaseProtocol)
{
case CaseProtocol.vanelife_http:
myExecutionDeviceList.myAdd(yourDeviceName, new CaseProtocolExecutionForVanelife_http((myConnectForVanelife_http)yourDeviceConnectInfo));
break;
case CaseProtocol.http:
myExecutionDeviceList.myAdd(yourDeviceName, new CaseProtocolExecutionForHttp((myConnectForHttp)yourDeviceConnectInfo));
break;
default:
SetNowActionError(yourDeviceName + " is an nonsupport Protocol");
break;
}
return true;
} /// <summary>
/// 添加或修改【runActuatorParameterList】
/// </summary>
/// <param name="yourParameterName"> Parameter Name</param>
/// <param name="yourParameterVaule">Parameter Vaule</param>
public void AddRunActuatorParameter(string yourParameterName, string yourParameterVaule)
{
runActuatorParameterList.myAdd(yourParameterName, yourParameterVaule);
if (OnActuatorParameterListChanged!=null)
{
this.OnActuatorParameterListChanged();
}
} /// <summary>
/// 设置 【case guide diver】
/// </summary>
/// <param name="yourCaseDictionary">your Case ID list</param>
public void SetCaseRunTime(Dictionary<int, Dictionary<int, CaseCell>> yourCaseDictionary, ProjctCollection yourProjctCollection)
{
if (yourCaseDictionary != null && yourProjctCollection!=null)
{
runTimeCaseDictionary = yourCaseDictionary;
runCellProjctCollection = yourProjctCollection;
caseRunTime = new myCaseRunTime();
caseRunTime.OnLoopChangeEvent += caseRunTime_OnLoopChangeEvent;
caseRunTime.OnQueueChangeEvent += caseRunTime_OnQueueChangeEvent;
}
else
{
SetNowActionError("your CaseDictionary or ProjctCollection is null");
}
} /// <summary>
/// 获取执行器是否可以执行
/// </summary>
/// <returns>is ok</returns>
private bool IsActionActuatorCanRun(CaseCell yourStartNode)
{
if (runCellProjctCollection == null)
{
SetAndSaveNowActionError("your CellProjctCollection is null");
return false;
}
if (runTimeCaseDictionary==null)
{
SetAndSaveNowActionError("your RunTimeCaseDictionary is null");
return false;
}
if (caseRunTime == null)
{
SetAndSaveNowActionError("your CaseRuntime is null");
return false;
}
if (myExecutionDeviceList.Count == )
{
SetAndSaveNowActionError("can not find any ExecutionDevice");
return false;
}
if (yourStartNode==null)
{
SetAndSaveNowActionError("your StartNode is null");
return false;
}
return true;
} /// <summary>
/// 执行项目任务
/// </summary>
/// <param name="yourStartNode">起始节点</param>
/// <returns>Success</returns>
public bool RunCaseScript(CaseCell yourStartNode)
{
switch (runState)
{
case CaseActuatorState.Running:
SetAndSaveNowActionError("当前任务还未结束");
return false;
case CaseActuatorState.Stoping:
SetAndSaveNowActionError("当前任务正在终止中");
return false;
case CaseActuatorState.Pause:
SetRunState(CaseActuatorState.Running);
myManualResetEvent.Set();
SetNowExecutiveData("任务恢复");
return true;
case CaseActuatorState.Stop:
if (yourStartNode == null)
{
SetAndSaveNowActionError("未发现任何可用节点");
return false;
}
else if (!IsActionActuatorCanRun(yourStartNode))
{
return false;
}
caseRunTime.readyStart(yourStartNode);
runExecutionResultList.Clear();
SetRunState(CaseActuatorState.Running);
myManualResetEvent.Set();
CreateNewActuatorTask();
SetNowExecutiveData("任务开始");
return true;
case CaseActuatorState.Trying:
SetAndSaveNowActionError("存在未还未结束指定项任务");
return false;
default:
return false;
} } /// <summary>
/// 暂停当前项目任务(可恢复)
/// </summary>
/// <returns>Success</returns>
public bool PauseCaseScript()
{
if (runState == CaseActuatorState.Running)
{
myManualResetEvent.Reset();
SetRunState(CaseActuatorState.Pause);
SetNowExecutiveData("任务已暂停");
return true;
}
else
{
SetAndSaveNowActionError("未发现处于运行状态中的任务");
return false;
}
} /// <summary>
/// 停止项目(不可恢复)
/// </summary>
/// <returns></returns>
public bool StopCaseScript()
{
if (runState == CaseActuatorState.Running)
{
SetRunState(CaseActuatorState.Stoping);
SetNowExecutiveData("正在终止任务");
return true;
}
else if (runState == CaseActuatorState.Pause)
{
myManualResetEvent.Set();
SetRunState(CaseActuatorState.Stoping);
SetNowExecutiveData("正在终止任务");
return true;
}
else if (runState == CaseActuatorState.Stoping)
{
SetAndSaveNowActionError("正在终止任务");
return false;
}
else
{
SetAndSaveNowActionError("当前项目已经停止");
return false;
}
} /// <summary>
/// 单步执行项目成员(必须在Pause状态,即时不在Pause状态,也会先进行Pause)
/// </summary>
/// <param name="yourNode">当前项目(如果项目已经开始可以为null)</param>
/// <returns>Success</returns>
public bool TryNextCaseScript(CaseCell yourNode)
{
if (runState == CaseActuatorState.Running)
{
PauseCaseScript();
myManualResetEvent.Set();
SetNowExecutiveData("单步执行>");
myManualResetEvent.Reset();
return true; }
else if (runState == CaseActuatorState.Stop)
{
if (RunCaseScript(yourNode))
{
PauseCaseScript();
SetNowExecutiveData("单步执行>");
myManualResetEvent.Set();
myManualResetEvent.Reset();
return true;
}
else
{
SetAndSaveNowActionError("无法进行单步执行");
return false;
}
}
else if (runState == CaseActuatorState.Pause)
{
SetNowExecutiveData("单步执行>");
myManualResetEvent.Set();
myManualResetEvent.Reset();
return true;
}
else if (runState == CaseActuatorState.Running)
{
SetAndSaveNowActionError("正在结束项目,无法进行单步执行");
return false;
}
else if (runState == CaseActuatorState.Trying)
{
SetAndSaveNowActionError("存在正在执行指定项任务");
return false;
}
return false;
} /// <summary>
/// 定项执行指定项(一直执行同一条CASE,仅在项目停止后可以使用,且goto及retry在这种任务中无效)
/// </summary>
/// <param name="yourNode">定项 数据</param>
/// <returns>is Success</returns>
public bool TryNowCaseScript(CaseCell yourNode)
{
if (runState == CaseActuatorState.Stop)
{
SetRunState(CaseActuatorState.Trying);
myManualResetEvent.Set();
CreateNewActuatorTry(yourNode);
return true;
}
else if (runState == CaseActuatorState.Trying)
{
SetAndSaveNowActionError("上一个指定项任务还未结束");
return false;
}
else
{
SetAndSaveNowActionError("要进行定向执行前,必须先停止任务");
return false;
}
} /// <summary>
/// 强制关闭所有正在执行的任务(谨慎调用)
/// </summary>
public void KillAll()
{
if (myActuatorTaskThread != null)
{
if (myActuatorTaskThread.IsAlive)
{
myActuatorTaskThread.Abort();
DisconnectExecutionDevice();
SetRunState(CaseActuatorState.Stop);
}
}
if (myActuatorTryThread != null)
{
if (myActuatorTryThread.IsAlive)
{
myActuatorTryThread.Abort();
DisconnectExecutionDevice();
SetRunState(CaseActuatorState.Stop);
}
} ClearInvalidThreadList();
} /// <summary>
/// 清理无效线程列表
/// </summary>
private void ClearInvalidThreadList()
{
for (int i = invalidThreadList.Count - ; i >= ; i--)
{
ErrorLog.PutInLog(string.Format("fing InvalidThread Name:{0} State:{1}" , invalidThreadList[i].Name ,invalidThreadList[i].ThreadState.ToString()));
if (invalidThreadList[i].IsAlive)
{
invalidThreadList[i].Abort();
}
else
{
invalidThreadList.Remove(invalidThreadList[i]);
}
}
for (int i = invalidThreadList.Count - ; i >= ; i--)
{
if (!invalidThreadList[i].IsAlive)
{
invalidThreadList.Remove(invalidThreadList[i]);
}
}
} /// <summary>
/// 实现【IDisposable】强烈建议结束前调用(即时当前可用使用disconnectExecutionDevice替代)
/// </summary>
public void Dispose()
{
KillAll();
DisconnectExecutionDevice();
myExecutionDeviceList.Clear();
runActuatorParameterList.Clear();
runActuatorStaticDataList.Clear();
runTimeCaseDictionary = null;
runCellProjctCollection = null;
if (caseRunTime!=null)
{
caseRunTime.OnLoopChangeEvent -= caseRunTime_OnLoopChangeEvent;
caseRunTime.OnQueueChangeEvent -= caseRunTime_OnQueueChangeEvent;
caseRunTime = null;
} } }

同时CaseActionActuator还包含一个比较重要的部分myCaseProtocolEngine,该部分是协议引擎,实际上只要PC上可以使用的协议通过简单的修改都可以加入到系统

一个基于.NET平台的自动化/压力测试系统设计简述

对于协议引擎必须继承规定好的接口实现,然后就能将协议加入系统,具体方法见【http://www.cnblogs.com/lulianqi/p/4773268.html】

这个模块还要许多其他部分比如Case文件的解析,及其他可以自定义的内容这里就不继续讲下去了,有兴趣的可以下载完整代码。

辅助工具模块MyCommonTool

MyCommonTool其实包含的东西也比较多不过功能都相对独立,见下图

一个基于.NET平台的自动化/压力测试系统设计简述

可以看到这个模块完全是由一个个独立的类组成,相互之间的联系非常少。有很多子模块提供不同的服务通过命名也大概可以看出来VoiceService提供语音服务,myWebTool提供web服务,myEncryption提供加密服务等等,它被系统的很多模块使用。由于类比较多而又没有什么逻辑上的联系,这里同样不继续讲下去了。需要了解的可以下载完整的代码

辅助UI显示模块MyControl

这个部分主要与UI的形式与呈现有关,当然包括了很多定制的数据的banding,在AutoTest及RemoteService都会用到里面的东西。不过针对不同的界面框架这部分是不能重用的。所以也不用怎么看,稍微贴张图吧。

一个基于.NET平台的自动化/压力测试系统设计简述

主程序界面AutoTest

对于AutoTest本身已经没有太多执行相关的逻辑处理了(都由CaseExecutiveActuator来处理),其主要处理case的呈现,及执行过程中的动态修改,还包括报告的格式化输出,同时还要2个重要功能组织多个User(执行体)跟连接远程测试主机。

先稍微过下界面相关的内容,如下图

一个基于.NET平台的自动化/压力测试系统设计简述                               一个基于.NET平台的自动化/压力测试系统设计简述

  • AutoTest                                                              这当然是主体
  • AutoTest‎.myDialogWindow                                  这里包含UI系统下的除主体外的其他UI窗口(右边的图展开了该项,可以看下)
  • AutoTest‎.myControl                                             同样是个UI控件的定制集合,不过这里的控件仅在AutoTest适合使用
  • AutoTest‎.myTool                                                  工具类,为AutoTest 的行为提供便利
  • AutoTest‎.RemoteServiceReference                     分布式远程访问模块,这个模块是引用WCF自动生成的,用于连接远程主机(这里WCF使用的自承载方式,程序运行时并不需要去配置什么其他的内容)

现在来看刚刚提到的一个重要的功能组织多个User,AutoTest可以创建任意多个虚拟执行体User,他们存储在CaseRunner中,同时CaseRunner还提供一些对User的基本的控制功能,如下图

一个基于.NET平台的自动化/压力测试系统设计简述

而这个部分其实放在逻辑层更加合适,不过之前设计经验不足。把CaseRunner于UI控件十分深的banding在一起了。绑定效果如下图

一个基于.NET平台的自动化/压力测试系统设计简述

这个就是大量CaseRunner绑定的效果(这个不是主界面,整体的显示效果可以看下【http://www.cnblogs.com/lulianqi/p/4773146.html】)

当然还有前面提到的另外一个重要功能连接远程测试主机,主要负责连接分布式部署在其他PC上的执行模块(就是后面要讲的RemoteService),同时可以控制及配置远程主机上的User(还包括连接的维持)。连接功能由RemoteClient组件来完成,它在前面提到的AutoTest‎.myTool 下,简单看下它的实现

基本结构如下图

一个基于.NET平台的自动化/压力测试系统设计简述

这个其实实现起来比较简单,因为借助WCF框架,很多工作框架已经做好了,不过一些通道的选择配置以及是否适合双工通信可能不是最优的,后面还需要调整,可以看下下面的code

  class ExecuteServiceCallback : IExecuteServiceCallback
{ public ExecuteServiceCallback()
{ } public delegate void RunnerStateChangeEventHandler(ExecuteServiceCallback sender, RemoteRunnerInfo remoteRunnerInfo); public event RunnerStateChangeEventHandler OnRunnerStateChange; public void ReportState(RemoteRunnerInfo remoteRunnerInfo)
{
if (OnRunnerStateChange!=null)
{
this.OnRunnerStateChange(this, remoteRunnerInfo);
}
}
} public class RemoteClient:IDisposable
{ /// <summary>
/// 描述当前Client连接状态
/// </summary>
public enum RemoteClientState
{
Connecting=, //正在连接
Connected=, //连接成功
Break = , //连接中断,并且正在进行重连
Lost = //连接中断,且不会重新连接(必须触发重连)
} ExecuteServiceClient executeServiceClient = null;
InstanceContext instanceContext = null;
//EndpointAddress myEp = new EndpointAddress("http://localhost:8087/SelService");
EndpointAddress myEp = null; private RemoteRunner showWindow;
private bool isLive = false;
private RemoteClientState clientState = RemoteClientState.Lost;
private int reCounectTime=; private Thread clientLife; public delegate void RunnerStateChangeEventHandler(RemoteClient sender, RemoteRunnerInfo remoteRunnerInfo);
public delegate void AllRunnerInforEventHandler(RemoteClient sender, RemoteRunnerInfo remoteRunnerInfo);
public delegate void ClientStateChangeEventHandler(RemoteClient sender, RemoteClientState nowSate);
public delegate void ClientErrorEventHandler(RemoteClient sender, string errorInfo); public event RunnerStateChangeEventHandler OnRunnerStateChange;
public event AllRunnerInforEventHandler OnPutAllRunnerInfor;
public event ClientStateChangeEventHandler OnClientStateChange;
public event ClientErrorEventHandler OnClientErrorInfor; public RemoteClient(EndpointAddress yourEp, RemoteRunner yourWindow )
{
myEp = yourEp;
showWindow = yourWindow;
} public override string ToString()
{
if (myEp != null)
{
return myEp.Uri.Host + ":" + myEp.Uri.Port;
}
else
{
return "Null Ep";
}
} /// <summary>
/// 获取或设置RemoteClient 的基础地址
/// </summary>
public EndpointAddress ClientEp
{
get { return myEp;}
set { myEp = value; }
} /// <summary>
/// 获取或设置ShowWindow
/// </summary>
public RemoteRunner ShowWindow
{
get { return showWindow; }
set { showWindow = value; }
} /// <summary>
/// 获取当前Client连接状态(自维护状态,该状态同时提示生命线程运行情况)
/// </summary>
public RemoteClientState ClientState
{
get { return clientState; }
} /// <summary>
/// 获取当前executeServiceClient通道状态
/// </summary>
public CommunicationState ExecuteServiceClientState
{
get
{
if (executeServiceClient==null)
{
return CommunicationState.Closed;
}
return executeServiceClient.State;
}
} /// <summary>
/// 获取或设置断线重连次数限制
/// </summary>
public int ReCounectTime
{
get { return reCounectTime; }
set { reCounectTime = value; }
} /// <summary>
/// 报告当前Client所有Runner状态
/// </summary>
/// <param name="remoteRunnerInfo"></param>
private void PutAllRunnerInfor(RemoteRunnerInfo remoteRunnerInfo)
{
if (OnPutAllRunnerInfor != null)
{
this.OnPutAllRunnerInfor(this, remoteRunnerInfo);
}
if (showWindow != null)
{
showWindow.RefreshRemoteRunnerView(remoteRunnerInfo);
}
} /// <summary>
/// 改变连接状态
/// </summary>
private void SetClientState(RemoteClientState nowState)
{
clientState = nowState;
if (OnClientStateChange != null)
{
this.OnClientStateChange(this, nowState);
}
} /// <summary>
/// 向订阅者报告错误信息
/// </summary>
/// <param name="errorInfo">错误信息</param>
private void SetClientErrorInfo(string errorInfo)
{
if(errorInfo!=null && OnClientErrorInfor!=null)
{
this.OnClientErrorInfor(this, errorInfo);
}
} /// <summary>
/// 创建一个【ExecuteServiceClient】实例
/// </summary>
/// <returns>【ExecuteServiceClient】实例</returns>
private ExecuteServiceClient RestartClient()
{
if (instanceContext==null)
{
//InstanceContext
ExecuteServiceCallback executeServiceCallback = new ExecuteServiceCallback();
executeServiceCallback.OnRunnerStateChange += executeServiceCallback_OnRunnerStateChange;
instanceContext = new InstanceContext(executeServiceCallback);
//Binding
System.ServiceModel.Channels.Binding binding = new WSDualHttpBinding();
((System.ServiceModel.WSDualHttpBinding)(binding)).Security.Mode = WSDualHttpSecurityMode.None;
((System.ServiceModel.WSDualHttpBinding)(binding)).Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.SendTimeout = new TimeSpan(, , );
binding.OpenTimeout = new TimeSpan(, , );
binding.ReceiveTimeout = new TimeSpan(, , ); System.ServiceModel.Channels.Binding tcpBinding = new NetTcpBinding();
((System.ServiceModel.NetTcpBinding)(tcpBinding)).Security.Mode = SecurityMode.None;
((System.ServiceModel.NetTcpBinding)(tcpBinding)).Security.Message.ClientCredentialType = MessageCredentialType.UserName;
tcpBinding.SendTimeout = new TimeSpan(, , );
tcpBinding.OpenTimeout = new TimeSpan(, , );
tcpBinding.ReceiveTimeout = new TimeSpan(, , ); executeServiceClient = new ExecuteServiceClient(instanceContext, binding, myEp);
//executeServiceClient = new ExecuteServiceClient(instanceContext, new WSDualHttpBinding(), myEp); instanceContext.Closed += instanceContext_Closed;
instanceContext.Opened += instanceContext_Opened;
return executeServiceClient;
}
else
{
instanceContext.Closed -= instanceContext_Closed;
instanceContext.Opened -= instanceContext_Opened;
instanceContext = null;
return RestartClient();
}
} #region RestartClient通信 /// <summary>
/// 处理收到的双工回调(仅报告有变化的Runner)
/// </summary>
void executeServiceCallback_OnRunnerStateChange(ExecuteServiceCallback sender, RemoteRunnerInfo remoteRunnerInfo)
{
if(OnRunnerStateChange!=null)
{
this.OnRunnerStateChange(this, remoteRunnerInfo);
}
if (remoteRunnerInfo == null)
{
if (showWindow != null)
{
showWindow.ShowError("Null Data" + "\r\n");
}
return;
}
if (remoteRunnerInfo.RunnerStateList != null)
{
if (showWindow != null)
{
showWindow.UpdataRemoteRunnerView(remoteRunnerInfo);
}
}
} /// <summary>
/// 获取当前Client所有Runner状态
/// </summary>
public void GetAllRunnerInfor()
{
if (ExecuteServiceClientState == CommunicationState.Opened)
{
try
{
RemoteRunnerInfo nowRemoteRunnerInfo = executeServiceClient.GetAllRunnerSate();
if (nowRemoteRunnerInfo != null)
{
PutAllRunnerInfor(nowRemoteRunnerInfo);
}
}
catch (Exception ex)
{
SetClientErrorInfo(ex.Message);
}
}
else
{
SetClientErrorInfo("连接未打开");
}
} /// <summary>
/// 获取当前Client所有Runner状态(提供内部使用,不会捕捉错误)
/// </summary>
private void GetAllRunnerInforEx()
{
RemoteRunnerInfo nowRemoteRunnerInfo = executeServiceClient.GetAllRunnerSate();
if (nowRemoteRunnerInfo != null)
{
PutAllRunnerInfor(nowRemoteRunnerInfo);
}
} /// <summary>
/// 启动指定执行器
/// </summary>
/// <param name="runnerList">执行器列表</param>
public void StartRunner(List<int> runnerList)
{
if (ExecuteServiceClientState == CommunicationState.Opened)
{
try
{
executeServiceClient.StartRunner(runnerList.ToArray());
}
catch (Exception ex)
{
SetClientErrorInfo(ex.Message);
}
}
else
{
SetClientErrorInfo("连接未打开");
}
} /// <summary>
/// 暂停指定执行器
/// </summary>
/// <param name="runnerList">执行器列表</param>
public void PauseRunner(List<int> runnerList)
{
if (ExecuteServiceClientState == CommunicationState.Opened)
{
try
{
executeServiceClient.PauseRunner(runnerList.ToArray());
}
catch (Exception ex)
{
SetClientErrorInfo(ex.Message);
}
}
else
{
SetClientErrorInfo("连接未打开");
}
} /// <summary>
/// 停止指定执行器
/// </summary>
/// <param name="runnerList">执行器列表</param>
public void StopRunner(List<int> runnerList)
{
if (ExecuteServiceClientState == CommunicationState.Opened)
{
try
{
executeServiceClient.StopRunner(runnerList.ToArray());
}
catch(Exception ex)
{
SetClientErrorInfo(ex.Message);
}
}
else
{
SetClientErrorInfo("连接未打开");
}
} #endregion private void instanceContext_Closed(object sender, EventArgs e)
{ } void instanceContext_Opened(object sender, EventArgs e)
{ } /// <summary>
/// 启动当前Client
/// </summary>
public void StartClient()
{
//Thread myThreadTest = new Thread(new ThreadStart(ExecutiveActuatorTask),10240);
if (clientLife != null)
{
if (clientLife.IsAlive)
{
if (showWindow != null)
{
showWindow.ShowError("IsAlive");
}
}
else
{
clientLife = null;
StartClient();
}
}
else
{
clientLife = new Thread(new ParameterizedThreadStart(ClientAliveTask));
clientLife.Name = "ClientLife" + myEp.ToString();
clientLife.Priority = ThreadPriority.Normal;
clientLife.IsBackground = true;
isLive = true;
clientLife.Start(executeServiceClient);
}
} /// <summary>
/// 停止当前Client
/// </summary>
public void StopClient()
{
isLive = false;
} /// <summary>
/// 保持连接的心跳及重新连接的线程任务
/// </summary>
private void ClientAliveTask(object yourExecuteClient)
{
ExecuteServiceClient executeClient = (ExecuteServiceClient)yourExecuteClient;
int counectTime = reCounectTime; ReConnect:
executeClient = RestartClient();
try
{
SetClientState(RemoteClientState.Connecting);
GetAllRunnerInforEx();
SetClientState(RemoteClientState.Connected);
}
catch (Exception ex)
{
MyCommonTool.ErrorLog.PutInLogEx(ex);
if (counectTime > && isLive)
{
counectTime--;
SetClientState(RemoteClientState.Break);
Thread.Sleep();
goto ReConnect;
}
else
{
StopClient();
}
} while (isLive)
{
try
{
executeClient.ExecuteServiceBeat();
}
catch (Exception ex)
{
SetClientState(RemoteClientState.Break);
Thread.Sleep();
MyCommonTool.ErrorLog.PutInLogEx(ex);
counectTime = reCounectTime;
goto ReConnect;
}
Thread.Sleep();
} SetClientState(RemoteClientState.Lost);
} public void Dispose()
{
if (clientLife != null)
{
if (clientLife.IsAlive)
{
clientLife.Abort();
}
}
executeServiceClient = null;
instanceContext = null;
}
}
}

分布式部署服务模块RemoteService

这个部分与AutoTest整体结构是相似的,在执行逻辑上也是同样依靠逻辑层的3个主要模块。而内部基于WCF单独实现了服务部分,服务内容主要包括对状态及业务返回数据进行过滤重组然后上报给连接的控制机,还有接收及处理控制机的控制或配置等命令请求。

下面看一下自身的结构,如下图

一个基于.NET平台的自动化/压力测试系统设计简述

可以看到结构上与AutoTest确实是大体一致的,主要看下跟分布式部署相关的2个组件

  • ExecuteService             直接提供服务说需要的部分(是WCF的必要部分)
  • ServerHost                    对服务连接的抽象再封装,所有对服务的操作,包括获取服务数据都通过这个组件,下层应用不直接使用ExecuteService服务(从结构关系图中也可以看出来ExecuteService只被ServerHost调用)

其他部分与远程服务没有太大关系的就不介绍了

ExecuteService 按要求实现IExecuteService接口,结构跟逻辑都比较清晰(现在这部分的设计十分简单,后期对服务内容的控制也只需要改动这个地方就可以了),直接看下下面的结构图

一个基于.NET平台的自动化/压力测试系统设计简述

ServerHost 其实就是对ExecuteService再一层的封装,加入了许多与执行数据相关的逻辑,以方便应用层面的直接调用,如下图

一个基于.NET平台的自动化/压力测试系统设计简述

如上图,订阅了多个回调委托来获取数据,在内部进行处理(MessageTransferChannel_RunnerCommandCallback,MessageTransferChannel_GetAllRemoteRunnerInfoCallback),同时也定义了许多委托抛出数据给应用使用,同时还需要介绍应用的命令处理后转发给ExecuteService,当然还有一个重要的功能,服务的启动跟维持也在这里完成。下面是这个类code的具体实现

  class ServerHost
{
Uri baseAddress = new Uri("http://localhost:8087/SelService");//初始默认值在运行时由设置值来决定
ServiceHost baseHost = null; public delegate void ServerHostMessageEventHandler(string sender, string message);
public event ServerHostMessageEventHandler OnServerHostMessage; public delegate List<CaseRunner> BackNowRunnerListEventHandler();
public event BackNowRunnerListEventHandler OnBackNowRunnerList; //public delegate void ServerHostCommandEventHandler(RunnerCommand command, List<int> runners);
//public event ServerHostCommandEventHandler OnServerHostCommand; /// <summary>
/// 获取或设置当前BaseAddress
/// </summary>
public Uri BaseAddress
{
get { return baseAddress; }
set { baseAddress = value; }
} /// <summary>
/// 获取当前BaseHost
/// </summary>
public ServiceHost BaseHost
{
get { return baseHost; }
} /// <summary>
/// 获取BaseHost当前连接状态
/// </summary>
public CommunicationState BaseHostState
{
get
{
if (baseHost == null)
{
return CommunicationState.Closed;
}
else
{
return baseHost.State;
}
}
} /// <summary>
/// 初始化ServerHost
/// </summary>
/// <param name="yourBaseAddress"></param>
public ServerHost(Uri yourBaseAddress)
{
baseAddress = yourBaseAddress;
MessageTransferChannel.MessageCallback += new Action<object, string>((a, b) => AddInfo(b)); MessageTransferChannel.OnRunnerCommand += MessageTransferChannel_RunnerCommandCallback;
MessageTransferChannel.OnGetAllRemoteRunnerInfo += MessageTransferChannel_GetAllRemoteRunnerInfoCallback;
} /// <summary>
/// 处理ExecuteService的远程指令【runner状态获取】
/// </summary>
RemoteRunnerInfo MessageTransferChannel_GetAllRemoteRunnerInfoCallback()
{
List<CaseRunner> caseRunnerList = CaseRunnerList;
if (caseRunnerList == null)
{
return new RemoteRunnerInfo();
}
return GetRunnerInfo(caseRunnerList, true);
} /// <summary>
/// 处理ExecuteService的远程指令【runner控制】
/// </summary>
void MessageTransferChannel_RunnerCommandCallback(ExecuteService sender, RunnerCommand command, List<int> runners)
{
List<CaseRunner> caseRunnerList=CaseRunnerList;
if(caseRunnerList==null)
{
return;
}
RunnerCommandExecute(caseRunnerList, command, runners);
} /// <summary>
/// 触发更新CaseRunner状态双工回调(局部更新)
/// </summary>
/// <param name="caseRunnerList">CaseRunner 列表</param>
public void SendStateCallBack(List<CaseRunner> caseRunnerList)
{
SendStateInfo(caseRunnerList, false);
} /// <summary>
/// 触发更新CaseRunner状态双工回调(全部更新)
/// </summary>
/// <param name="caseRunnerList">CaseRunner 列表</param>
public void SendAllStateCallBack(List<CaseRunner> caseRunnerList)
{
SendStateInfo(caseRunnerList, true);
} /// <summary>
/// 像执行行体请求CaseRunner 最新列表
/// </summary>
private List<CaseRunner> CaseRunnerList
{
get
{
if (OnBackNowRunnerList() != null)
{
return OnBackNowRunnerList();
}
return null;
}
} /// <summary>
/// 输出提示信息
/// </summary>
/// <param name="message"></param>
private void AddInfo(string message)
{
if(OnServerHostMessage!=null)
{
this.OnServerHostMessage("baseHost", message);
}
} /// <summary>
/// 启动BaseHost
/// </summary>
public void OpenBaseHost()
{
if (baseHost == null)
{
baseHost = new ServiceHost(typeof(ExecuteService), baseAddress); ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15; baseHost.Description.Behaviors.Add(smb);
//baseHost.AddServiceEndpoint(typeof(IExecuteService), new BasicHttpBinding(), "");
//baseHost.AddServiceEndpoint(typeof(IExecuteService), new WSDualHttpBinding(), ""); System.ServiceModel.Channels.Binding binding = new WSDualHttpBinding();
((System.ServiceModel.WSDualHttpBinding)(binding)).Security.Mode = WSDualHttpSecurityMode.None;
((System.ServiceModel.WSDualHttpBinding)(binding)).Security.Message.ClientCredentialType = MessageCredentialType.UserName; System.ServiceModel.Channels.Binding tcpBinding = new NetTcpBinding();
((System.ServiceModel.NetTcpBinding)(tcpBinding)).Security.Mode = SecurityMode.None;
((System.ServiceModel.NetTcpBinding)(tcpBinding)).Security.Message.ClientCredentialType = MessageCredentialType.UserName; //测试开安全双工只能在本机使用
baseHost.AddServiceEndpoint(typeof(IExecuteService), binding, ""); baseHost.Opening += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Opening"));
baseHost.Opened += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Opened"));
baseHost.Closed += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Closed"));
baseHost.Closing += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Closing"));
baseHost.Faulted += new EventHandler((yourObject, yourEventAgrs) => AddInfo("Faulted")); Thread openBaseThread = new Thread(new ThreadStart(BaseHostOpen));
openBaseThread.IsBackground = true;
openBaseThread.Start(); }
else
{
if (baseHost.State == CommunicationState.Opened)
{
AddInfo("服务已经开启");
}
else if (baseHost.State == CommunicationState.Opening)
{
AddInfo("服务正在开启");
}
else
{
baseHost.Abort();
baseHost = null;
OpenBaseHost();
}
}
} private void BaseHostOpen()
{
try
{
baseHost.Open();
}
catch (Exception ex)
{
AddInfo(ex.Message);
}
} /// <summary>
/// 关闭BaseHost
/// </summary>
public void CloseBaseHost()
{
if (baseHost == null)
{
AddInfo("未发现服务");
}
else
{
if (baseHost.State != CommunicationState.Closed)
{
AddInfo(baseAddress.ToString() + "服务关闭");
baseHost.Close();
}
else
{
AddInfo(baseAddress.ToString() + "服务已经关闭");
}
}
} /// <summary>
/// 执行远程命令
/// </summary>
/// <param name="caseRunnerList">CaseRunner 列表</param>
/// <param name="command">命令类型</param>
/// <param name="Runners">受影响Runner</param>
private void RunnerCommandExecute(List<CaseRunner> caseRunnerList, RunnerCommand command, List<int> Runners)
{
switch (command)
{
case RunnerCommand.Start:
if (Runners != null)
{
if (Runners.Count > )
{
foreach (int tempRunnerIndex in Runners)
{
if (caseRunnerList.Count >= tempRunnerIndex)
{
caseRunnerList[tempRunnerIndex].RunQuiet();
}
else
{
AddInfo("tempRunnerIndex error");
}
}
}
}
else
{
AddInfo("Runners is null");
}
break;
case RunnerCommand.Stop:
if (Runners != null)
{
if (Runners.Count > )
{
foreach (int tempRunnerIndex in Runners)
{
if (caseRunnerList.Count >= tempRunnerIndex)
{
caseRunnerList[tempRunnerIndex].StopQuiet();
}
else
{
AddInfo("tempRunnerIndex error");
}
}
}
}
else
{
AddInfo("Runners is null");
}
break;
case RunnerCommand.Pause:
if (Runners != null)
{
if (Runners.Count > )
{
foreach (int tempRunnerIndex in Runners)
{
if (caseRunnerList.Count >= tempRunnerIndex)
{
caseRunnerList[tempRunnerIndex].PauseQuiet();
}
else
{
AddInfo("tempRunnerIndex error");
}
}
}
}
else
{
AddInfo("Runners is null");
}
break;
default:
break;
}
} /// <summary>
/// 触发更新CaseRunner状态双工回调
/// </summary>
/// <param name="caseRunnerList">CaseRunner 列表</param>
/// <param name="isAll">是否全部更新</param>
private void SendStateInfo(List<CaseRunner> caseRunnerList,bool isAll)
{
if (BaseHostState != CommunicationState.Opened)
{
return;
}
if (caseRunnerList.Count > )
{
RemoteRunnerInfo remoteRunnerInfo = GetRunnerInfo(caseRunnerList,isAll);
if (remoteRunnerInfo.RunnerStateList != null)
{
if (remoteRunnerInfo.RunnerStateList.Count > )
{
if (MessageTransferChannel.OnRunnerInfoCallback != null)
{
MessageTransferChannel.OnRunnerInfoCallback(remoteRunnerInfo);
}
}
}
}
} /// <summary>
/// 获取List<CaseRunner>中的更新信息
/// </summary>
/// <param name="runnerList">CaseRunner 列表</param>
/// <param name="isUpdataAll">T表示全部更新,F标识局部更新</param>
/// <returns>更新信息</returns>
private RemoteRunnerInfo GetRunnerInfo(List<CaseRunner> runnerList, bool isUpdataAll)
{
RemoteRunnerInfo remoteRunnerInfo = new RemoteRunnerInfo();
if (runnerList == null)
{
return null;
}
foreach (CaseRunner tempRunner in runnerList)
{
if (tempRunner.IsNeedUpdata || isUpdataAll)
{
tempRunner.IsNeedUpdata = false; RunnerState tempRunnerState = new RunnerState();
if (tempRunner.RunerActuator.NowExecutionResultList != null)
{
if (tempRunner.RunerActuator.NowExecutionResultList.Count > )
{
myExecutionDeviceResult tempResult = tempRunner.RunerActuator.NowExecutionResultList[tempRunner.RunerActuator.NowExecutionResultList.Count - ];
tempRunnerState.RunnerID = runnerList.IndexOf(tempRunner);
tempRunnerState.RunnerName = tempRunner.RunnerName;
tempRunnerState.NowCell = tempResult.caseId.ToString();
tempRunnerState.CellResult = tempResult.result.ToString();
tempRunnerState.State = tempRunner.RunnerState.ToString();
tempRunnerState.Time = tempResult.spanTime;
tempRunnerState.RunDetails = tempResult.backContent;
tempRunnerState.RunnerProgress = tempRunner.RunerActuator.RunProgress;
remoteRunnerInfo.AddRunnerState(tempRunnerState);
}
else if (isUpdataAll)
{
tempRunnerState.RunnerID = runnerList.IndexOf(tempRunner);
tempRunnerState.RunnerName = tempRunner.RunnerName;
tempRunnerState.NowCell = "";
tempRunnerState.CellResult = "";
tempRunnerState.State = tempRunner.RunnerState.ToString();
tempRunnerState.Time = "";
tempRunnerState.RunDetails = "";
tempRunnerState.RunnerProgress = tempRunner.RunerActuator.RunProgress;
remoteRunnerInfo.AddRunnerState(tempRunnerState);
}
else
{
tempRunnerState.RunnerID = runnerList.IndexOf(tempRunner);
tempRunnerState.RunnerName = tempRunner.RunnerName;
tempRunnerState.State = tempRunner.RunnerState.ToString();
tempRunnerState.RunnerProgress = tempRunner.RunerActuator.RunProgress;
remoteRunnerInfo.AddRunnerState(tempRunnerState);
}
}
}
}
return remoteRunnerInfo;
} /// <summary>
/// 获取List<CaseRunner>中的更新信息(局部更新)
/// </summary>
/// <param name="runnerList">CaseRunner 列表</param>
/// <returns>更新信息</returns>
private RemoteRunnerInfo GetRunnerInfo(List<CaseRunner> runnerList)
{
return GetRunnerInfo(runnerList, false);
} }

最后放几个动画,简单演示下Auto组件的部分功能

一个基于.NET平台的自动化/压力测试系统设计简述

一个基于.NET平台的自动化/压力测试系统设计简述

一个基于.NET平台的自动化/压力测试系统设计简述

系统代码打包下载  AutoTest_V5.3_CodeOnly.rar

上一篇:javascript if 与 if else 的一点小认识


下一篇:Mario是一个基于.NETCore的简单快速开发框架