tep環境變數、fixtures、用例三者之間的關係

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="//qa.com/api/users/login",
            headers={"Content-Type": "application/json"},
            json={
                "username": "admin",
                "password": "123456",
            }
        )
    assert response.status_code < 400

請求介面//qa.com/api/users/login,斷言響應狀態碼小於400。問題來了:url固定,假如需要切換兩個環境qarelease,該怎麼辦?

參數化

無論是做自動化測試還是性能測試,都會接觸到參數化這個詞。它是指把程式碼中的固定數據(硬編碼)定義成變數,讓每次運行時數據不一樣,固定數據變為動態數據。動態數據的來源是變數、資料庫、外部文件等。動態數據的類型一般是常量的字元串,也可以是函數,比如JMeter的函數助手,也可以是依賴注入,比如pytest的fixture。

依賴注入的fixture

「依賴注入是控制反轉(IoC, Inversion of Control)的一種技術形式」,這句話出自維基百科,我也不知道什麼意思,畫個圖簡單表達下:

意思是,給client一個injectorclient不需要做什麼,就能用到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"了。

既然是變數,那麼就能隨便賦值,strfunctionclassobject都行。比如在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": "//qa.com",
            },
            "release": {
                "domain": "//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": "//qa.com",
                "domain_school": "//school.qa.com",  # 新增
                "domain_org": "//org.qa.com"  # 新增
            },
            "release": {
                "domain": "//release.com",
                "domain_school": "//school.release.com"  # 新增
                "domain_org": "//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_schoolenv_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.domainenv_vars.domain_schoolenv_vars.domain_org,新增了2個fixture url_schoolurl_org

更進一步,也許會定義fixture login_schoollogin_org,靈活選擇。

小結

本文循序漸進的講解了tep環境變數、fixtures和用例之間的關係,重點對tep.fixture.url進行了解釋,只要理解了它,整體關係就很清楚了。之所以要用fixture,原因一是多人協作共享,我們需要用別人寫好的函數,復用返回值,有些同學習慣定義函數參數,參數不變還好,萬一哪天改了,別人引用的用例會全部報錯,fixture很好的限制了這一點,它默認是不能傳參的,雖然可以通過定義內部函數來實現傳參,但是並不推薦這麼做,寧願增加冗餘程式碼,定義多個fixture,也比程式碼耦合度高好一些。原因二是import的問題,pytest會自動查找conftest.py里的fixture,tep會進一步自動查找fixtures下的fixture導入到conftest.py,不需要import就能使用,減少了import程式碼,避免了可能會出現的循環導入問題。

Tags: