**由于做数据驱动时,变量获取数据总是csv里的第一行数据,因此看了一下源码,但是还是很蒙b**
一、运行逻辑步骤
1.生成的pytest用例文件中:
a.测试类类继承HttpRunner;
b.入口为test_start(),如果需要进行数据驱动,则重写test_start()方法改为传参的方式test_start(param)
源码的test_start()方法:
def test_start(self, param: Dict = None) -> "HttpRunner":
"""main entrance, discovered by pytest"""
self.__init_tests__() # 1.将测试用例中的测试步骤都加入到列表
self.__project_meta = self.__project_meta or load_project_meta(
self.__config.path # 2.加载元数据信息,load testcases, .env, debugtalk.py
)
self.__case_id = self.__case_id or str(uuid.uuid4()) # 生成case_id
st_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) # 日志保存时间(这我自己加的)
self.__log_path = self.__log_path or os.path.join( # 日志目录
self.__project_meta.RootDir, "logs", f"{st_time}-{self.__case_id}.run.log"
)
log_handler = logger.add(self.__log_path, level="DEBUG") # 3.生成日志
# parse config name
config_variables = self.__config.variables # 4.config-获取变量信息,默认是{}
if param:
config_variables.update(param) # 5.传入参数中的变量信息更新到config_variables
config_variables.update(self.__session_variables) # 别的地方设置的变量也加进来
self.__config.name = parse_data( # 6.获取config name
self.__config.name, config_variables, self.__project_meta.functions
)
if USE_ALLURE: # allure报告
# update allure report meta
allure.dynamic.title(self.__config.name)
allure.dynamic.description(f"TestCase ID: {self.__case_id}")
logger.info(
f"Start to run testcase: {self.__config.name}, TestCase ID: {self.__case_id}"
)
# 以上准备步骤完成,开始跑用例
try:
return self.run_testcase(
TestCase(config=self.__config, teststeps=self.__teststeps)
)
finally:
logger.remove(log_handler)
logger.info(f"generate testcase log: {self.__log_path}")
源码的run_testcase()方法:
def run_testcase(self, testcase: TestCase) -> "HttpRunner":
"""run specified testcase
Examples:
>>> testcase_obj = TestCase(config=TConfig(...), teststeps=[TStep(...)])
>>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj)
"""
self.__config = testcase.config # 获取config的内容
self.__teststeps = testcase.teststeps
# prepare
self.__project_meta = self.__project_meta or load_project_meta(
self.__config.path
)
self.__parse_config(self.__config)
self.__start_at = time.time()
self.__step_datas: List[StepData] = []
self.__session = self.__session or HttpSession()
# save extracted variables of teststeps
extracted_variables: VariablesMapping = {}
# run teststeps
for step in self.__teststeps:
# override variables
# step variables > extracted variables from previous steps
step.variables = merge_variables(step.variables, extracted_variables)
# step variables > testcase config variables
step.variables = merge_variables(step.variables, self.__config.variables)
# parse variables
step.variables = parse_variables_mapping(
step.variables, self.__project_meta.functions
)
# run step
if USE_ALLURE:
with allure.step(f"step: {step.name}"):
extract_mapping = self.__run_step(step)
else:
extract_mapping = self.__run_step(step)
# save extracted variables to session variables
extracted_variables.update(extract_mapping)
self.__session_variables.update(extracted_variables)
self.__duration = time.time() - self.__start_at
return self
二、其他处理模块
数据处理之类的方法都放在parser.py中,下面是举例
数据关联
变
量
、
{变量}、
变量、变量的代逻辑源码示例:
def parse_string(
raw_string: Text,
variables_mapping: VariablesMapping,
functions_mapping: FunctionsMapping,
) -> Any:
""" parse string content with variables and functions mapping.
Args:
raw_string: raw string content to be parsed.
variables_mapping: variables mapping.
functions_mapping: functions mapping.
Returns:
str: parsed string content.
Examples:
>>> raw_string = "abc${add_one($num)}def"
>>> variables_mapping = {"num": 3}
>>> functions_mapping = {"add_one": lambda x: x + 1}
>>> parse_string(raw_string, variables_mapping, functions_mapping)
"abc4def"
"""
try:
match_start_position = raw_string.index("$", 0) # 3
parsed_string = raw_string[0:match_start_position] # abc
except ValueError:
parsed_string = raw_string
return parsed_string
while match_start_position < len(raw_string):
# Notice: notation priority
# $$ > ${func($a, $b)} > $var
# search $$
dollar_match = dolloar_regex_compile.match(raw_string, match_start_position)
if dollar_match:
match_start_position = dollar_match.end()
parsed_string += "$"
continue
# search function like ${func($a, $b)}
func_match = function_regex_compile.match(raw_string, match_start_position)
if func_match:
func_name = func_match.group(1)
func = get_mapping_function(func_name, functions_mapping)
func_params_str = func_match.group(2)
function_meta = parse_function_params(func_params_str)
args = function_meta["args"]
kwargs = function_meta["kwargs"]
parsed_args = parse_data(args, variables_mapping, functions_mapping)
parsed_kwargs = parse_data(kwargs, variables_mapping, functions_mapping)
try:
func_eval_value = func(*parsed_args, **parsed_kwargs)
except Exception as ex:
logger.error(
f"call function error:\n"
f"func_name: {func_name}\n"
f"args: {parsed_args}\n"
f"kwargs: {parsed_kwargs}\n"
f"{type(ex).__name__}: {ex}"
)
raise
func_raw_str = "${" + func_name + f"({func_params_str})" + "}"
if func_raw_str == raw_string:
# raw_string is a function, e.g. "${add_one(3)}", return its eval value directly
return func_eval_value
# raw_string contains one or many functions, e.g. "abc${add_one(3)}def"
parsed_string += str(func_eval_value)
match_start_position = func_match.end()
continue
# search variable like ${var} or $var
var_match = variable_regex_compile.match(raw_string, match_start_position)
if var_match:
var_name = var_match.group(1) or var_match.group(2)
var_value = get_mapping_variable(var_name, variables_mapping)
if f"${var_name}" == raw_string or "${" + var_name + "}" == raw_string:
# raw_string is a variable, $var or ${var}, return its value directly
return var_value
# raw_string contains one or many variables, e.g. "abc${var}def"
parsed_string += str(var_value)
match_start_position = var_match.end()
continue
curr_position = match_start_position
try:
# find next $ location
match_start_position = raw_string.index("$", curr_position + 1)
remain_string = raw_string[curr_position:match_start_position]
except ValueError:
remain_string = raw_string[curr_position:]
# break while loop
match_start_position = len(raw_string)
parsed_string += remain_string
return parsed_string
数据驱动3种方式源码示例:
三、求助大佬们!!
感觉hrun数据驱动运行时,有bug,所以看了下源码,但是还很蒙b,bug如下:
(我操作应该是没问题的啊!)
csv数据文件:
生成的pytest用例代码:
点击运行会运行2遍:
第一遍:
第二遍:
尝试过很多次,还是这样,这是为啥呢?求助大佬
四、问题解决
感谢大佬,问题得以解决!
pydantic包的版本问题导致,具体原因未知,但好歹解决
安装pydantic包:pip install pydantic==1.8.2