Combate de pruebas y desarrollo de software | Registra varios pozos pisados al escribir decoradores

Este artículo es una reproducción de las notas de estudio de indeyo Xiao Linger, un excelente estudiante del Instituto de Pruebas de Hogwarts, el enlace original:
http://qrcode.testing-studio.com/f?from=51cto&url=https://ceshiren.com/tag/ % E7% B2% BE% E5% 8D% 8E% E5% B8% 96 Indique la fuente de la reimpresión

antecedentes

Decorator es un azúcar sintáctico muy útil en Python, que puede reducir la escritura de mucho código repetitivo.
Hace poco aprendí sobre el manejo de excepciones del marco de automatización de aplicaciones. Hay una cierta cantidad de código repetitivo. Lo voy a usar como tema para practicar el decorador.
Grabemos el camino del decorador.

坑 1 : Sugerencia: asegúrese de que sus módulos / paquetes de prueba tengan nombres Python válidos.

Mensaje de error

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)

la razón

Existe una relación de llamada mutua entre el archivo exception.py y el archivo base_page.py.

solución

Coloque la información de importación del paquete de la llamada al bucle en la función. Siempre que la información de referencia de una de las partes se coloque en la función, no es necesario colocar ambas partes.
Solo cambié el archivo exception.py, base_page.py permaneció sin cambios.

excepción.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: índice de tupla fuera de rango

Mensaje de error

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

la razón

Es muy fácil cometer este error al escribir decoradores por primera vez.

def decorator(func):
    def magic(*args, **kwargs):
        _self: BasePage = args[0]
        ...
        return magic(*args, **kwargs)
    # 这里的问题!!!不应该返回函数调用,要返回函数名称!!!
    return magic()

¿Por qué la llamada a la función de retorno informa este error?
Porque cuando se llama a la función magic (), no se pasa ningún parámetro, pero los parámetros de entrada se citan en magic (), entonces args no tiene valor, por lo que args [0] naturalmente no está disponible.

solución

Solo quita los soportes

def decorator(func):
    def magic(*args, **kwargs):
        _self: BasePage = args[0]
        ...
        return magic(*args, **kwargs)
    # 返回函数名,即函数本身
    return magic

Pit 3: el manejo de excepciones solo se ejecuta una vez y la automatización no puede continuar

Mensaje de error

Principalmente varias excepciones en el proceso de localización de elementos, como NoSuchElementException, TimeoutException y otros problemas comunes.

la razón

Después del manejo de excepciones, la lógica recursiva se escribe incorrectamente. return func () ejecuta func () y salta de la lógica de manejo de excepciones, por lo que el manejo de excepciones se ejecuta solo una vez.
La forma correcta de escribir es return magic ().
Se siente como un error cometido por el decorador Xiaobai de nuevo ... emmm ...

solución

Para intuir, se ha filtrado el código sin importancia y el código lógico de manejo de excepciones se publicará al final del artículo.

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

Pit 4: ¿Cómo reutilizar el controlador?

problema

Cuando intenté escribir un decorador por primera vez, encontré un problema.
Find_elements es necesario en el decorador ¿De dónde viene el controlador? ¿Y cómo obtener las variables privadas error_max y error_count de BasePage? ¿Crear un objeto BasePage? Luego, ¿pasar el controlador por la función func?
El controlador de func es privado y no se puede llamar externamente (resulta que puede ser emmm ...).
Traté de hacer públicas las variables anormalmente relacionadas, pero fue inútil y todavía no pude resolver el problema de llamar a find_elements.

solución

El enfoque de Sihan es crear una variable self en el decorador y tomar args [0], que es el primer parámetro self de la función func.
_self: BasePage = args [0] Esta simple oración respondió con éxito a todas mis preguntas.
En la definición de función de clase, self representa la clase en sí, por lo que puede obtener el atributo ._driver y llamar a find_elements.

坑 5 : AttributeError

Informar un error cuando esté a punto de hacer clic después de encontrar el elemento

Mensaje de error

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

la razón

Después de mirar la función de búsqueda, después de encontrar el elemento, devuelve el elemento en sí

@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

Es decir, el decorador se equivoca

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

solución

La llamada a la función debe ser devuelta en el decorador, o el decorador devorará el retorno de la función en sí.

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

Pensando: Al escribir decoradores, varios retornos parecían un poco mareados. Cada función se puede devolver, ¿qué significa? ? ?

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

Implementación completa del decorador

excepción.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

Experiencia de aprendizaje

Es mejor no leer primero la explicación de Sihan y escribir al decorador de acuerdo con su propio entendimiento, para que el efecto de aprendizaje sea mejor.
Traté de resolver el problema cuando encontré el problema, y ​​el pozo en el que pisé era impresionante.
Realmente no hay ninguna pista para referirse a la solución de Sihan nuevamente, y entonces habrá una repentina sensación de claridad.
He pisado estos pozos en este momento, si hay alguna omisión, agréguela ~

Este artículo es una reproducción de las notas de estudio de indeyo Xiao Linger, un excelente estudiante del Instituto de Pruebas de Hogwarts, el enlace original:
http://qrcode.testing-studio.com/f?from=51cto&url=https://ceshiren.com/tag/ % E7% B2% BE% E5% 8D% 8E% E5% B8% 96 Indique la fuente de la reimpresión

Supongo que te gusta

Origin blog.51cto.com/14293469/2561925
Recomendado
Clasificación