zoukankan      html  css  js  c++  java
  • 【Python】实现将Excel编写的用例上传到testlink指定用例集

    此功能已优化: https://github.com/HeBinz/TestCase2Testlink

    背景

    百科上说TestLink 是基于web的测试用例管理系统,主要功能是测试用例的创建、管理和执行,并且还提供了一些简单的统计功能。其他的信息可以参照他们的官网http://www.testlink.org/

    楼主所在的项目,需求、提测、测试等等都是使用的是gitlab的一个个issue加标签管理的,用例的维护在开始的时候也是用的它。后来我们的直接上级职位发生了变更,新leader建议我们使用testlink。

    试用了一段时间之后,发现一个非常令人诟病的地方--用例导入只支持xml格式,而且它本身的用例编写也不太方便。

    这也是我写这个用例导入小工具的初衷。

    思路

    开始时博主想的是,把Excel编写的用例(Excel应该是所有测试人员编写用例的首选吧)转换为xml格式导入,后来发现更好的办法-封装Python testlink API。

    写得比较仓促,还有很多可以改进的地方,先用起来,后续优化。

    具体实现

    环境依赖

    环境依赖 安装方法
    Python3
    xlrd库 pip install xlrd
    testlink库 pip install TestLink-API-Python-client

    目录结构

    目录结构如下,testCase目录用于存放测试用例,upload_excel_data.py用于用例转换上传,logger_better.py用于记录日志,。

    D:PROJECTUPLOAD_DATA2TESTLIN
    │  logger_better.py
    │  upload_excel_data.py
    │
    └─testCase
            testCase_Example.xlsx
    

    使用方法

    • 登陆testlink后点击上方个人账号进入个人中心,新页面点击 '生成新的秘钥',使用该key替换掉upload_excel_data.py文件中的key值;

    • 使用get_projects_info函数获取项目所在project_id,替换掉upload_excel_data.py中的project_id

    • 使用鼠标选中想要上传用例的用例集,点击右键获取父节点ID,替换掉upload_excel_data.py中的father_id

    upload内容

    #! /usr/bin/python
    # coding:utf-8 
    """ 
    @author:Bingo.he 
    @file: upload_excel_data.py 
    @time: 2018/05/03 
    """
    import collections
    import testlink
    import xlrd
    import os
    from logger_better import Log
    
    logger = Log(os.path.basename(__file__))
    
    count_success = 0
    count_fail = 0
    
    def get_projects_info():
        project_ids = []
        projects = tlc.getProjects()
        for project in projects:
            project_ids.append({project['name']: project['id']})
        return project_ids
    
    def get_projects_id():
        project_ids = []
        projects = tlc.getProjects()
        for project in projects:
            project_ids.append(project['id'])
        return project_ids
    
    def get_suites(suite_id):
        """
        获取用例集
        :return:
        """
        try:
            suites = tlc.getTestSuiteByID(suite_id)
            return suites
        except testlink.testlinkerrors.TLResponseError as e:
            # traceback.print_exc()
            logger.warning(str(e).split('
    ')[1])
            logger.warning(str(e).split('
    ')[0])
            return
    
    def readExcel(file_path):
        """
        读取用例数据
        :return:
        """
        case_list = []
        try:
            book = xlrd.open_workbook(file_path)  # 打开excel
        except Exception as error:
            logger.error('路径不在或者excel不正确 : ' + str(error))
            return error
        else:
            sheet = book.sheet_by_index(0)  # 取第一个sheet页
            rows = sheet.nrows  # 取这个sheet页的所有行数
            for i in range(rows):
                if i != 0:
                    case_list.append(sheet.row_values(i))  # 把每一条测试用例添加到case_list中
        return case_list
    
    def check_excel_data(func):
        """
        参数有效性校验
        :param func:
        :return:
        """
    
        def _check(*args, **kw):
            global count_fail
            global count_success
    
            # 校验项目ID及测试集ID的有效性
            if not args[0] in get_projects_id():
                logger.error('project_id is not auth')
                return
            if not get_suites(args[1]):
                logger.error('father_id is not auth')
                return
    
            # 检测测试数据的有效性
            for k, v in kw.items():
                if v == "" and k not in ['summary', 'importance']:
                    logger.warning("TestCase '{title}' Parameter '{k}' is null".format(title=kw['title'], k=k))
            try:
                func(args[0], args[1], kw)
                count_success += 1
            except Exception as e:
                logger.error(e)
                count_fail += 1
    
        return _check
    
    def format_info(source_data):
        """
        转换Excel中文关键字
        :param source_data:
        :return:
        """
        switcher = {
            "低": 1,
            "中": 2,
            "高": 3,
            "自动化": 2,
            "手工": 1
        }
        return switcher.get(source_data, "Param not defind")
    
    @check_excel_data
    def create_testcase(test_project_id, suits_id, data):
        """
        :param test_project_id:
        :param suits_id:
        :param data:
        :return:
        """
        # 设置优先级默认值及摘要默认值
        if data['importance'] not in [1, 2, 3]:
            data['importance'] = 3
        if data["summary"] == "":
            data["summary"] = "无"
    
        # 初始化测试步骤及预期结果
        for i in range(0, len(data["step"])):
            tlc.appendStep(data["step"][i][0], data["step"][i][1], data["automation"])
    
        tlc.createTestCase(data["title"], suits_id, test_project_id, data["authorlogin"], data["summary"],
                           preconditions=data["preconditions"], importance=data['importance'], executiontype=2)
    
    def excute_creat_testcase(test_project_id, test_father_id, test_file_name):
        # 对project_id father_id 做有效性判断
        if test_project_id not in get_projects_id():
            logger.error('project_id is not auth')
            return
        if not get_suites(test_father_id):
            logger.error('father_id is not auth')
            return
    
        # 获取用例
        test_cases = readExcel(os.path.join('testCase', test_file_name))
        if not isinstance(test_cases, collections.Iterable):
            return
    
        # 格式化用例数据
        for test_case in test_cases:
            testCase_data = {
                "title": test_case[0],
                "preconditions": test_case[1],
                "step": list(zip(test_case[2].split('
    '), test_case[3].split('
    '))),  # 以换行符作为测试步骤的分界
                "automation": format_info(test_case[4]),  # 1  手工, 2 自动
                "authorlogin": test_case[5],
                "importance": format_info(test_case[6]),
                "summary": test_case[7]
            }
    
            create_testcase(test_project_id, test_father_id, **testCase_data)
        logger.info("本次操作共提交 {} 条数据,成功导入 {} 条,失败 {} 条".format(count_success + count_fail, count_success, count_fail))
    
    if __name__ == "__main__":
        url = "http://localhost/testlink/lib/api/xmlrpc/v1/xmlrpc.php"  # 替换为testlink对应URL
        key = "3aca080de61e3e24b5be209a23fa0652"  # 这个key是错误的key,登陆testlink后点击上方个人账号进入个人中心,新页面点击 '生成新的秘钥'获取
        file_name = "testCase_Example.xlsx"
        project_id = "2354879"  # 可以通过 print(get_projects_info())获取
        father_id = "2054879"  # 鼠标选中想要上传用例的用例集,点击右键获取父节点ID
        tlc = testlink.TestlinkAPIClient(url, key)
    
        # print("项目信息: ", get_projects_info())
        excute_creat_testcase(project_id, father_id, file_name)
    
    

    logger内容

    #! /usr/bin/python
    # coding:utf-8 
    """ 
    @author:Bingo.he 
    @file: logger_better.py 
    @time: 2018/02/12 
    """  
    import logging
    import time
    import os
    
    cur_path = os.path.dirname(os.path.realpath(__file__))
    log_path = os.path.join(cur_path, 'logs')
    
    if not os.path.exists(log_path):  os.mkdir(log_path)
    
    class Log():
        def __init__(self, logger, logname='{}.log'.format(time.strftime('%Y-%m-%d'))):
            self.logname = os.path.join(log_path, logname)
            self.logger = logging.getLogger(logger)
            self.logger.setLevel(logging.DEBUG)
            self.formatter = logging.Formatter('[%(asctime)s]-[%(name)s]-%(levelname)s: %(message)s')
    
        def __console(self, level, message):
            fh = logging.FileHandler(self.logname, 'a', encoding='utf-8')
            fh.setLevel(logging.DEBUG)
            fh.setFormatter(self.formatter)
            self.logger.addHandler(fh)
    
            ch = logging.StreamHandler()
            ch.setLevel(logging.DEBUG)
            ch.setFormatter(self.formatter)
            self.logger.addHandler(ch)
    
            if level == 'info':
                self.logger.info(message)
            elif level == 'debug':
                self.logger.debug(message)
            elif level == 'warning':
                self.logger.warning(message)
            elif level == 'error':
                self.logger.error(message)
            self.logger.removeHandler(ch)
            self.logger.removeHandler(fh)
            fh.close()
    
        def debug(self, message):
            self.__console('debug', message)
    
        def info(self, message):
            self.__console('info', message)
    
        def warning(self, message):
            self.__console('warning', message)
    
        def error(self, message):
            self.__console('error', message)
    
    if __name__ == "__main__":
        log = Log(os.path.basename(__file__))
        log.info("---测试开始----")
        log.info("操作步骤1,2,3")
        log.warning("----测试结束----")
    

    其他:

    • 用例步骤分隔符:当前使用换行符分隔,可修改excute_creat_testcase函数中testCase_data的step参数
    • Excel中作者信息必须与提供的key值对应

    测试用例格式

    在此感谢该API的作者。

    Python testlink API Github项目地址

  • 相关阅读:
    windows性能计数器
    bootstrap2.0与3.0的区别
    prototype.js简介
    .NET生成静态页面并分页
    .net 生成 静态页面
    传统的生成静态页面
    vimrc
    nginx模块动态加载(http)
    ffmpeg --help full
    confiure
  • 原文地址:https://www.cnblogs.com/Detector/p/9022408.html
Copyright © 2011-2022 走看看