在我们最初学习和使用selenium进行自动化的时候,肯定都是在本地IDE中进行脚本编写和执行脚本。最常用的执行方式就是使用单元测试框架,如java的testng,ruby的rspec,python的unittest。当我们在IDE中执行的时候其实是多个用例串行执行的,如果进行单元测试,其实这样做是无所谓的。毕竟代码级别的执行效率是很快的。再多的用例很快也能执行完成。但是如果是执行UI自动化脚本,当用例数几百上千后会非常慢,而且在一台机器上执行很有可能因为硬件和网络问题而运行失败。所以我们要进行分布式执行,最常用的方式就是通过jenkins或者selenium grid来执行。但这样做仍然觉得使用起来不够直观清晰。我们也可以通过消息队列来实现分布式的脚本执行。我选用的redis,其他的MQ工具也是可以的。
redis实质上是一个内存数据库,做数据存储的。但是在自动化测试中,我们可以用它的订阅发布来实现消息的传递。当我们要执行一轮测试的时候,就向一个频道发布消息。而脚本执行客户端订阅这个频道,并接受到我的发布信息。当客户端接收到信息后,就根据这个信息去redis服务器读脚本的执行队列。在队列中取到每个客户端需要执行的脚本名称,然后就开始执行这个脚本。执行完后再去队列中取下一个脚本名称,依次循环直到队列为空。基本的设计原理就是这样。
先封装redis的方法,我使用的是java jfinal框架,Redis.use().getJedis()是该框架获取jedis对象的方法:
public class RedisKit {
public void pubMesage(String channel,String message) {
Jedis jds = null;
try{
jds=Redis.use().getJedis(); //获取jedis对象
jds.publish(channel, message); //publish 发布消息
System.out.println("测试任务队列id:"+message+" 发布成功!");
}catch (Exception e) {
System.out.println("测试任务消息发布失败:"+e);
}finally {
jds.close();
}
}
/**
* 添加队列元素
* @param queName 队列名称
* @param el 加入的元素
* @return
*/
public boolean addQueueEle(String queName,String el) {
Jedis jds = null;
boolean isCreate = true;
try{
jds=Redis.use().getJedis();
jds.rpush(queName, el); //rpush将元素插入队列
System.out.println("脚本:"+el+" 插入队列 "+queName+" 成功!");
}catch (Exception e) {
isCreate = false;
System.out.println("脚本名加入测试任务队列 "+queName+" 失败!");
}finally {
jds.close();
}
return isCreate;
}
}
在service类中调用上面的方法:
public String addTaskQueue(String pid, String cli, String rid) {
List<Runinfo> tasks = null;
boolean isCreated = false;
RedisKit rkit = new RedisKit();
HashMap<Object, Object> result = getCaseWaitRun(pid, rid);
if (result.get("status") == Status.SUCCESS) {
tasks = (List<Runinfo>) result.get("body");
}
isCreated=createQueue(rid, tasks); //round id 作为队列名称
// 创建队列成功,就发布消息
if (isCreated == true) {
rkit.pubMesage(cli, rid);
}
return Status.booleanResultCode(isCreated)
}
/**
* 创建脚本执行队列
* @param queueName 队列名称
* @param els 要加入的元素,脚本名称
* @return
*/
public boolean createQueue(String queueName, List<Runinfo> els) {
boolean stQueue = false;
RedisKit rkit = new RedisKit();
for (int i = 0; i < els.size(); i++) {
String queueElement = els.get(i).get("caseName");
stQueue = rkit.addQueueEle(queueName, queueElement);
}
return stQueue;
}
在controller中接收前端参数,并返回执行结果:
/**
* 接收到前端传递的round名,启动该round的测试,发布测试消息到客户端,
* 并将round中的case名加入测试任务队列
*/
@ActionKey("/startRun")
public void startRun() {
String pid=getPara("pid"); //项目id
String rid=getPara("rid"); //本轮测试id
String client=getPara("cli"); //执行客户端地址
//如果客户端地址为空,则表示所有客户端都可以执行,所以赋值为all
if(client.equals("null")){
client="all";
}
String[] msg = { "启动该轮测试成功!", "启动该轮测试失败!" };
String st=run.addTaskQueue(pid, client,rid );
responseResult(st, msg);
}
通过pid和rid在数据库中查询得到本轮测试要加入队列的脚本名称,而队列的名称我们用rid来命名,消息的频道通过cli来命名,并且该频道发布的信息就是rid。在客户端会订阅两个频道,客户端ip和all,如果它接收到这两个频道的信息,就会取得队列的名称rid,客户端监听方法如下,使用的python:
def get_pub_msg(self):
r = redis.StrictRedis(host = "10.7.3.17", port = 6379) #redis服务器地址
p = r.pubsub()
cli=self.get_cli_ip() #获取本机ip
p.subscribe(cli,"all") # 订阅本机ip和all两个频道
print("Start listening channal:"+cli+",all")
for item in p.listen(): #一直监听这两个频道,直到获取到消息
if item['type']=='message':
return item
取到了队列名称就可以向队列取脚本名称了:
def get_task_case_name(self,queue_name,timeout=10):
r = redis.StrictRedis(host = "10.7.3.17", port = 6379)
t=r.blpop(queue_name, timeout)
return t
代码只是展示一个思路,直接复制是跑不通的,因为里面很多方法是我自己封装的。大家可以根据这个思路用自己熟悉的语言去做。