51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1772|回复: 5
打印 上一主题 下一主题

基于excel的selenium自动化测试平台实现思路

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2017-5-10 15:20:26 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
本帖最后由 307641452 于 2017-5-16 22:00 编辑

从16年11月份接触了selenium以后,就一直想写一个基于excel的自动化测试平台(姑且叫做平台),但是一直有些问题无法解决也就迟迟没有动手,目前有个初步的想法,下面介绍给感兴趣的童鞋。这个平台有很多人做过,我在测试群里面分享了想法之后也是被打击的相当厉害,全是认为这鸟玩意没用的观点,但我的目的很简单,就是要把我想做的东西做出来,也许我不会去用,也许做的确实一文不值,这都不是问题,我觉得我的想法并不是只停留在想法这个阶段,自己动手去实现了,就够了。   这个平台要解决的问题,其实很简单,就是可以用前台去随意的控制你要执行哪些用例,目前应用最多的就是持续集成工具+junit来执行用例,但我个人是非常不喜欢xml这玩意的,自己想运行哪些,放到一个组里面直接跑,跑完了在前台直接看报告,方便。
一、用例载体
既然是基于excel的用例,那也就是说你只要在excel中利用提供的关键字把业务逻辑写出来,然后把文件传到服务器就行了,用例的组织采用了页面元素统一管理的方式,也就是页面对象的设计方法,如下图。你只要在第一个sheet页中把定位方式和表达式写上,以后就可以在任意的业务逻辑中使用你起的名字来操作这个元素了,免去了重复录入表达式的麻烦,而且也便于对元素统一修改。
元素表示例:

第二个sheet页就是业务逻辑了,用过关键字驱动的同学应该都比较了解,这里最核心的东西就是关键字,托java反射机制的福,关键字在后台映射非常容易,扩展也非常方便;我目前提供了一些常用的关键字,后续还需要大量扩充;由于关键字比较多,所以我把它们分了几个主类,便于查找,而且excel的主类、关键字列都是select形式的,不需要记住怎么拼写,脸熟就行,不喜欢英文的还可以把关键字和主类都用中文替换。不过业务逻辑的编辑还是需要自动化基础知识的,啥也不了解就直接编写,很难保证用例跑的通。
脚本示例:

关键字select模式:

我目前提供的关键字:

二、介绍完用例了,那么接下来就需要把用例上传到服务器了,上传很简单,首先你需要在左侧的功能树中建立需要的模块之类的,便于后面查找,推荐使用“项目-模块-功能”这种路径去管理用例,这个自己随意了,看下面图(前端界面比较难看):

定位到用例所在的功能路径后,点击右侧中的新增就可以了,其中所属模块自动获取左侧选中的结点,名字和文件必须有,见下图

上传的用例一般情况下作者可能需要马上验证这个用例是否能够正常运行,所以这个页面提供了单个或者多个用例运行的功能,选中你需要检查的用例,然后点击run按钮,会弹出一个选择hub的select,这个hub也就是你想在哪台及其运行你的脚本,当然大多数会选自己的机器啦~。。  选择好了就可以运行用例了,这个不提供停止运行的功能,真想停的话就直接关闭自动化脚本打开的浏览器就行了。
其他修改删除功能就略过了。还有一点,用例直接执行的话,并发只能是1个。

三、用例有了,也可以选择用例运行了,是不是少了点什么? 我每次都要去选择用例运行吗?如果每次需要运行多个固定的用例,那多麻烦呀!  没错,分组功能就是为这个设计的 ,见下图:

      你可以新建一个分组,这里就需要设置并发数量了,这根据你所能控制的node结点机器数量和用例数量自己考量,建议一个远程机器不要超过3个并发。建好了之后可以通过关联用例按钮进入关联页面,这个页面会把你还没有关联的用例都查出来,选择游泳的关联之!点击取消关联就进入取消关联页面,这里会把你已经关联的用例全部查出来,取消之即可!然后就是运行功能了,和用例运行差不多,会让你选择再哪些远程机运行用例,不用的是用例组运行是会并发执行的,机器越多并发的越多。

四、刚才运行功能的介绍中提到了选择服务器的步骤,这里就要介绍一下hub管理了,非常简单的功能,ip  ,端口,浏览器类型是必须提供的,hub会通过selenium grid管理注册了的远程电脑。

五、用例或者用例组运行了之后会生成报告,报告的输出目录可以配置到某个管理报告的tomcat下面;平台提供了结果查看菜单,在这里可以查看到正在排队、正在运行、运行完成的任务。前两者不能查看报告,完成了的可以直接查看报告:

这个页面提供了删除功能和停止当前任务的功能,删除功能可以删除队列任务和已经完成的任务,队列任务会直接物理删除,已经完成的会逻辑删除。正在运行的任务也可以删除,但是也是逻辑删除,就是说你不先停掉就直接删除的话,他还是会在后台跑到全部结束为止,浪费资源。点击查看报告链接会新打开一个窗口,展示testng的报告,和junit基本上差不多,不过我这个是用了reportng美化了一下,但依然比较丑!

六、后台任务是根据队列机制运行的,也就是说你提交了多个用例组,他只能一个组一个组的运行,每个组都可以并发。然后组的运行优先级高于用例,也就是说任务队列中没有待运行的组才会去运行用例。

平台就算介绍完了,第一次自己想写点东西,很不成熟,推翻重做了好几次,小伙伴们有啥好的建议都提出来哈,集思广益,我会不断改进。

1.我又想起来一个功能  通过前台输入java类文件的名称,后台来调用之前用java开发的脚本进行测试,简单的业务就用excel 写, 遇到复杂的  ,就用java代码写脚本,兼容两种模式。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

x

评分

参与人数 1测试积点 +10 收起 理由
lsekfe + 10 赞一个!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1
回复

使用道具 举报

该用户从未签到

5#
 楼主| 发表于 2017-5-17 09:55:05 | 只看该作者
项目中使用了一些工具包,包括:
poi:读取excel内容;
testng:执行测试用例;
reportng:美化testng报告的插件;
log4j:日志输出;
selenium:这个就不用说了;
apach-fileupload:文件上传功能;
前段用的jquery easyui;
回复 支持 反对

使用道具 举报

该用户从未签到

4#
 楼主| 发表于 2017-5-15 09:17:55 | 只看该作者
本帖最后由 307641452 于 2017-5-16 22:20 编辑

功能已经都实现了,今晚把视线的方式发上来,大家有兴趣的可以讨论下。前台的功能就不用细说了,无非是把组的id或者用例id传到后台,保存到数据库,如果是组id,那么就在任务表里面生成一条新记录,如果是用例,就把用例全都写到用例的临时表里面。
1.后台会有一个随着tomcat一起启动的监听器,只要实现ServletContextListener这个接口,然后在web.xml中配置一下listener即可;这个网上有很多资料,一查就会了。
这个监听器的作用很简单,就是启动后台扫描数据库任务表、临时用例表的一个进程(只写出关键部分):
  1.      public void contextDestroyed(ServletContextEvent arg0) {
  2.                
  3.                   if (taskThread != null && taskThread.isInterrupted()) {  
  4.                           taskThread.interrupt();  
  5.                  }  
复制代码


2.后台扫描进程启动之后,会优先扫描任务表来执行组的任务,这里有个小功能,就是有些任务还没执行就被用户删掉了,所以说扫描到了任务之后会先判断是否被删除了,如果是就物理删除,如果不是就执行这个组里面的所有关联用例;
    如果没扫描到组任务,就会去扫描临时用例表,扫描到了就执行。
  如果都没有扫描到,那么睡眠3s再次扫描,关键代码如下:
  1. while (!this.isInterrupted())  {// 线程未中断执行轮询  
  2.                           
  3.                          //获取执行列表中最早的一个
  4.                  List<AtpTask> temptasklist=new ArrayList<AtpTask>();
  5.                  List<CaseTemp> tempcaselist =new ArrayList<CaseTemp>();
  6.                  temptasklist=atdao.findTaskBystatus(CommonUtils.newtask);
  7.                  
  8.                  if(temptasklist.size()==0){//没有要运行的组,则扫描是否有要运行的用例:
  9.                          
  10.                          tempcaselist= tcdao.findByStatus(CommonUtils.newtempcase);       
  11.                          if(tempcaselist.size()>0){
  12.                                          //将所有新的临时用例设置成运行状态。
  13.                                         for(int i=0;i<tempcaselist.size();i++){
  14.                                                 tcdao.setStatusById(tempcaselist.get(i).getIdtempcase(), CommonUtils.runningcase);
  15.                         
  16.                                         }
  17.                                         //运行用例
  18.                                         test.RunCase();       
  19.                                         //运行完成后删掉所有跑完的用例
  20.                                         tcdao.deleteByStatus(CommonUtils.runningcase);
  21.                                  
  22.                          }else{
  23.                                 // System.out.println("轮询任务线程未扫描到任何需要运行的套件和用例,5s后再次扫描.......");
  24.                          }

  25.                  }else if(temptasklist.size()==1){
  26.                                 
  27.                                  AtpTask atptask=new AtpTask();
  28.                                 atptask=temptasklist.get(0);
  29.                                 System.out.println("获得的对象为:"+atptask.toString());
  30.                                 if(atptask.getDeleteflag()==1){//如果删除状态为1,就删掉他。
  31.                                         System.out.println("进入删除task分支");
  32.                                         //删除这条task—
  33.                                         atdao.delete("idtask", atptask.getIdtask());
  34.                                                
  35.                                 }else{//运行这个task
  36.                                                
  37.                                                  //根据获取的任务,拿到idsuit,然后查询到对应suit
  38.                                                  int idsuit=atptask.getIdsuit();
  39.                                                  Suit suit=new Suit();
  40.                                                  suit=sdao.findById("idsuit", idsuit);
  41.                                                
  42.                                                  //根据该suit,获取到testng需要并发的数量
  43.                                                  int threadcount=1;//获取可执行状态后,将其置为false并跳出循环执行测试。
  44.                                                
  45.                                                        //设置这个task的状态为1执行
  46.                                                  atdao.setStatusById(atptask.getIdtask(), CommonUtils.runningtask);       
  47.                                                        threadcount=suit.getThreadcount()==0?1:suit.getThreadcount();
  48.                                                        //设置完状态后开始执行这个任务,
  49.                                                         System.out.println("获得的线程并发数为:"+threadcount);
  50.                                                         test.RunSuit(threadcount,atptask.getPath());//并发数为参数。                       
  51.                                                                        
  52.                                                         //3.单次执行完成后把对应task置为完成状态。
  53.                                                         atdao.setStatusById(atptask.getIdtask(), CommonUtils.finishedtask);       
  54.                                         }
  55.                          }
  56.                  //所有任务都没有获取到,则等待2s再次扫描
  57.                          try{
  58.                                  Thread.sleep(5000); //每隔3000ms执行一次  
  59.                    } catch (InterruptedException e) {  
  60.                        e.printStackTrace();  
  61.                            }  
  62.                         }

  63.                
复制代码
3.运行testng就比较简单了,为了让朋友们看的明白些,我加了几行注释,大家看看代码就懂了:
  1. //执行组
  2.                 public void RunSuit(int threadcount,String midpath){         
  3.                                 PropUtil PropUtil = new PropUtil("FrameWork.properties");
  4.                                 String reportdir=PropUtil.get("ReportDir");
  5.                                 //设置运行的输出目录,每次运行目录均单独保存。
  6.                                 SingleTestng.getTestNG().setOutputDirectory(reportdir+"\\"+midpath);
  7.                                 //设置并发数量,参数由前段传入
  8.                                 SingleTestng.getTestNG().setDataProviderThreadCount(threadcount);
  9.                                 //设置要运行的类
  10.                                   SingleTestng.getTestNG().setTestClasses(new Class[]{SuitTestControler.class});
  11.                                   SingleTestng.getTestNG().run();
  12.                                           
  13.                                         }
  14.                 //执行多个用例         
  15.                 public void RunCase(){       
  16.                        
  17.                                 PropUtil PropUtil = new PropUtil("FrameWork.properties");
  18.                                 String defaultdir=PropUtil.get("ReportDir");
  19.                                 SingleTestng.getTestNG().setOutputDirectory(defaultdir+"//"+"defaut-out");
  20.                                                  
  21.                                   SingleTestng.getTestNG().setTestClasses(new Class[]{CaseTestControler.class});
  22.                                   SingleTestng.getTestNG().run();
  23.                                  
  24.                           
  25.                                   }
复制代码
4.至于测试的类,了解selenium的人应该都能想到,就是把excel文件的关键字和参数读取到,然后利用反射机制去执行封装好的方法,代码较多就不全贴了:

  1. try {
  2.                  this.InitElementMap(filename);
  3.                  int actionnum=eu.getLastRowNum(eu.ActionSheet);
  4.                  for(int i=2;i<=actionnum;i++)
  5.                   {
  6.                        
  7.                          if(eu.getCellData(eu.ActionSheet, i, 0).equals("eleAction"))
  8.                                 {
  9.                                  System.out.println("进入行"+i+":eleAction");
  10.                                           this.element=this.findelement(this.getLocator(eu.getCellData(eu.ActionSheet, i, 1)));
  11.                                          
  12.                                          method=EleAction.class.getMethod(eu.getCellData(eu.ActionSheet, i, 2), WebElement.class,String.class);
  13.                                          method.invoke(eleaction,this.element,eu.getCellData(eu.ActionSheet, i, 3));               
  14.                                 }
  15.                                 else if(eu.getCellData(eu.ActionSheet, i, 0).equals("winAction"))
  16.                                 {
  17.                                         System.out.println("进入行"+i+":winAction");
  18.                                         method=WinAction.class.getMethod(eu.getCellData(eu.ActionSheet, i, 2), String.class);
  19.                                         System.out.println(method.getName());
  20.                                         method.invoke(winaction,eu.getCellData(eu.ActionSheet, i, 3));
  21.                                 }
  22.                                 else if(eu.getCellData(eu.ActionSheet, i, 0).equals("eleWait"))
  23.                                 {
  24.                                         System.out.println("进入行"+i+":eleWait");
  25.                                          this.element=this.findelement(this.getLocator(eu.getCellData(eu.ActionSheet, i, 1)));
  26.                                          
  27.                                          method=EleWait.class.getMethod(eu.getCellData(eu.ActionSheet, i, 2), WebElement.class,String.class);
  28.                                          method.invoke(elewait,this.element,eu.getCellData(eu.ActionSheet, i, 3));               
  29.                                 }
  30.                                 else if(eu.getCellData(eu.ActionSheet, i, 0).equals("winWait"))
  31.                                 {
  32.                                         System.out.println("进入行"+i+":winWait");
  33.                                         method=WinWait.class.getMethod(eu.getCellData(eu.ActionSheet, i, 2), String.class);
  34.                                          method.invoke(winwait,eu.getCellData(eu.ActionSheet, i, 3));
  35.                                 }
  36.                                 else if(eu.getCellData(eu.ActionSheet, i, 0).equals("eleAssert"))
  37.                                 {
  38.                                         System.out.println("进入行"+i+":eleAssert");
  39.                                          this.element=this.findelement(this.getLocator(eu.getCellData(eu.ActionSheet, i, 1)));
  40.                                          
  41.                                          method=EleAssert.class.getMethod(eu.getCellData(eu.ActionSheet, i, 2),WebElement.class, String.class);
  42.                                          method.invoke(eleassert,this.element,eu.getCellData(eu.ActionSheet, i, 3));               
  43.                                 }
  44.                                 else if(eu.getCellData(eu.ActionSheet, i, 0).equals("winAssert"))
  45.                                 {
  46.                                         System.out.println("进入行"+i+":winAssert");
  47.                                         method=WinAssert.class.getMethod(eu.getCellData(eu.ActionSheet, i, 2), String.class);
  48.                                          method.invoke(winassert,eu.getCellData(eu.ActionSheet, i, 3));
  49.                                 }else {
  50.                                         System.out.println("第"+i+"行的数据有误,请检查主类名称是否正确!!!!!!!!!");
  51.                                         Assert.assertTrue(false, "运行失败了:"+"第"+i+"行的数据有误,请检查主类名称是否正确!!!!!!!!!");
  52.                                         logger.debug("第"+i+"行的数据有误,请检查主类名称是否正确!!!!!!!!!");
  53.                                 }
  54.                           }
  55.                  } catch (Exception e) {
  56.                                  ispass=false;
  57.                                   Assert.assertTrue(false, "运行失败了:"+e.toString());
  58.                                   e.printStackTrace();
  59.                   }finally{
  60.                           String ends = DateTimeUtil.formatedTime("yyyy-MM-dd HH:mm:ss:SSS");
  61.                           if (this.ispass) {
  62.                                         logger.info("案例 【" + casename + "】 运行通过!");
  63.                                 } else {
  64.                                         String dateTime = DateTimeUtil.formatedTime("yyyy-MMdd-HHmmssSSS");
  65.                                         StringBuffer sb = new StringBuffer();
  66.                                         String captureName = sb.append(capturePath)
  67.                                                         .append(casename).append(dateTime).append(".png")
  68.                                                         .toString();
  69.                                
  70.                                         captureScreenshot(captureName);
  71.                                         logger.error("案例 【"  + casename
  72.                                                         + "】 运行失败,请查看截图快照:" + captureName);
  73.                                          
  74.                                 }
  75.                                 logger.info("======" + ends + ":案例【"  + casename
  76.                                                 + "】结束======");
  77.                                 afterTestStops = System.currentTimeMillis();
  78.                                 logger.info("======本次案例运行消耗时间 " + (double)(afterTestStops - beforeTestStarts)
  79.                                                 / 1000 + " 秒!======");
  80.                                 this.driver.quit();
  81.                         }
  82.                
  83.                
复制代码
上面的代码是运行每个用例都用到的反射代码,大致思路就是这样了,因为我是半路出家,去年才自学的java,代码写的很是没有艺术,朋友们见谅哈~
最近准备着手写一个跟这个平台类似的http接口测试平台,当然接口就简单的多了,只是没学过界面,写前端比较痛苦,相信一个月内可以搞定。
等到接口平台也写完了,就去看看jmeter的源码,如果看的懂,就改造改造也搞成bs模式的,以后测试人员只要录了脚本上传上去,开发就可以随便运行查看结果,省的我们测试人员老是被他们叫去跑脚本。

回复 支持 反对

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-27 11:03 , Processed in 0.073295 second(s), 27 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表