背景
裝飾器是python里面一個很有用的語法糖( Syntactic Sugar),可以減少大量重復(fù)代碼的編寫首懈。
剛好最近學(xué)習(xí)了app自動化框架的異常處理菱农,存在一定重復(fù)代碼炕淮,準備當作題材缠借,拿來練習(xí)一下裝飾器干毅。
下面記錄一下裝飾器的踩坑之路。
坑 1:Hint: make sure your test modules/packages have valid Python names.
報錯信息
test_market.py:None (test_market.py)
ImportError while importing test module 'D:\project\Hogwarts_11\test_appium\testcase\test_market.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
test_market.py:9: in <module>
from test_appium.page.app import App
..\page\app.py:12: in <module>
from test_appium.page.base_page import BasePage
..\page\base_page.py:16: in <module>
from test_appium.utils.exception import exception_handle
..\utils\exception.py:11: in <module>
from test_appium.page.base_page import BasePage
E ImportError: cannot import name 'BasePage' from 'test_appium.page.base_page' (D:\project\Hogwarts_11\test_appium\page\base_page.py)
原因
exception.py 文件和 base_page.py 文件之間存在相互調(diào)用關(guān)系泼返。
解決方案
把循環(huán)調(diào)用的包引入信息放在函數(shù)內(nèi)硝逢。只要一方的引用信息放在函數(shù)里即可,不必兩邊都放绅喉。
我只在 exception.py 文件里改了渠鸽,base_page.py 保持不變。
exception.py
def exception_handle(func):
def magic(*args, **kwargs):
# 防止循環(huán)調(diào)用報錯
from test_appium.page.base_page import BasePage
# 獲取BasePage實例對象的參數(shù)self柴罐,這樣可以復(fù)用driver
_self: BasePage = args[0]
...
坑 2:IndexError: tuple index out of range
報錯信息
test_search.py:None (test_search.py)
test_search.py:11: in <module>
from test_appium.page.app import App
..\page\app.py:12: in <module>
from test_appium.page.base_page import BasePage
..\page\base_page.py:52: in <module>
class BasePage:
..\page\base_page.py:74: in BasePage
def find(self, locator, key=None):
..\page\base_page.py:50: in exception_handle
return magic()
..\page\base_page.py:24: in magic
_self: BasePage = args[0]
E IndexError: tuple index out of range
原因
第一次寫裝飾器真的很容易犯這個錯徽缚,來看下哪里寫錯了
def decorator(func):
def magic(*args, *kwargs):
_self: BasePage = args[0]
...
return magic(args, **kwargs)
# 這里的問題!8锿馈凿试!不應(yīng)該返回函數(shù)調(diào)用,要返回函數(shù)名稱K浦ァ:焓 !
return magic()
為什么返回函數(shù)調(diào)用會報這個錯呢国觉?
因為調(diào)用magic()函數(shù)的時候吧恃,沒有傳參進去,但是magic()里面引用了入?yún)⒙榫鳎@時args沒有值痕寓,自然就取不到args[0]了。
解決方案
去掉括弧就好了
def decorator(func):
def magic(*args, *kwargs):
_self: BasePage = args[0]
...
return magic(args, **kwargs)
# 返回函數(shù)名蝇闭,即函數(shù)本身
return magic
坑 3:異常處理只執(zhí)行了1次呻率,自動化無法繼續(xù)
報錯信息
主要是定位元素過程中出現(xiàn)的各種異常,NoSuchElementException呻引、TimeoutException等常見問題礼仗。
原因
異常處理后,遞歸邏輯寫得不對逻悠。return func()執(zhí)行了func()元践,跳出了異常處理邏輯,所以異常處理只執(zhí)行一次童谒。
正確的寫法是 return magic()单旁。
感覺又是裝飾器小白容易犯的錯誤…emmm…
解決方案
為了直觀,已過濾不重要代碼饥伊,異常處理邏輯代碼會在文末放出象浑。
def exception_handle(func):
def magic(*args, *kwargs):
_self: BasePage = args[0]
try:
return func(args, kwargs)
# 彈窗等異常處理邏輯
except Exception as e:
for element in _self._black_list:
elements = _self._driver.find_elements(element)
if len(elements) > 0:
elements[0].click()
# 異常處理結(jié)束蔫饰,遞歸繼續(xù)查找元素
# 這里之前寫成了return func(args, *kwargs),所以異常只執(zhí)行一次S洳颉Bㄓ酢!r嚼埂杖剪!
return magic(args, **kwargs)
raise e
return magic
坑 4:如何復(fù)用driver?
問題
自己剛開始嘗試寫裝飾器的時候外盯,發(fā)現(xiàn)一個問題。
裝飾器內(nèi)需要用到 find_elements翼雀,這時候 driver 哪里來饱苟?還有 BasePage 的私有變量 error_max 和 error_count 怎么獲取到呢?創(chuàng)建一個 BasePage 對象狼渊?然后通過 func 函數(shù)來傳遞 driver 箱熬?
func的driver是私有的,不能外部調(diào)用(事實證明可以emmm…)狈邑。
我嘗試把異常相關(guān)的變量做成公共的城须,沒用,還是無法解決find_elements的調(diào)用問題米苹。
解決方案
思寒的做法是糕伐,在裝飾器里面創(chuàng)建一個self變量,取args[0]蘸嘶,即函數(shù)func的第一個入?yún)elf良瞧。
_self: BasePage = args[0]這一簡單的語句成功解答了我所有的疑問。
類函數(shù)定義里面 self 代表類自身训唱,因此可以獲取 ._driver 屬性褥蚯,從而調(diào)用 find_elements。
坑 5:AttributeError
找到元素后况增,準備點擊的時候報錯
報錯信息
EINFO:root:('id', 'tv_search')
INFO:root:None
INFO:root:('id', 'image_cancel')
INFO:root:('id', 'tv_agree')
INFO:root:('id', 'tv_search')
INFO:root:None
test setup failed
self = <test_appium.testcase.test_search.TestSearch object at 0x0000018946B70940>
def setup(self):
self.page = App().start().main().goto_search()
test_search.py:16:
self = <test_appium.page.main.MainPage object at 0x0000018946B70780>
def goto_search(self):
self.find(self._search_locator).click()
E AttributeError: 'NoneType' object has no attribute 'click'
..\page\main.py:20: AttributeError
原因
看了下 find 函數(shù)赞庶,找到元素后,有返回元素本身
@exception_handle
def find(self, locator, key=None):
logging.info(locator)
logging.info(key)
# 定位符支持元組格式和兩個參數(shù)格式
locator = locator if isinstance(locator, tuple) else (locator, key)
WebDriverWait(self._driver, 10).until(expected_conditions.visibility_of_element_located(locator))
element = self._driver.find_element(*locator)
return element
那就是裝飾器寫得不對了
def exception_handle(func):
def magic(*args, *kwargs):
_self: BasePage = args[0]
try:
# 這里只是執(zhí)行了函數(shù)澳骤,但是沒有return
func(args, **kwargs)
# 彈窗等異常處理邏輯
except Exception as e:
raise e
return magic
解決方案
要在裝飾器里面返回函數(shù)調(diào)用歧强,要不然函數(shù)本身的返回會被裝飾器吃掉。
def exception_handle(func):
def magic(*args, *kwargs):
_self: BasePage = args[0]
try:
# return函數(shù)執(zhí)行結(jié)果
return func(args, *kwargs)
# 彈窗等異常處理邏輯
except Exception as e:
raise e
return magic
思考:寫裝飾器的時候为肮,各種return看著有點頭暈誊锭。每個函數(shù)里面都可以return,分別代表什么含義呢弥锄?丧靡?蟆沫?
def exception_handle(func):
def magic(args, *kwargs):
_self: BasePage = args[0]
try:
# 第1處 return:傳遞func()函數(shù)的返回值。如果不寫温治,原有return則失效
return func(args, kwargs)
# 彈窗等異常處理邏輯
except Exception as e:
for element in _self._black_list:
elements = _self._driver.find_elements(element)
if len(elements) > 0:
elements[0].click()
# 異常處理結(jié)束饭庞,遞歸繼續(xù)查找元素
# 第2處 return:遞歸調(diào)用裝飾后的函數(shù)。magic()表示新函數(shù)熬荆,func()表示原函數(shù)舟山,不可混淆
return magic(args, **kwargs)
raise e
# 第3處 return:返回裝飾后的函數(shù),裝飾器語法卤恳。不能返回函數(shù)調(diào)用magic()
return magic
裝飾器完整實現(xiàn)
exception.py
import logging
logging.basicConfig(level=logging.INFO)
def exception_handle(func):
def magic(*args, *kwargs):
# 防止循環(huán)調(diào)用報錯
from test_appium.page.base_page import BasePage
# 獲取BasePage實例對象的參數(shù)self累盗,這樣可以復(fù)用driver
_self: BasePage = args[0]
try:
# logging.info('error count is %s' % _self._error_count)
result = func(args, kwargs)
_self._error_count = 0
# 返回調(diào)用函數(shù)的執(zhí)行結(jié)果,要不然返回值會被裝飾器吃掉
return result
# 彈窗等異常處理邏輯
except Exception as e:
# 如果超過最大異常處理次數(shù)突琳,則拋出異常
if _self._error_count > _self._error_max:
raise e
_self._error_count += 1
for element in _self._black_list:
# 用find_elements若债,就算找不到元素也不會報錯
elements = _self._driver.find_elements(element)
logging.info(element)
# 是否找到彈窗
if len(elements) > 0:
# 出現(xiàn)彈窗,點擊掉
elements[0].click()
# 彈窗點掉后拆融,重新查找目標元素
return magic(args, **kwargs)
# 彈窗也沒有出現(xiàn)蠢琳,則拋出異常
logging.warning("no error is found")
raise e
return magic
學(xué)習(xí)心得
最好先不看思寒的講解,根據(jù)自己的理解寫一遍裝飾器镜豹,這樣學(xué)習(xí)效果最好傲须。
遇到問題嘗試解決,踩過的坑印象深刻。
實在沒有頭緒再參考思寒的解法,那時會有一種豁然開朗的感覺松嘶。
目前就踩到這些坑,如有遺漏菇绵,歡迎補充~
(文章來源于霍格沃茲測試學(xué)院)
更多技術(shù)文章可點擊
http://qrcode.testing-studio.com/f?from=jianshu&url=https://ceshiren.com/t/topic/3822