tep是一款測試工具,在pytest測試框架基礎上整合了第三方包,提供專案腳手架,幫助以寫Python程式碼方式,快速實現自動化專案落地。
在tep專案中,自動化測試用例都是放到tests
目錄下的,每個.py
檔案相互獨立,沒有依賴,1個檔案即1條用例,彼此分離。
雖然用例也能相互引用,但是除非萬不得已,一般不建議這麼做,牽一髮動全身,後期維護困難。
用例的程式碼編寫,思路是從上往下的,和pytest/unittest/script常規寫法無異,不會有學習成本,一般也不會有問題。有成本有問題的可能是環境變數和fixtures,因為tep做了封裝,提供了依賴注入的共享方式,fixture又是pytest較難理解的知識點,所以有必要通過本文來講講tep環境變數、fixtures、用例三者之間的關係,幫助理解,以便更靈活順手的藉助tep實現pytest自動化專案。
假如不用環境變數和fixtures
假如不用環境變數和fixtures,是完全可以的!比如,在tests
下新建指令碼login_test.py
:
from tep.client import request
def test():
response = request("post",
url="https://qa.com/api/users/login",
headers={"Content-Type": "application/json"},
json={
"username": "admin",
"password": "123456",
}
)
assert response.status_code < 400
請求介面https://qa.com/api/users/login
,斷言響應狀態碼小於400。問題來了:url
固定,假如需要切換兩個環境qa
和release
,該怎麼辦?
引數化
無論是做自動化測試還是效能測試,都會接觸到引數化這個詞。它是指把程式碼中的固定資料(硬編碼)定義成變數,讓每次執行時資料不一樣,固定資料變為動態資料。動態資料的來源是變數、資料庫、外部檔案等。動態資料的型別一般是常量的字串,也可以是函式,比如JMeter的函式助手,也可以是依賴注入,比如pytest的fixture。
依賴注入的fixture
“依賴注入是控制反轉(IoC, Inversion of Control)的一種技術形式”,這句話出自維基百科,我也不知道什麼意思,畫個圖簡單表達下:
意思是,給client
一個injector
,client
不需要做什麼,就能用到service
。
pytest的fixture實現了依賴注入,允許我們在不修改測試程式碼的情況下,引入fixture來額外新增一些東東。
對於url
來說,域名是需要做引數化的,不同環境域名不同,所以tep把它做成了fixture,通過函式引數引入:
from tep.client import request
from tep.fixture import *
def test(url): # 引入fixture
response = request("post",
url=url("/api/users/login"),
headers={"Content-Type": "application/json"},
json={
"username": "admin",
"password": "123456",
}
)
assert response.status_code < 400
tep.fixture.url
定義如下:
@pytest.fixture(scope="session")
def url(env_vars):
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain + uri
return domain_and_uri
如果一眼就看懂了,恭喜你,如果一眼就看懵了,沒關係。我會花功夫把它講明白,它很關鍵!
把fixture當變數看
雖然從定義上看,fixture是用def
關鍵字定義的函式,但是理解上把它看做變數就可以了。比如:
import pytest
@pytest.fixture
def name():
return "dongfanger"
一般函式的用法是函式名加小括號,通過name()
才能得到"dongfanger"
。fixture不一樣,以上定義可以理解為:
name = "dongfanger"
把"dongfanger"
賦值給name
,fixture名 = return值。通過變數name
就得到"dongfanger"
了。
既然是變數,那麼就能隨便賦值,str
、function
、class
、object
都行。比如在fixture內部定義個函式:
import pytest
@pytest.fixture
def who():
def get_name():
return "dongfanger"
return get_name
理解為把函式名get_name
賦值給fixture名變數:
who = get_name
get_name
是個函式名,需要加小括號get_name()
才能得到"dongfanger"
。who
也必須通過who()
才能得到"dongfanger"
。再看tep.fixture.url
是不是清楚些了:
@pytest.fixture(scope="session")
def url(env_vars):
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain + uri
return domain_and_uri
理解為把函式名domain_and_uri
賦值給fixture名變數:
url = domain_and_uri
使用時通過url("/api")
得到域名和uri拼接後的結果。
第2行的def url(env_vars):
也有一個引數env_vars
,接下來繼續解釋。
fixture引數是其他fixture
fixture的引數只能是其他fixture。比如:
import pytest
@pytest.fixture
def chinese_name():
return "東方er"
@pytest.fixture
def english_name(chinese_name):
return "dongfanger"
呼叫english_name
,pytest會先執行引數裡的其他fixture chinese_name
,然後執行自己english_name
。
如果把tep.fixture.url
拆成兩步來看,就很清晰了,第一步:
@pytest.fixture(scope="session")
def url(env_vars):
func = None
return func
第二步:
@pytest.fixture(scope="session")
def url(env_vars):
func = None
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain + uri
func = domain_and_uri
return func
環境變數
tep.fixture.url
的引數是另外一個fixture env_vars
環境變數,它的定義如下:
from tep.fixture import *
@pytest.fixture(scope="session")
def env_vars(config):
class Clazz(TepVars):
env = config["env"]
"""Variables define start"""
# Environment and variables
mapping = {
"qa": {
"domain": "https://qa.com",
},
"release": {
"domain": "https://release.com",
}
# Add your environment and variables
}
# Define properties for auto display
domain = mapping[env]["domain"]
"""Variables define end"""
return Clazz()
只看中間註釋"""Variables define start"""
到"""Variables define end"""
部分即可。url
引數化的域名就在這裡,mapping
字典建立了環境和變數之間的對映,根據不同的環境key,獲取不同的變數value。
config
fixture的作用是讀取conf.yaml
檔案裡面的配置。
引數化的方式很多,JMeter提供了4種引數化方式,tep的fixture env_vars
借鑑了JMeter的使用者自定義變數:
env_vars.put()
和env_vars.get()
借鑑了JMeter BeanShell的vars.put()
和vars.get()
。
例項:測試多個網址
講到最後,形成了思路,通過實際的例子,看看環境變數、fixtures、用例是怎麼用起來的,加深下印象。假如qa
環境有2個網址,學校端和機構端,指令碼都需要用到。
第一步修改env_vars
,編輯fixture_env_vars.py
:
"""Variables define start"""
# Environment and variables
mapping = {
"qa": {
"domain": "https://qa.com",
"domain_school": "https://school.qa.com", # 新增
"domain_org": "https://org.qa.com" # 新增
},
"release": {
"domain": "https://release.com",
"domain_school": "https://school.release.com" # 新增
"domain_org": "https://org.release.com" # 新增
}
# Add your environment and variables
}
# Define properties for auto display
domain = mapping[env]["domain"]
domain_school = mapping[env]["domain_school"] # 新增
domain_org = mapping[env]["domain_org"] # 新增
"""Variables define end"""
新增了6行程式碼,定義了env_vars.domain_school
和env_vars.domain_org
。
第二步定義fixtures,新建fixture_url.py
:
@pytest.fixture(scope="session")
def url_school(env_vars):
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain_school + uri
return domain_and_uri
@pytest.fixture(scope="session")
def url_org(env_vars):
def domain_and_uri(uri):
if not uri.startswith("/"):
uri = "/" + uri
return env_vars.domain_org + uri
return domain_and_uri
參照tep.fixture.url
,修改env_vars.domain
為env_vars.domain_school
和env_vars.domain_org
,新增了2個fixture url_school
和url_org
。
更進一步,也許會定義fixture login_school
和login_org
,靈活選擇。
小結
本文循序漸進的講解了tep環境變數、fixtures和用例之間的關係,重點對tep.fixture.url
進行了解釋,只要理解了它,整體關係就很清楚了。之所以要用fixture,原因一是多人協作共享,我們需要用別人寫好的函式,複用返回值,有些同學習慣定義函式引數,引數不變還好,萬一哪天改了,別人引用的用例會全部報錯,fixture很好的限制了這一點,它預設是不能傳參的,雖然可以通過定義內部函式來實現傳參,但是並不推薦這麼做,寧願增加冗餘程式碼,定義多個fixture,也比程式碼耦合度高好一些。原因二是import
的問題,pytest會自動查詢conftest.py
裡的fixture,tep會進一步自動查詢fixtures
下的fixture匯入到conftest.py
,不需要import
就能使用,減少了import
程式碼,避免了可能會出現的迴圈匯入問題。