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次。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM