一:yaml簡介及基礎語法
yaml是專門用來寫配置文件的語言,非常簡潔和強大,遠比 JSON 格式方便。
1.1 yaml基礎語法規則
- 大小寫敏感
- 使用縮進表示層級關系
- 不允許使用 TAB 鍵來縮進,只允許使用空格鍵來縮進
- 縮進的空格數量不重要
- 使用"#"來表示注釋
1.2 yaml 支持的數據結構有三種
- 對象:鍵值對的集合,又稱為映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 數組:一組按次序排列的值,又稱為序列(sequence) / 列表(list)
- 純量(scalars):單個的、不可再分的值
1.2.1 對象
對象的一組鍵值對,使用:(冒號)結構表示。冒號之后必須有一個空格
#test.yml文件 animal: pets #轉為python 如下: {'animal': 'pets'}
yaml 也允許另一種寫法,將所有鍵值對寫成一個行內對象。
#test.yml文件 hash: { name: Steve, foo: bar } #轉為python如下: {'hash': {'name': 'Steve', 'foo': 'bar'}}
1.2.2 數組
一組連詞線開頭的行,構成一個數組。
#test.yml文件 - Cat - Gog - Goldfis #轉為python 如下: ['Cat', 'Gog', 'Goldfis']
數據結構的子成員是一個數組,則可以在該項下面縮進一個空格。
#test.yml 文件 - - Cat - Dog - Goldfish #轉為 python 如下: [['Cat', 'Dog', 'Goldfish']]
數組也可以采用行內表示法。
#test.yml文件 animal: [Cat, Dog] #轉為 python 如下; {'animal': ['Cat', 'Dog']}
1.2.3 復合結構
對象和數組可以結合使用,形成復合結構。
#test.yml文件 languahes: - JavaScript - java - Python websites: YAML: yaml.org Ruby: ruby-lang.org Python: python.org Perl: use.perl.org #轉為 python 如下; { 'languahes': ['JavaScript', 'java', 'Python'], 'websites': {'YAML': 'yaml.org', 'Ruby': 'ruby-lang.org', 'Python': 'python.org', 'Perl': 'use.perl.org'} }
1.2.4 純量
純量是最基本的、不可再分的值。
純量包含:字符串 整數 浮點數 布爾值 Null(用~表示) 時間 日期 其中:用~表示null ; 時間、日期采用IS08601格式
#test.yml文件 name: 'xiaoli' age: 22 weight: 57.30 isStudent: true address: ~ time: 2001-12-14t21:59:43.10-05:00 date: 1976-07-31 #轉為 python 如下: {'name': 'xiaoli', 'age': 22, 'weight': 57.3, 'isStudent': True, 'address': None,
'time': datetime.datetime(2001, 12, 14, 21, 59, 43, 100000, tzinfo=datetime.timezone(datetime.timedelta(-1, 68400))),
'date': datetime.date(1976, 7, 31)}
yaml允許使用兩個感嘆號,強制轉換數據類型。
#test.yml文件 e: !!str 22 f: !!str true #轉為 python 如下: {'e': '22', 'f': 'true'}
1.2.5 錨點&和引用*
在yaml文件中如何引用變量?當我們在一個yaml文件中寫很多測試數據時候,比如一些配置信息像用戶名,郵箱,數據庫配置等很多地方都會重復用到。
重復的數據,如果不設置變量,后續維護起來就很困難。
yaml文件里面也可以設置變量(錨點&),其它地方重復用到的話,可以用*引用
#test.yml文件
defaults: &defaults
adapter: postgres
host: localhost
development:
database: myapp_development
<<: *defaults
test:
database: myapp_test
<<: *defaults
等同於下面的代碼:
defaults:
adapter: postgres
host: localhost
development:
database: myapp_development
adapter: postgres
host: localhost
test:
database: myapp_test
adapter: postgres
host: localhost
&用來建立錨點(defaults),<<表示合並到當前數據,*用來引用錨點。
1.2.6 *引用value值
上面的例子是對defaults整體的數據,引用到其它地方了,有時候我們只想引用其中的一個值
#test.yml 文件 defaults: adapter: postgres host: &host 127.0.0.1 development: database: myapp_development myhost: *host #等同於下面的數據 defaults: adapter: postgres host: &host 127.0.0.1 development: database: myapp_development myhost: 127.0.0.1
二:yaml文件的讀寫
python本身並沒有自帶的處理yaml文件的庫,需要下載並安裝第三方庫PyYAML 或 ruamel.yaml ,這里我們先安裝PyYAML。
#安裝命令 pip install pyyaml # 下載速度慢的話加上清華鏡像源 pip install pyyaml -i https://pypi.tuna.tsinghua.edu.cn/simple
2.1 從yaml中讀取數據
# yaml文件,文件名為test.yml os: Android osVersion: 10 account: username: xiaoqq password: 123456 deviceName: null appPackage: ~ bool1: True
讀取代碼:
import yaml import os file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml')) def read_yaml_data(): with open(file_path, 'r', encoding='utf-8') as f: data = yaml.load(f, Loader=yaml.FullLoader) print(f'讀取的數據:{data}') print(f'數據類型為:{type(data)}') if __name__ == '__main__': read_yaml_data()
讀取結果:
讀取的數據:{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True}
數據類型為:<class 'dict'>
從讀取結果可以看出:
1,讀取出來的數據不會改變原數據類型,即yaml里是什么數據類型,讀出來就是什么類型。
2,Loader=yaml.FullLoader參數不寫的話對結果不會有影響,但運行時會出現警告信息。
3,yaml.load(f.read(), Loader=yaml.FullLoader)也可以寫成yaml.load(f, Loader=yaml.FullLoader),讀取出來的結果相同。
注意:
pyyaml模塊在python中用於處理yaml格式數據,主要使用yaml.safe_dump()、yaml.safe_load()函數將python值和yaml格式數據相互轉換。當然也存在yaml.dump()、yaml.load()函數,同樣能實現數據轉換功能,只是官方不太推薦使用。官方給出的解釋,因為yaml.safe_dump()、yaml.safe_load() 能夠,而且yaml.safe_dump()、yaml.safe_load()比yaml.dump()、yaml.load()安全:
2.2 從yaml中讀取多組數據
yaml多組數據時,每組數據之間需要用3橫杠分隔’—’,如下:
#test.yml文件 os: Android osVersion: 10 account: username: xiaoqq password: 123456 deviceName: null appPackage: ~ bool1: True --- os: ios osVersion: 12 account1: username2: Lilei password2: 888888
從yaml中讀取多組數據時需要使用yaml.load_all()或者yaml.safe_load_all()方法,返回結果為一個生成器,需要使用for循環語句獲取每組數據。代碼如下:
def read_yaml_data(): file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml')) with open(file_path, 'r', encoding='utf-8') as f: data = yaml.safe_load_all(f) print(f'讀取的數據:{data}') print(f'數據類型為:{type(data)}') for i in data: print(i) if __name__ == '__main__': read_yaml_data()
讀取結果:
讀取的數據:<generator object load_all at 0x102b28258> 數據類型為:<class 'generator'> {'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True} {'os': 'ios', 'osVersion': 12, 'account1': {'username2': 'Lilei', 'password2': 888888}}
2.3 單組數據寫入yaml文件
使用yaml.dump()或者yaml.safe_dump()方法,加入allow_unicode=True參數防止寫入的中文亂碼,如下
def write_yaml_data(): data = {'hash': {'name': 'Steve', 'foo': '公寓'}} file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml')) with open(file_path, 'w', encoding='utf-8') as f: yaml.safe_dump(data, f, allow_unicode=True) if __name__ == '__main__': write_yaml_data()
執行結果:

2.4 多組數據寫入yaml文件
使用yaml.dump_all()或者yaml.safe_dump_all()方法,如下:
def write_yaml_data(): # 寫入多組數據 data = {'hash': {'name': 'Steve', 'foo': '公寓'}} data1 = {'hash1': {'name1': 'admin', "name": "流動人口社區"}} file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml')) with open(file_path, 'w', encoding='utf-8') as f: yaml.safe_dump_all(documents=[data,data1], stream=f, allow_unicode=True) if __name__ == '__main__': write_yaml_data()
執行結果:

2.5 ruamel.yaml 讀寫yaml文件
用yaml模塊寫入字典嵌套字典這種復雜的數據,會出現大括號{ },不是真正的yaml文件數據,可以用ruamel模塊就解決。
參考:https://www.cnblogs.com/yoyoketang/p/9255109.html
安裝方法:
pip install ruamel.yaml
2.5.1 用原生的yaml模塊寫入這種字典嵌套字典的復雜數據
import os import yaml # 將字典寫入到yaml desired_caps = { 'platformName': 'Android', 'platformVersion': '7.0', 'deviceName': 'A5RNW18316011440', 'appPackage': 'com.tencent.mm', 'appActivity': '.ui.LauncherUI', 'automationName': 'Uiautomator2', 'unicodeKeyboard': [True,"hh"], 'resetKeyboard': True, 'noReset': True, 'chromeOptions': {'androidProcess': 'com.tencent.mm:tools'} } curpath = os.path.dirname(os.path.realpath(__file__)) yamlpath = os.path.join(curpath, "caps.yaml") # 寫入到yaml文件 with open(yamlpath, "w", encoding="utf-8") as f: yaml.dump(desired_caps, f)
運行結果:

由運轉結果,發現字典嵌套的字典,出現了大括號:{androidProcess: 'com.tencent.mm:tools'},(在pyyaml版本為6.0時 已經不存在該問題)這不是真正的yaml數據,不是我們想要的,解決辦法看下文
2.5.2 使用ruamel模塊 方法跟yaml差不多,只是在使用dump方法多個一個參數:Dumper=yaml.RoundTripDumper
import os from ruamel import yaml # 將字典寫入到yaml desired_caps = { 'platformName': 'Android', 'platformVersion': '7.0', 'deviceName': 'A5RNW18316011440', 'appPackage': 'com.tencent.mm', 'appActivity': '.ui.LauncherUI', 'automationName': 'Uiautomator2', 'unicodeKeyboard': True, 'resetKeyboard': True, 'noReset': True, 'chromeOptions': {'androidProcess': 'com.tencent.mm:tools'} } curpath = os.path.dirname(os.path.realpath(__file__)) yamlpath = os.path.join(curpath, "caps.yaml") # 寫入到yaml文件 with open(yamlpath, "w", encoding="utf-8") as f: yaml.dump(desired_caps, f, Dumper=yaml.RoundTripDumper)
執行結果:

2.6 ruamel.yaml 讀yaml文件
2.6.1.使用ruamel.yaml模塊也能讀yaml文件,使用方法相對於之前的yaml.load方法多加一個參數:Loader=yaml.Loader
def read_yaml_data(): # 讀取單組數據 file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml')) with open(file_path, 'r', encoding='utf-8') as f: data = yaml.load(f, Loader=yaml.Loader) print(f'讀取的數據:{data}') print(f'數據類型為:{type(data)}') if __name__ == '__main__': read_yaml_data()
讀取結果:
讀取的數據:{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True}
數據類型為:<class 'dict'>
三:使用template替換yaml文件中的變量
在接口自動化測試的時候,yaml 文件一般放測試的數據或當配置文件使用,yaml 文件存放靜態的數據是沒問題的,python的數據類型基本上都是支持的。
有時候我們想在 yaml 文件中引用變量來讀取 python 代碼的設置值
3.1 template 使用
template 是字符串模板,用於替換字符串中的變量,是 string 的一個類引用變量有 2 種格式
- $variable 使用 $變量名 引用變量
- ${variable} 使用 ${變量名} 大括號包起來
第一種 $variable
from string import Template tempTem = Template("My name is $name , i like $fancy") d = {'name': 'admin', 'fancy': 'python'} print(tempTem.substitute(d))
執行結果:
My name is admin, i like python
第二種 ${variable}
from string import Template tempTemp1 = Template("My name is ${name} , i like ${fancy}") d = {'name': 'admin', 'fancy': 'python'} print(tempTemp1.substitute(d))
執行結果:
My name is admin, i like python
3.2 safe_substitute使用
上面的方式只能嚴格的匹配變量,當字符串中有$符號,不想匹配變量的時候,會報錯
from string import Template tempTemplate = Template("$My name is ${name} , i like ${fancy}") d = {'name': 'admin', 'fancy': 'python'} print(tempTemplate.substitute(d))
這段,$符號加在My的前面,我只想讓它是一個普通的字符串,不想引用變量,就出現了報錯說找不到這個key
Traceback (most recent call last): File "E:/PycharmScripts/test_selenium_wire_yaml/test_yaml.py", line 25, in <module> print(tempTemp2.substitute(d)) File "C:\Python37\lib\string.py", line 132, in substitute return self.pattern.sub(convert, self.template) File "C:\Python37\lib\string.py", line 125, in convert return str(mapping[named]) KeyError: 'My'
雖然字符串定義了多個變量,但是引用的時候只給了name這個值,也不影響運行,沒給值的當普通字符串出來,這樣就很完美了
tempTemplate = Template("$My name is ${name} , i like ${fancy}") d = {'name': 'admin','fancy': 'python'} print(tempTemplate.safe_substitute(d))
執行結果;
$My name is admin , i like python
3.3 yaml 文件引用變量
通過前面 Template 的基礎使用,已經掌握了基本的用法了,接下來在 yaml 文件中引用變量
request: headers: Content-Type: application/json User-Agent: python-requests/2.18.4 json: username: $user password: $psw
python讀yaml文件代碼:
from string import Template import yaml with open("test.yml", encoding='utf-8') as fp: read_yml_str = fp.read() # print(read_yml_str) tempTemplate1 = Template(read_yml_str) new_yaml = tempTemplate1.safe_substitute({"user": "admin", "psw": "123456 "}) print(f'Template補全的數據:\n{new_yaml}') # yml 文件數據,轉 python 類型 yaml_data = yaml.safe_load(new_yaml) print(f'yml文件轉python的數據:\n{yaml_data}')
執行結果:
''' Template補全的數據: request: headers: Content-Type: application/json User-Agent: python-requests/2.18.4 json: username: admin password: 123456 yml文件轉python的數據: {
'request': {'headers': {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4'},
'json': {'username': 'admin', 'password': 123456}}
} '''
四:yaml文件調用python外部函數
如何在 yaml文件中引用一個 python 的函數?
問題分析
其實yaml 和 json 文件本質上是一樣的,都是靜態的文件,是不能直接引用 python 的函數。
那這時候就有人問到了,那為什么 httprunner 框架可以在yaml文件中引用函數呢?
這是因為 httprunner 框架封裝過對 yaml 文件的讀取了,它是先讀取文件內容,正則提取到 ${} 括號里面的函數內容,再把函數的值替換過去
那么我們能不能實現這種效果呢?
當然是可以的,可以參考httprunner的實現,也可以用到 python 的模板 jinja2 來實現。
使用模板可以編寫出可讀性更好,更容易理解和維護的代碼,並且使用范圍非常廣泛,因此怎么使用模板主要取決於我們的想象力和創造力。
python的模板庫jinja2 功能是非常強大的
4.1 jinja2 模板庫
pip install jinja2
4.2 render 函數實現
在yaml文件中,通過 {{ 函數名稱() }} 來引用函數

寫個 render 函數讀取 yaml 文件內容
import jinja2 import os,random def jinja2_template_render(tpl_path, **kwargs): path, filename = os.path.split(tpl_path) env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 創建一個包加載器對象 template = env.get_template(filename) # 獲取一個模板文件 return template.render(**kwargs) # 進行模板渲染
讀取到的yaml文件本質上都是字符串來讀取的,通過jinja2 模板來讀取,會先把函數的值替換進去。最后再轉成python的dict結構
import jinja2 import os,random,yaml def jinja2_template_render(tpl_path, **kwargs): path, filename = os.path.split(tpl_path) env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 創建一個包加載器對象 template = env.get_template(filename) # 獲取一個模板文件 return template.render(**kwargs) # 進行模板渲染 # yaml 文件調用以下函數 def rand_str(): return str(random.randint(1000000, 2000000)) if __name__ == '__main__': r = jinja2_template_render('test.yml',**{"user":"admin","pwd":1234,"rand_str":rand_str}) print(f'jinja2渲染后的數據\n{r}') print(yaml.safe_load(r))
執行的結果:
''' jinja2渲染后的數據: username: admin password: 1234 func: 1980646 {'username': 'admin', 'password': 1234, 'func': 1980646} '''
上面讀取函數是寫死的,我們希望能自動加載類似於debugtalk.py的文件來自動加載函數
4.3 自動加載debug.py里面的函數
寫一個debug.py 文件,實現 yaml 文件里面定義的函數去替換值
#debug.py文件 import random # yaml 文件調用以下函數 def rand_str(): return str(random.randint(1000000, 2000000))
在run.py里面定義一個函數自動讀取debug.py里面的函數,生成dict 鍵值對格式
關鍵動態導入對象請參考:https://blog.csdn.net/edward_zcl/article/details/88809212
def all_functions(): """加載debug.py模塊""" debug_module = importlib.import_module("debug") all_function = inspect.getmembers(debug_module, inspect.isfunction) print(dict(all_function)) return dict(all_function)
函數返回 {'rand_str': <function rand_str at 0x0000000002E3F798>}
完整的run.py文件內容
import jinja2 import os,random,yaml import importlib,inspect def jinja2_template_render(tpl_path, **kwargs): path, filename = os.path.split(tpl_path) env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 創建一個包加載器對象 template = env.get_template(filename) # 獲取一個模板文件 return template.render(**kwargs) # 進行模板渲染 def all_functions(): """加載debug.py模塊""" debug_module = importlib.import_module("debug") all_function = inspect.getmembers(debug_module, inspect.isfunction) print(dict(all_function)) return dict(all_function) if __name__ == '__main__': temlplate_params = all_functions() #獲取調用函數 temlplate_params["user"] = "admin" #獲取user變量值 temlplate_params["pwd"] = "1234" #獲取pwd變量值 r = jinja2_template_render('test.yml',**temlplate_params) print(f'jinja2渲染后的數據\n{r}') print(yaml.safe_load(r))
執行結果:
''' #all_functions()返回值 {'rand_str': <function rand_str at 0x0000000002E3F708>} jinja2渲染后的數據 username: admin password: 1234 func: 1730704 {'username': 'admin', 'password': 1234, 'func': 1730704} '''
