zoukankan      html  css  js  c++  java
  • 【python开发】迈出第一步,这可能是我唯一一次的Python开发了

    好久没写博了,今天就瞎唠唠吧

    背景:

    组内有个测试平台,是基于Python2+tornado 框架写的,之前自己维护了一套系统的UIweb自动化代码,现在需要集成进去。这很可能是自己唯一一次基于python开发了,后面组内要换java。

    难点:

    其实开发内容很简单,但是由于之前只写接口、UI这样的自动化,开发没实操过,难点来源于没实操过,
    比如:ajax交互、js、轮询等等。反正不懂的就搜,不能搜怎么写代码 /认真脸
    https://cn.bing.com/
    http://google.com/
    翻不了墙的就用bing吧,也不错。

    设计尝试

    由于一直没有解决掉如果在vue实现的前端页面下,通过属性赋值这样的操作去上传图片(随着学习vue,希望可以找到解决方法)。
    故我一直都是采用的autoit这个window程序去实现的上传图片操作,其实也蛮稳定的。

    只不过受制于这个程序,我不能将我的UI自动化脚本在Linux上执行,也不能实现无ui的执行。所以呢,综上所述,我只能把我的脚本放在一台执行脚本专用的window机器上执行。
    这个前提条件确定下来了,我就得想怎么实现了。

    为了更好让集成到测试平台里,让我的脚本更具灵活性,我想到了使用selenium-Grid的方式,由linux去执行脚本,然后调用远程的window机器去执行脚本,看起来不错哦。好的,学习grid,
    调试grid,一切顺利。直到最后一步需要见证奇迹的时刻,果然。。。

    失败了,为什么呢?因为当执行到调用autoit的.exe程序时候,是从脚本执行的机器上去找的。wtf, 算了 ,工期将至,先实现一版用起来吧(流下了没有技术的泪水o(╥﹏╥)o)。

    设计实现方案确定

    这时候我发现了python-jenkins这个第三方库,提供了丰富的API操作Jenkins的方法。
    所以我的方案hi:在测试机器上本地启动一个jenkins服务,然后通过python-jenkins去调用jenkins提供的API去运行测试脚本,再jenkin集成allure生成一个漂亮的报告,行得通哦。

    需求功能确定

    需求确定:

    1. 可以在 case模块跟mark标记里去选择要执行的测试用例
      case模块就不用说了,就是这个

      mark标记,就是pytest框架里的mark标记功能,可以给case上打上你定义的mark,然后执行时候可以指定mark去执行被标记的case,场景比如:你标记出冒烟需要的case,打上smoke

      然后执行测试:pytest -m smoke 就可以啦

    2. 可以查看测试机器的状态
      因为就一台机器,所以一个人在执行测试的时候,其他人再点击执行就不行啦,只要机器不是空闲的,就把执行测试的按钮置灰。

    3. 然后列表页展示相关信息,可以查看测试报告
      这个就是通过API去获取任务执行情况了,然后把allure报告地址也给返回到前端

    功能实现

    终于到撸代码的时候了,因为这个平台是用Python的开发框架 tornado+bootstrap实现的,现学现卖吧,咱也不敢问。

    1. 先写个html页面出来
        <div class="page-content">
            <h3>测试平台</h3>
            <br>
            <form method="post" action="/exec_test">
                <div>
                    <p>选择模块进行测试:
                        <select id="sel_case_module" name="case_module" style="border-left- 1px;margin-left: 5px; 360px;">
                            <option value="" selected="selected">请选择要执行测试的case模块</option>
                            <option value="all_case">全部case</option>
                        </select>
                    </p>
                </div>
                <div>
                    <p>选择mark进行测试:
                        <select id="sel_case_mark" name="case_mark" style=" 360px;">
                            <option value="" selected="selected">请选择要执行测试的mark</option>
                        </select>
                    </p>
                </div>
            </form>
            <button type="button" id="exec_test" class="btn btn-sm btn-light" onclick="execute_test()" disabled="true">
                <a href="javascript:void(0)">执行测试</a>
            </button>
            <div>
                <p style="padding-top: 10px;">测试机器:</p>
                <div id="running_build_status" name="running_build_status" style=""></div>
            </div>
            <div class="popover-title" >
              <strong>
                <span>测试结果:<span id="test_result"></span></span>
              </strong>
            </div>
            <table class="table table-bordered table-hover table-condensed table-striped" style="font-size: 12px;min- 1200px;margin-bottom: 0">
              <tr>
                <th style="word-break: keep-all;min- 60px">当前构建任务名称</th>
                <th style="word-break: keep-all;min- 60px">当前构建任务号</th>
                <th style="word-break: keep-all;min- 130px;">当前构建任务状态</th>
                <th style="word-break: keep-all">当前构建任务时长(s)</th>
                <th style="word-break: keep-all">当前构建开始时间</th>
                <th style="word-break: keep-all;min- 130px;">测试报告地址</th>
              </tr>
                <td id="job_name"></td>
                <td id="job_number"></td>
                <td id="job_status"></td>
                <td id="job_duration"></td>
                <td id="start_time"></td>
                <td id="test_report">
                    <a href="" target="_blank"></a>
                </td>
              <tbody id="test_info">
              </tbody>
            </table>
        </div>
    </div>
    

    页面长啥样呢,咳咳,美观咱就算了,就是个能用就行

    1. 那怎么样让后端返回我写的这个html页面呢?
      很简单,在服务端路由分发里加上对应的url
    # 路由分发
    urls = [
        (r'/ui_ad_mis', AdMisHandler)
    ]
    

    然后去对应的AdMisHandler去写对应的服务就好了

    class AdMisHandler(BaseHandler):
        def get(self):
            '''
            返回UI测试页面
            '''
            self.render("html/ui_ad_mis.html")
    
    1. 那前端页面的下拉菜单列表怎么获取我在jenkin配置的参数化构建里配置的参数呢?
      很简单,后端通过API获取到配置的参数,然后返回给前端就好

    先看后端

    class QueryCase(BaseHandler):
        def get(self):
            '''
            查询case模块
            :return: case_module 用例模块
                     case_mark mark标记
            '''
            server = self.jenkins_server()
            case_module = server.get_job_info("exec_group_by_module")["actions"][0]["parameterDefinitions"][0]["choices"]
            case_mark = server.get_job_info("exec_group_by_mark")["actions"][0]["parameterDefinitions"][0]["choices"]
            self.finish({"case_module": case_module, "case_mark": case_mark})
    

    别忘记去配置url,然后通过这个服务,我们返回case_module,和case_mark,下面就是前端去接收了。我是希望在点击进入页面的时候,这个列表就可以获取到,所以
    在点击左侧菜单栏的时候去发起这个请求,query_case()

                    <ul class="submenu" style="overflow: hidden; display: none;">
                        <li id="ad_mis">
                            <a href="#" onclick="showAtRight('ui_ad_mis');query_case()">
                                <i class="icon-double-angle-right"></i>广告后台</a>
                        </li>
                    </ul>
    
    1. 页面上写好了,那么怎么去发起请求呢? 这里通过js,ajax
    //查询选择框下拉
    function query_case(){
        $.ajax({
            type: "Get",
            async: true,
            dataType: "json",
            url: "/query_case",
            success:function(data){
                for (var i=0;i<data["case_module"].length;i++){
                    document.getElementById("sel_case_module").options
                        .add(new Option(data["case_module"][i],data["case_module"][i]));
                }
                for (var i=0;i<data["case_mark"].length;i++){
                    document.getElementById("sel_case_mark").options
                        .add(new Option(data["case_mark"][i],data["case_mark"][i]));
                }
            },
            error:function(data){
                console.log("请求失败");
            }
        })
    }
    

    上面在js文件里定义qurey_case()这个函数。
    type: "Get",是get方法;
    dataType: "json",数据类型是json;
    url: "/query_case", 这里就会指向你后端url配置里的路径了;
    success:function(data),当请求成功,data就是后端返回的数据了,然后通过for循环去遍历展示到下拉框里

                <div>
                    <p>选择模块进行测试:
                        <select id="sel_case_module" name="case_module" style="border-left- 1px;margin-left: 5px; 360px;">
                            <option value="" selected="selected">请选择要执行测试的case模块</option>
                            <option value="all_case">全部case</option>
                        </select>
                    </p>
                </div>
                <div>
                    <p>选择mark进行测试:
                        <select id="sel_case_mark" name="case_mark" style=" 360px;">
                            <option value="" selected="selected">请选择要执行测试的mark</option>
                        </select>
                    </p>
                </div>
    

    看下实现的效果

    OK,那么我选中了一个下拉后,点击执行,就要去执行测试了。
    同样的套路,前端定义点击事件,去调用js里的 execute_test()函数。

    html文件

            <button type="button" id="exec_test" class="btn btn-sm btn-light" onclick="execute_test()" disabled="true">
                <a href="javascript:void(0)">执行测试</a>
            </button>
    

    后端
    后端通过server.build_job()去执行配置好的任务即可,这里也考虑到了,如果同时选择了module,mark执行后的操作,
    test_result_info 这个代码有冗余,可以优化掉,暂时先这样了

    class ExecTestHandler(BaseHandler):
        '''
        执行测试
        :param sel_case_value 选择的case模块
               sel_case_value  选择的case标记
        '''
        def post(self):
            sel_case_value = self.get_argument("case_module")
            sel_case_mark = self.get_argument("case_mark")
            # 当选择了mark,没选择case模块,执行markcase
            if sel_case_mark != '' and sel_case_value == '':
                server = self.jenkins_server()
                current_job_name = "exec_group_by_mark"
                next_build_number = server.get_job_info(current_job_name)['nextBuildNumber']
                server.build_job(current_job_name, {"mark_name": sel_case_mark})
                test_result_info = {
                    "code": 0,
                    "desc": "",
                    "current_job_name": current_job_name,
                    "current_build_number": next_build_number,
                    "current_last_build_result": "测试中",
                }
                self.finish(test_result_info)
            elif sel_case_mark == '' and sel_case_value != '':
                server = self.jenkins_server()
                if sel_case_value == "all_case":
                    current_job_name = "exec_all_case"
                    next_build_number = server.get_job_info(current_job_name)['nextBuildNumber']
                    server.build_job(current_job_name)
                else:
                    current_job_name = "exec_group_by_module"
                    next_build_number = server.get_job_info('exec_group_by_module')['nextBuildNumber']
                    server.build_job("exec_group_by_module", {"case_name": sel_case_value})
                test_result_info = {
                    "code": 0,
                    "desc": "",
                    "current_job_name": current_job_name,
                    "current_build_number": next_build_number,
                    "current_last_build_result": "测试中",
                }
                self.finish(test_result_info)
            elif sel_case_value == '' and sel_case_mark == '':
                self.finish({"code":-1,"desc":"缺少case模块参数"})
    

    上面就是执行测试的过程了,因为执行测试是需要时间的,不会立即结束,所以返回点数据到前端页面展示,让人知道开始测试啦

    效果就是这样

    5.然后我这边怎么知道测试是否结束了呢?
    这里通过前端的轮询请求查询,当执行结束后,把剩下的数据返回
    前端

                    // 轮询Jenkins执行状态
                    var timer = null;
                    timer = window.setInterval(query_result, 10000); //10s一次
                    function query_result() {
                        req_data = {
                            "job_name": data['current_job_name'],
                            "job_number": data['current_build_number']
                        }
                        $.ajax({
                            type: "Post",
                            async: true,
                            dataType: "json",
                            data: req_data,
                            url: "/query_result",
                            success: function (data) {
                                //查询结果是"SUCCESS",请求查询构建信息
                                if (data["result"] == "SUCCESS") {
                                    window.clearInterval(timer);
                                    $.ajax({
                                        type: "Post",
                                        async: true,
                                        dataType: "json",
                                        url: "/get_job_info",
                                        data: req_data,
                                        success: function (data) {
                                            window.clearInterval(window.setInterval(query_result, 5000));
                                            // console.log("构建成功,查询构建信息");
                                            // console.log(data);
                                            document.querySelector("#job_status").textContent = data['current_last_build_result'];
                                            document.querySelector("#job_duration").textContent = data['current_job_build_duration'];
                                            document.querySelector("#start_time").textContent = data['current_job_build_start_time'];
                                            document.querySelector("#test_report>a").textContent = "查看报告";
                                            document.querySelector("#test_report>a").setAttribute("href", data["report_link"]);
                                        },
                                        error: function (data) {
                                            window.clearInterval(window.setInterval(query_result, 5000));
                                            console.log("查询构建信息失败");
                                        },
                                    })
    

    后端这里有个查询jenkins构建是否完成的服务

    class QueryResultHandler(BaseHandler):
        '''
        查询构建结果
        :return build_result
        '''
        def post(self):
            next_build_number = int(self.get_argument('job_number'))
            current_job_name = self.get_argument('job_name')
            server = self.jenkins_server()
            job_last_build_result = server.get_build_info(current_job_name, next_build_number)['result']  # 任务最新一次构建结果
            build_result = {"result": job_last_build_result}
            self.finish(build_result)
    
    

    当构建完成后,去调用另一个返回剩下信息和测试报告地址的服务(有个解析控制台log的方法,暂时还没写,不急,哈哈)

    class ReturnJobInfoHandler(BaseHandler):
        '''
        构建完成后,返回信息
        :return test_result_info
        '''
        def parse_jenkins_console_log(self):
            '''
            解析Jenkins控制台的log输出
            :return: test_time 测试时长
            '''
            pass
    
        def post(self):
            next_build_number = int(self.get_argument('job_number'))
            current_job_name = self.get_argument('job_name')
            server = self.jenkins_server()
            for i in range(0, 100, 1):
                job_build_duration = (server.get_build_info(current_job_name, next_build_number)['duration']) / 1000
                if job_build_duration == 0:
                    sleep(2)
                    job_build_duration
                    i += 1
            new_job_build_duration = job_build_duration
            time_stamp = server.get_build_info(current_job_name, next_build_number)['timestamp'] / 1000
            current_job_build_start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time_stamp))  # 构建开始日期
            report_link = "http://xxxx:9090/job/%s/%s/allure" % (current_job_name, next_build_number)
            test_result_info = {
                "current_last_build_result": "测试完成",
                "current_job_build_duration": new_job_build_duration,
                "current_job_build_start_time": current_job_build_start_time,
                "report_link": report_link
            }
            self.finish(test_result_info)
    

    OK 测试结束后,页面就可以看到完成的信息了

    点击查看报告

    还是比较好看的报告。

    1. 上面还有个查询机器状态的功能
      这里也是通过js轮询去查询的,然后根据不同的状态给机器状态改色
    //轮询测试机器执行任务状态
    var timer = null;
    timer = window.setInterval(query_running_build,3000);
    function query_running_build(){
        $.ajax({
            type: "Get",
            async: true,
            dataType: "json",
            url: "/query_running_build",
            success:function(data){
                document.querySelector("#running_build_status").textContent = data["running_status"];
                if (data["running_status"] === "空闲中"){
                    document.querySelector("#running_build_status").setAttribute("style", "color: #00be67");
                    document.querySelector("#exec_test").disabled=false;
                }
                else{
                    document.querySelector("#running_build_status").setAttribute("style", "color: red");
                    document.querySelector("#exec_test").disabled=true;
                }
            },
            error:function(data){
                console.log("获取机器执行状态失败,停止轮询")
                window.clearInterval(window.setInterval(query_running_build,3000));
            }
        })
    }
    

    后端对应的服务

    class QueryRunningBuild(BaseHandler):
        def get(self):
            '''
            返回是否有构建任务
            :return: running_status: 构建中,空闲中
            '''
            server = self.jenkins_server()
            running_build = server.get_running_builds()
            if len(running_build) == 0:
                run_status = {"running_status": "空闲中"}
            else:
                run_status = {"running_status": "构建中"}
            self.finish(run_status)
    

    差不多就是这些吧,附上
    python-jenkins的地址:

    https://pypi.org/project/python-jenkins/
    https://python-jenkins.readthedocs.io/en/latest/api.html

    后面再开发可能就是用java springboot +vue 去维护我们新的平台了。那时候应该有新的收获吧。
    以上内容,如果有可以改进或者改错的地方,欢迎指出

  • 相关阅读:
    漫步温泉大道有感
    不可多得的”魔戒“:一堂成功学大师们的浓缩课
    四川新闻网关于IT诗人的报道
    赠徐蕴筝(帮别人名字作诗)
    再游草堂
    赠申芳菲(帮别人名字作诗)
    Oracle内部错误:ORA00600[15801], [1]一例
    Oracle内部错误:ORA00600[OSDEP_INTERNAL]一例
    Oracle O立方服务平台(O3SP)
    Oracle RAC内部错误:ORA00600[keltnfyldmInit]一例
  • 原文地址:https://www.cnblogs.com/pingguo-softwaretesting/p/11428812.html
Copyright © 2011-2022 走看看