• 方案一可借鉴轮子:

    https://github.com/wuyuewuzhenmin/Jmeter-ptc
    

    方案二可借鉴轮子:

    https://github.com/myzhan/boomer
    
  • 你都从 yaml 文件里面拿到了所有的请求和参数这一些,接下来不就像写接口测试脚本一样吗,把对应的参数放到 python 的脚本里面对应的位置,然后用 requests 去执行。所谓的 testsuite testcase teststep ,无非就是执行过程中的先后顺序,然后再去遍历执行每个过程中又嵌套的 testsuite testcase teststep。

    """ Running testcases. """
       Examples:
           >>> tests_mapping = {
                   "project_mapping": {
                       "functions": {}
                   },
                   "testcases": [
                       {
                           "config": {
                               "name": "XXXX",
                               "base_url": "http://127.0.0.1",
                               "verify": False
                           },
                           "teststeps": [
                               {
                                   "name": "test description",
                                   "variables": [],        # optional
                                   "request": {
                                       "url": "http://127.0.0.1:5000/api/users/1000",
                                       "method": "GET"
                                   }
                               }
                           ]
                       }
                   ]
               }
    
           >>> testcases = parser.parse_tests(tests_mapping)
           >>> parsed_testcase = testcases[0]
    
           >>> test_runner = runner.Runner(parsed_testcase["config"])
           >>> test_runner.run_test(parsed_testcase["teststeps"][0])
    
  • 首先,校验 yaml 文件的格式,返回 yaml 的数据。

    def load_yaml_file(yaml_file):
        """ load yaml file and check file content format
        """
        with io.open(yaml_file, 'r', encoding='utf-8') as stream:
            yaml_content = yaml.load(stream)
            _check_format(yaml_file, yaml_content)
            return yaml_content
    

    接下来,根据你的测试指令后的文件的后缀名,读取对应格式的文件。

    def load_file(file_path):
        if not os.path.isfile(file_path):
            raise exceptions.FileNotFound("{} does not exist.".format(file_path))
    
        file_suffix = os.path.splitext(file_path)[1].lower()
        if file_suffix == '.json':
            return load_json_file(file_path)
        elif file_suffix in ['.yaml', '.yml']:
            return load_yaml_file(file_path)
        elif file_suffix == ".csv":
            return load_csv_file(file_path)
        else:
            # '' or other suffix
            err_msg = u"Unsupported file format: {}".format(file_path)
            logger.log_warning(err_msg)
            return []
    

    然后,下面这个我觉得就很明显了。

    def load_teststep(raw_testinfo):
      ...
    
    def load_testcase(raw_testcase):
      ...
    
    def load_testcase_v2(raw_testcase):
      """ load testcase in format version 2. """
      ...
    
    def load_testsuite(raw_testsuite):
      ...
    

    剩下一些类似 __extend_with_api_ref() __extend_with_testcase_ref() 等等的函数,无非就是读取你的测试步骤中调用的扩展 api 或者 testcase,locate_debugtalk_py() 函数作用就更明显了。一系列相关的东西都加载完,最后开始 load_test 进行最后的汇总组合。

    def load_tests(path, dot_env_path=None):
        """ load testcases from file path, extend and merge with api/testcase definitions.
    
        Args:
            path (str): testcase/testsuite file/foler path.
                path could be in 2 types:
                    - absolute/relative file path
                    - absolute/relative folder path
            dot_env_path (str): specified .env file path
    
        Returns:
            dict: tests mapping, include project_mapping and testcases.
                  each testcase is corresponding to a file.
                {
                    "project_mapping": {
                        "PWD": "XXXXX",
                        "functions": {},
                        "env": {}
                    },
                    "testcases": [
                        {   # testcase data structure
                            "config": {
                                "name": "desc1",
                                "path": "testcase1_path",
                                "variables": [],                    # optional
                            },
                            "teststeps": [
                                # test data structure
                                {
                                    'name': 'test desc1',
                                    'variables': [],    # optional
                                    'extract': [],      # optional
                                    'validate': [],
                                    'request': {}
                                },
                                test_dict_2   # another test dict
                            ]
                        },
                        testcase_2_dict     # another testcase dict
                    ],
                    "testsuites": [
                        {   # testsuite data structure
                            "config": {},
                            "testcases": {
                                "testcase1": {},
                                "testcase2": {},
                            }
                        },
                        testsuite_2_dict
                    ]
                }
    
        """
        if not os.path.exists(path):
            err_msg = "path not exist: {}".format(path)
            logger.log_error(err_msg)
            raise exceptions.FileNotFound(err_msg)
    
        if not os.path.isabs(path):
            path = os.path.join(os.getcwd(), path)
    
        load_project_tests(path, dot_env_path)
        tests_mapping = {
            "project_mapping": project_mapping
        }
    
        def __load_file_content(path):
            loaded_content = None
            try:
                loaded_content = load_test_file(path)
            except exceptions.FileFormatError:
                logger.log_warning("Invalid test file format: {}".format(path))
    
            if not loaded_content:
                pass
            elif loaded_content["type"] == "testsuite":
                tests_mapping.setdefault("testsuites", []).append(loaded_content)
            elif loaded_content["type"] == "testcase":
                tests_mapping.setdefault("testcases", []).append(loaded_content)
            elif loaded_content["type"] == "api":
                tests_mapping.setdefault("apis", []).append(loaded_content)
    
        if os.path.isdir(path):
            files_list = load_folder_files(path)
            for path in files_list:
                __load_file_content(path)
    
        elif os.path.isfile(path):
            __load_file_content(path)
    
        return tests_mapping
    

    总之,多看看源码就好辣 hhh
    源码:https://github.com/httprunner/httprunner
    回答问题的相关代码文件: ./httprunner/loader.py

  • ppts - 网页性能分析工具 at 2019年09月27日

    ppts v1.4.0: 当输出格式为 json 时,支持通过 webhook 发送测试数据给群机器人

  • ppts - 网页性能分析工具 at 2019年09月23日

    ppts 安装需要 node 环境,版本最好是最新的,有使用问题或技术问题可以直接到 Github 上提 issues,或此处留言。如果对你的使用有帮助,请点赞或 Star 一下 ~