この記事はindeyo、ホグワーツのテスト研究所、元のリンクの優秀な学生の研究ノートから再生されます。
http://qrcode.testing-studio.com/f?from=51cto&url=https://ceshiren.com/tag/ %E7%B2%BE%E5%8D%8E%E5%B8%96再印刷のソースを示してください
バックグラウンド
デコレータは、Pythonで非常に便利な構文シュガーであり、繰り返しの多いコードの記述を減らすことができます。
最近、アプリ自動化フレームワークの例外処理について学びました。繰り返しコードがある程度あります。これをテーマとして使用して、デコレータを練習します。
デコレータのパスを記録しましょう。
坑1:ヒント:テストモジュール/パッケージに有効なPython名があることを確認してください。
エラーメッセージ
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ファイルの間には相互呼び出し関係があります。
解決
ループ呼び出しのパッケージインポート情報を関数に入れます。片方の参照情報が機能に配置されている限り、両側を配置する必要はありません。
exception.pyファイルを変更しただけで、base_page.pyは変更されていません。
exception.py
def exception_handle(func):
def magic(*args, **kwargs):
# 防止循环调用报错
from test_appium.page.base_page import BasePage
# 获取BasePage实例对象的参数self,这样可以复用driver
_self: BasePage = args[0]
...
坑2:IndexError:タプルインデックスが範囲外です
エラーメッセージ
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)
# 这里的问题!!!不应该返回函数调用,要返回函数名称!!!
return magic()
return関数呼び出しがこのエラーを報告するのはなぜですか?
magic()関数が呼び出されたとき、パラメーターは渡されませんが、入力パラメーターはmagic()で引用されます。現時点では、argsには値がないため、args [0]は当然使用できません。
解決
ブラケットを外すだけ
def decorator(func):
def magic(*args, **kwargs):
_self: BasePage = args[0]
...
return magic(*args, **kwargs)
# 返回函数名,即函数本身
return magic
ピット3:例外処理は1回だけ実行され、自動化を続行できません
エラーメッセージ
NoSuchElementException、TimeoutException、その他の一般的な問題など、要素の検索プロセスにおける主にさまざまな例外。
理由
例外処理後、再帰ロジックが正しく記述されていません。return func()はfunc()を実行し、例外処理ロジックからジャンプするため、例外処理は1回だけ実行されます。
正しい書き方はreturnmagic()です。
デコレーターのシャオバイがまた間違えたような気がします...えーと...
解決
直感的には、重要でないコードはフィルタリングされており、例外処理ロジックコードは記事の最後にリリースされます。
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()
# 异常处理结束,递归继续查找元素
# 这里之前写成了return func(*args, **kwargs),所以异常只执行一次!!!!!
return magic(*args, **kwargs)
raise e
return magic
ピット4:ドライバーを再利用する方法は?
問題
私が最初にデコレータを書き込もうとしたとき、私は問題を見つけました。
デコレータにはFind_elementsが必要ですが、ドライバはどこから来たのですか?そして、BasePageのプライベート変数error_maxとerror_countを取得する方法は?BasePageオブジェクトを作成しますか?次に、ドライバーをfunc関数に渡しますか?
funcのドライバーはプライベートであり、外部から呼び出すことはできません(emmmである可能性があります...)。
異常に関連する変数を公開しようとしましたが、役に立たず、find_elementsの呼び出しの問題を解決できませんでした。
解決
Sihanのアプローチは、デコレータで自己変数を作成し、関数funcの最初のパラメータselfであるargs [0]を取得することです。
_self:BasePage = args [0]この単純な文は、私のすべての質問にうまく答えました。
クラス関数定義では、selfはクラス自体を表すため、._ driver属性を取得して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関数を確認した後、要素を検索した後、要素自体を返します。
@exception_handle
def find(self, locator, key=None):
logging.info(locator)
logging.info(key)
# 定位符支持元组格式和两个参数格式
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:
# 这里只是执行了函数,但是没有return
func(*args, **kwargs)
# 弹窗等异常处理逻辑
except Exception as e:
raise e
return magic
解決
関数呼び出しはデコレータで返される必要があります。そうでない場合、関数自体の戻りはデコレータによって食べられます。
def exception_handle(func):
def magic(*args, **kwargs):
_self: BasePage = args[0]
try:
# return函数执行结果
return func(*args, **kwargs)
# 弹窗等异常处理逻辑
except Exception as e:
raise e
return magic
思考:デコレータを書くとき、さまざまなリターンが少し目がくらむように見えました。各関数を返すことができますが、それはどういう意味ですか???
def exception_handle(func):
def magic(*args, **kwargs):
_self: BasePage = args[0]
try:
# 第1处 return:传递func()函数的返回值。如果不写,原有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()
# 异常处理结束,递归继续查找元素
# 第2处 return:递归调用装饰后的函数。magic()表示新函数,func()表示原函数,不可混淆
return magic(*args, **kwargs)
raise e
# 第3处 return:返回装饰后的函数,装饰器语法。不能返回函数调用magic()
return magic
デコレータの完全な実装
exception.py
import logging
logging.basicConfig(level=logging.INFO)
def exception_handle(func):
def magic(*args, **kwargs):
# 防止循环调用报错
from test_appium.page.base_page import BasePage
# 获取BasePage实例对象的参数self,这样可以复用driver
_self: BasePage = args[0]
try:
# logging.info('error count is %s' % _self._error_count)
result = func(*args, **kwargs)
_self._error_count = 0
# 返回调用函数的执行结果,要不然返回值会被装饰器吃掉
return result
# 弹窗等异常处理逻辑
except Exception as e:
# 如果超过最大异常处理次数,则抛出异常
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:
# 出现弹窗,点击掉
elements[0].click()
# 弹窗点掉后,重新查找目标元素
return magic(*args, **kwargs)
# 弹窗也没有出现,则抛出异常
logging.warning("no error is found")
raise e
return magic
学習経験
最初にシハンの説明を読まないで、あなた自身の理解に従ってデコレータを書くのが最善です。そうすれば、学習効果が最高になります。
問題に遭遇したときに問題を解決しようとしましたが、踏んだピットが印象的でした。
Sihanの解決策をもう一度参照する手がかりは実際にはありません。そうすると、突然の明確さの感覚が生まれます。
現在、これらのピットを踏んでいますが、抜けがあれば追加してください〜
この記事はindeyo、ホグワーツのテスト研究所、元のリンクの優秀な学生の研究ノートから再生されます。
http://qrcode.testing-studio.com/f?from=51cto&url=https://ceshiren.com/tag/ %E7%B2%BE%E5%8D%8E%E5%B8%96再印刷のソースを示してください