RobotFramework(后称 RF)是很多测试人员的挚爱,包括我,虽然用过许多测试框架,甚至自己也实践开发过,但 RF 扩展性是我最喜欢的,用过 RF 的朋友应该知道,RF 是通过导入本地的测试库来支撑整个测试过程的运行,所以这样就会带来一些问题,比如:
面对上面的一些问题,我在想是否可以将测试库这一块资源给服务化,用云的形式来承载,答案当然是可以的,那接下来就是设计实现
1、技术预研:
上面的方式有个大毛病,比如同时实例化了 Selenium2Library 和 AppiumLibrary,用上面的远程调用方式是通过一个新库去同时继承父类库来实现远程调用,这样一来会带来问题:
太不人性化了,脑洞放开一点,既然可以作用在目标机器,也就是说其实可以将测试库独立分离开来管理,形成自己的一套服务,也就是说可以这么玩:
有了上面一套预研结论之后,就可以开始设计实现的方案
2、设计方案:
方案 | 描述 |
---|---|
架构方案 | 采用 C/S 模式,客户端参照 Remote.py 实现,服务端参照 robotremoteserver 实现 |
调用方案 | 通过客户端带特定的参数请求服务端来调用指定的测试库 |
云测试库管理方案 | 通过读取配置文件,用标签和测试库一一对应,并生成可执行实例化的云服务器程序 |
[
{"api":"appium","lib":"AppiumLibrary"},
{"api":"selenium","lib":"Selenium2Library"},
{"api":"request","lib":"RequestsLibrary"},
{"api":"macaca","lib":"MacacaLibrary"},
{"api":"easy","lib":"EasyLibrary"}
]
#coding=utf-8
import sys
import xmlrpclib
from AppiumLibrary import AppiumLibrary
from Selenium2Library import Selenium2Library
from RequestsLibrary import RequestsLibrary
from MacacaLibrary import MacacaLibrary
from EasyLibrary import EasyLibrary
class CloudLibrary():
def __init__(self):
self.appium=AppiumLibrary()
self.selenium=Selenium2Library()
self.request=RequestsLibrary()
self.macaca=MacacaLibrary()
self.easy=EasyLibrary()
def get_library(self,library):
if library=="appium":
return self.appium
elif library=="selenium":
return self.selenium
elif library=="request":
return self.request
elif library=="macaca":
return self.macaca
elif library=="easy":
return self.easy
else:
return self
if __name__ == '__main__':
from robotcloudserver import RobotCloudServer
RobotCloudServer(CloudLibrary(),"192.168.1.1",8270,None)
服务器会自动获取本地 ip 并启动服务
2、客户端的实现:
客户端是参照了 Remote.py 的方式,增加了参数化解析,具体的内容大家可以下载去查看,这里主要讲部署使用
如上图分别有 Cloud_robot.py 和 Cloud_ride.py,其实是分别放在 robot 和 robotide 的目录下:
Cloud_robot:/Library/Python/2.7/site-packages/robot/libraries
Cloud_ride:/Library/Python/2.7/site-packages/robotide/lib/robot/libraries
在 win 系统上也是一样的,就是找到 site-packages 文件夹就好,放好之后将两个文件都重命名为 Cloud.py,并且修改 libraries 下的init.py 模块导入的内容:
都搞定之后就是在 ride.py 上的调用,导入测试库的时候可以这样子:
导入成功之后,查看一下关键字
然后在尝试一下执行,对于 selenium2library 的调用执行,要使其做用在本机或其他远程目标机器的话,比如用 nodejs 版的 selenium-standalone 启动了一个服务,用上 remote_url 参数就好,Appium,Macaca 也是一样道理,只要对得上 ip 地址就 ok,结合之前实现过的 docker-selenium-grid 的浏览器云并发方案,以及 f2etest 的浏览器云那就更方便了
*** Settings ***
Library Cloud 192.168.43.228:8270/selenium WITH NAME Selenium2Library
*** Test Cases ***
testweb
Open Browser http://www.baidu.com chrome \ remote_url=http://192.168.43.4:4444/wd/hub
sleep 0.5
和在本地使用差异不大,参数就是我们在配置文件里面定义的 api,比如 selenium 对应就是导入 SeleniumLibrary,request 导入 RequestLibrary
查看导入的库,都是 ok 的,但这样就不用自己去维护测试库了,测试库只要维护一份,就可以整个团队共用,同时可以根据不同的参数导入自己需要使用的测试库,这方便了对测试库的管理和应用,这几步下来就已经解决问题 1 和 2
3、中心代理服务器的实现:
正如问题 3 和 4,更多的会偏向 4,比如测试库 A 在机器 A 上,测试库 B 在机器 B 上,但这个两个库都需要调用,按照上面的方法启动两个服务器的话,需要连接到两个不同的云来切换库,是否可以有一种方法可以让所有库的连接 ip 地址都是唯一的,于是就想到了这种架构
import xmlrpclib
import socket
from SimpleXMLRPCServer import SimpleXMLRPCServer
cloud_dict={}
def get_ip():
iplist=[]
iplist = socket.gethostbyname_ex(socket.gethostname())
ip=iplist[2][0]
return ip
def set_register(cloud_name,cloud_addr,cloud_port):
clo_address="{cloud_addr}:{cloud_port}".format(cloud_addr=cloud_addr,cloud_port=cloud_port)
try:
print cloud_dict[cloud_name]
tmpaddr=cloud_dict[cloud_name]
print "the cloudname:{cloud_name},ip:{addr} is exist".format(cloud_name=cloud_name,addr=tmpaddr)
return False
except:
cloud_dict[cloud_name]=clo_address
print cloud_dict[cloud_name]
print "the cloudname:{cloud_name} register ok,ip:{addr} ".format(cloud_name=cloud_name,addr=clo_address)
return True
def get_cloud_address(cloud_name,taga=None):
try:
print "aaa "+cloud_dict[cloud_name]
ipaddr=cloud_dict[cloud_name]
print "It point to {cloud_name}:{ipaddr}\nImport the lib:{taga} ".format(cloud_name=cloud_name,ipaddr=ipaddr,taga=taga)
return "http://"+ipaddr
except:
ipaddr="http://"+str(get_ip())
print "The cloud is not exist,It point to local center cloud:{ipaddr}".format(ipaddr=get_ip())
return "error"
ipaddress=get_ip()
server = SimpleXMLRPCServer((ipaddress, 8280))
print "The Cloud Proxy Server Listening on {ip}:{port} ...".format(ip=ipaddress,port=8280)
server.register_function(get_cloud_address,"get_cloud_address")
server.register_function(set_register,"set_register")
server.serve_forever()
这样就代表注册成功了,一般云名字都是唯一的,如果有其他测试库云再用如 cloud1 来注册的话,这样是代理服务器会返回云名字已存在,那换个名字注册就好了
其实就是在本身的 api 参数后面通过@cloud_name来指向对应云名字的服务器地址,获取其 ip 之后创建对应的连接客户端,这样就能够指向到名字叫 cloud1 的测试库云并使用对应的资源,对应其他测试库也是一样的,这样就能保证连接的 ip 是唯一的,通过不同的云名字来指向到不同的测试库云,实现了测试库云的集群,这才是一个比较基础的云服务,这样一来问题 3 和问题 4 都解决了
4、难点攻克::
def _get_keyword_by_tag(self,name,tag):
tmpliby=self._library.get_library(tag)
if type(self._runliby)==type(tmpliby):
pass
else:
self._runliby=tmpliby
if name == 'stop_remote_server':
return self.stop_remote_server
kw = getattr(self._runliby, name, None)
if not self._is_function_or_method(kw):
return None
return kw
就是这样通过客户端调用上面的方法,传入不同的 api 参数来获取测试库的唯一实例,保证了库的正常运作和稳定性,当然这里看是只有一个实例,其实多人调用也是可以的,比如像 Selenium 这种,其他调用的更多是 webdriver 的实例,这个是每次 open broswer 的时候都会创建的,用库的实例找到 webdriver 的实例来运行, 其他库也是一样的道理,我在之前的并发自动化的时候也验证过这个过程,所以说这种云的方式是能实现多人共用同一测试库的方案,而且这样以后只需要维护一套测试库即可
5、局限性分析::
基于上面的实现方式是有局限的