1. 程式人生 > 實用技巧 >37 web自動化實戰三 前置後置條件 (fixture yield知識點 conftest.py )

37 web自動化實戰三 前置後置條件 (fixture yield知識點 conftest.py )

目錄

80節

1.前置後置條件----測試夾具fixture

2.conftest.py 檔案

3.重執行機制

1.測試夾具fixture

背景:之前的測試程式碼中,並沒有實現測試完成後,關閉瀏覽器操作的行為。

這麼多年測試經驗的你,不難理解:開啟瀏覽器、關閉瀏覽器,可以看做是前置條件和後置條件。

那麼在unittest中的setUp() 、tearDown()在這裡還能不能用呢?答案是否定的,因為pytest的類在使用fixture是不相容unittest的,不能繼承unittest.TestCase,就無法使用setUp() 、tearDown()。(setUp() 、tearDown()是unittest框架裡的功能啦)

智慧的pytest,提供了一套系統,作為前置、後置條件---------->測試夾具

申明測試夾具實現語法:

①定義的普通函式(瀏覽器函式(這裡定義函式名是driver))上,新增@pytest.fixture(),申明這是個測試夾具

②把return driver改為 :yield driver ,實現前置、後置條件(yield前面的就是前置條件,後面的就是後置條件)

yield可以理解為return,有返回值------區別是,執行yield,其後面的程式碼會繼續執行,執行return,其後面的程式碼不會執行

③後置清理語句:driver.quit()

測試夾具如何使用:

將測試夾具的定義的函式名,作為引數,放到測試用例的函式中------>def test_login_error(self,test_info,driver):

程式執行順序:

測試用例的方法中要用到driver,呼叫driver這個測試夾具,再執行測試用例的方法,最後再執行後置清理)

程式碼實現如下:

"""登入功能的測試用例"""

import pytest
from middware.handler import HandlerMiddle
from config.config import Wait_time

data = HandlerMiddle.excel.read_data("login")

@pytest.fixture()
def driver():
    from selenium import webdriver

    
##前置條件 # 1.開啟瀏覽器 driver = webdriver.Chrome() # 設定隱性等待 等待的時間就可以放在config中,直接引數呼叫 ##方法一:放在yaml中 wait_time = HandlerMiddle.yaml_data["selenium"]["wait_time"] ##方法二、放在config.py中 # wait_time = Wait_time driver.implicitly_wait(wait_time) yield driver #後置q清理 driver.quit() @pytest.mark.login class TestLogin(): """登入功能的測試類""" @pytest.mark.smoke @pytest.mark.error @pytest.mark.parametrize("test_info",data) def test_login_error(self,test_info,driver):
  。
  。
  。

知識拓展:yield

1).生成器generator的概念

在Python中,一邊迴圈一邊計算的機制,稱為生成器:generator。

2).為什麼要用生成器? 

列表所有資料都在記憶體中,如果有海量資料的話將會非常耗記憶體。

如:僅僅需要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。

如果列表元素按照某種演算法推算出來,那我們就可以在迴圈的過程中不斷推算出後續的元素,這樣就不必建立完整的list,從而節省大量的空間。

簡單一句話:我又想要得到龐大的資料,又想讓它佔用空間少,那就用生成器!

3).怎麼建立生成器?-------2種方法

①只要把一個列表生成式的[]改成(),就建立了一個generator

>>> My_list = [x * x for x in range(10)]
>>> My_list
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]    #返回的是一個存放資料列表,資料量大,會很佔記憶體
>>> gen = (x * x for x in range(10))
>>> gen   #返回一個生成器,需要哪些資料,在迴圈的過程中不斷推算出後續的元素,節省空間
<generator object <genexpr> at 0x1022ef630>

L是一個My_list,而gen是一個generator

帶有 yield 關鍵字的函式,那麼這個函式就不再是一個普通函式,而是一個generator。呼叫函式就是建立了一個生成器(generator)物件。

舉例,yield實現斐波那契數列

def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b      # 使用 yield
        a, b = b, a + b 
        n = n + 1
 
for n in fab(5): 
    print n

如果這裡的yield用 print 列印數字,會導致該函式可複用性較差,因為 fab 函式返回 None,其他函式無法獲得該函式生成的數列。

呼叫 fab(5) 不會執行 fab 函式,而是返回一個 iterable 物件!在 for 迴圈執行時,每次迴圈都會執行 fab 函式內部的程式碼,執行到 yield b 時,fab 函式就返回一個迭代值,下次迭代時,程式碼從 yield b 的下一條語句繼續執行,而函式的本地變數看起來和上次中斷執行前是完全一樣的,於是函式繼續執行,直到再次遇到 yield。

“圓規”正傳,回到fixture~~~~~~~~~~~

在unittest中,有setUpClass,tearDownClass,那麼在pytest中的fixture應該如何實現呢??

在@pytest.fixture()中新增引數scope=“class”,在執行程式時,每個測試用例的類,只會執行一次。

※:pytest執行程式時,捕獲所有的輸出,加-s (不加-s的話,輸出資訊不會顯示)

  即pytest -m "tagname" -s

舉個栗子:

不加scope=“class”(預設scope="function")

import pytest
#申明測試夾具
@pytest.fixture()
def fixt():
    #前置條件
    print("這是前置條件")

    yield

    #後置清理
    print("這是後置條件")

@pytest.mark.demo
class TestDemo():
    """測試用例類"""

    def test_demo1(self,fixt):
        pass

    def test_demo2(self,fixt):
        pass

結果:兩個用例,執行了2次

加scope=“class”

import pytest
#申明測試夾具
@pytest.fixture(scope="class")
def fixt():
    #前置條件
    print("這是前置條件")

    yield

    #後置清理
    print("這是後置條件")

@pytest.mark.demo
class TestDemo():
    """測試用例類"""

    def test_demo1(self,fixt):
        pass

    def test_demo2(self,fixt):
        pass

結果:一個測試類,只會執行一次測試夾具

除了上面2種形式,還有scope=“module”型別的

即,每個模組裡面,即使有多個class,也只會執行一次前置、後置條件。

程式碼實現如下:

import pytest
#申明測試夾具 @pytest.fixture(scope
="function") def function_fixt(): #前置條件 print("這是function級別前置條件") yield #後置清理 print("這是function級別後置條件") #申明測試夾具 @pytest.fixture(scope="class") def class_fixt(): #前置條件 print("這是class級別前置條件") yield #後置清理 print("這是class級別後置條件")
#申明測試夾具 @pytest.fixture(scope
="module") def module_fixt(): #前置條件 print("這是module級別前置條件") yield #後置清理 print("這是module級別後置條件") @pytest.mark.demo class TestDemo(): """測試用例類""" def test_demo1(self,function_fixt,class_fixt,module_fixt): pass def test_demo2(self,function_fixt,class_fixt,module_fixt): pass @pytest.mark.demo class TestDemo2(): def test_demo3(self,function_fixt,class_fixt,module_fixt): pass

執行pytest -m "demo" -s,結果如下:

在實際的測試中,一個class測試類,新增一個class級別的測試夾具;不需要每個用例都開啟、關閉一次瀏覽器,這樣會降低測試效率。

fixture的基本內容,到這裡全部描述完了。

下面再考慮個問題,在實際的測試工作中,註冊、登入,取現等操作,每執行一次,就要開啟/關閉一次瀏覽器,會比較麻煩,所以應該將fixture放在一個公共的檔案中,讓別的模組去呼叫即可。---conftest.py

2.conftest.py 檔案

在專案路徑下,直接新建一個py檔案,命名為conftest.py。------注意:這個名字是固定的,作用:儲存所有的fixture

①為什麼不將測試夾具放在common模組中去?

在因為driver這個物件,在測試用例中會經常的使用,放在common中要經常進行模組的import操作;而conftest是pytest裡面有一個比較智慧的地方,不需要去匯入,執行程式的時候,會直接去conftest.py檔案去找driver,找到了就繼續執行,找不到就報錯。省了很多匯入的操作,也避免了因為匯入模組可能存在路徑錯誤的隱患。

程式碼實現:

"""固定檔名conftest.py,儲存所有的測試夾具fixture"""
import pytest
from middware import handler

@pytest.fixture()
def driver():
    from selenium import webdriver

    ##前置條件
    #開啟瀏覽器
    driver = webdriver.Chrome()

    # 設定隱性等待 等待的時間就可以放在config中,直接引數呼叫
    wait_time = handler.HandlerMiddle.yaml_data["selenium"]["wait_time"]
    driver.implicitly_wait(wait_time)

    yield driver

    #後置清理
    driver.quit()

test_login.py用例(無需匯入conftest.py):

"""登入功能的測試用例"""

import pytest
from middware.handler import HandlerMiddle

data = HandlerMiddle.excel.read_data("login")

@pytest.mark.login
class TestLogin():
    """登入功能的測試類"""

@pytest.mark.smoke @pytest.mark.error @pytest.mark.parametrize("test_info",data) def test_login_error(self,test_info,driver): """登入失敗測試步驟 1.開啟瀏覽器 2.訪問登入頁面 3.元素定位+元素操作,輸入使用者名稱和密碼,點選登入 4.通過獲取頁面內容得到實際結果,進行斷言 :return: """ #2.訪問登入頁面 url = "http://120.78.128.25:8765/Index/login.html" driver.get(url) #3.元素定位+元素操作,輸入使用者名稱和密碼,點選登入 driver.find_element_by_name("phone").send_keys(eval(test_info["data"])["username"]) #定位輸入手機號為空 driver.find_element_by_name("password").send_keys(eval(test_info["data"])["password"])#定位輸入的密碼為空 driver.find_element_by_class_name("btn-special").click() #4.通過獲取頁面內容得到實際結果,進行斷言 #實際結果是在頁面上的提示,再次進行定位 actual_result = driver.find_element_by_class_name("form-error-info").text expected_result = test_info["expected_result"] #斷言 assert actual_result == expected_result

執行run.py(同上面的run.py)即可完成測試login_error的用例。

3.重執行機制 -----多次執行

背景:為什麼使用重執行機制?

對於web自動化測試中,可能因為網速的問題導致測試不通過,所以要進行重新執行。(多次執行後,每次都失敗,可能是bug)

①安裝:pip install pytest-rerunfailures

②執行:pytest --reruns N (N表示rerun的次數)

pytest --reruns N --reruns-delay M (M表示每執行一次,中間的間隔延遲時間)

舉個栗子:

import pytest
@pytest.fixture(scope="function")
def function_fixt():
    #前置條件
    print("這是function級別前置條件")
    yield
    #後置清理
    print("這是function級別後置條件")

@pytest.mark.demo
class TestDemo():
    """測試用例類"""

    def test_demo1(self,function_fixt):
        assert 1==2  #斷言不通過,進行rerun

    def test_demo2(self,function_fixt):
        pass

執行pytest -m "demo" --reruns 3,結果如下:

執行:pytest -m "demo" --reruns 3 --reruns-delay 5 的結果如下:

rerun3次,加首次執行,一共會執行4次。