Используйте Pytest+Appium+Allure для автоматического тестирования пользовательского интерфейса.

Я давно занимаюсь UI-автотестированием, прочитал много статей в сообществе TesterHome, собрал много информации в интернете, добавил свой код и многое изучил в процессе отладки. . Я надеюсь, что это может помочь друзьям, которые занимаются автоматическим тестированием пользовательского интерфейса.

Текст в основном знакомит с некоторыми полезными методами и опытом избежания ловушек в процессе реализации автоматического тестирования пользовательского интерфейса с помощью Pytest+Allure+Appium. Статья может быть немного суховата, читатели должны пить больше воды! О(∩_∩)О~

В основном использовал что:

  • Python3
  • Аппий
  • Allure-pytest
  • Питест

Необычный, но полезный подход Appium

1. Appium напрямую выполняет метод оболочки adb

# Appium 启动时增加 --relaxed-security 参数 Appium 即可执行类似adb shell的方法
> appium -p 4723 --relaxed-security


# 使用方法
def adb_shell(self, command, args, includeStderr=False):
 """
    appium --relaxed-security 方式启动
    adb_shell('ps',['|','grep','android'])

    :param command:命令
    :param args:参数
    :param includeStderr: 为 True 则抛异常
    :return:
    """
    result = self.driver.execute_script('mobile: shell', {
 'command': command,
 'args': args,
 'includeStderr': includeStderr,
 'timeout': 5000
 })
 return result['stdout']复制代码

2. Appium напрямую захватывает изображения элементов

element = self.driver.find_element_by_id('cn.xxxxxx:id/login_sign')
pngbyte = element.screenshot_as_png
image_data = BytesIO(pngbyte)
img = Image.open(image_data)
img.save('element.png')
# 该方式能直接获取到登录按钮区域的截图 复制代码

3. Appium напрямую получает лог мобильного терминала

# 使用该方法后,手机端 logcat 缓存会清除归零,从新记录
# 建议每条用例执行完执行一边清理,遇到错误再保存减少陈余 log 输出
# Android
logcat = self.driver.get_log('logcat')

# iOS 需要安装 brew install libimobiledevice 
logcat = self.driver.get_log('syslog')

# web 获取控制台日志
logcat = self.driver.get_log('browser')

c = '\n'.join([i['message'] for i in logcat])
allure.attach(c, 'APPlog', allure.attachment_type.TEXT)
#写入到 allure 测试报告中 复制代码

4. Appium передает файлы напрямую с устройства

# 发送文件
#Android
driver.push_file('/sdcard/element.png', source_path='D:\works\element.png')

# 获取手机文件
png = driver.pull_file('/sdcard/element.png')
with open('element.png', 'wb') as png1:
    png1.write(base64.b64decode(png))

# 获取手机文件夹,导出的是zip文件
folder = driver.pull_folder('/sdcard/test')
with open('test.zip', 'wb') as folder1:
    folder1.write(base64.b64decode(folder))

# iOS
# 需要安装 ifuse
# > brew install ifuse 或者 > brew cask install osxfuse 或者 自行搜索安装方式

driver.push_file('/Documents/xx/element.png', source_path='D:\works\element.png')

# 向 App 沙盒中发送文件
# iOS 8.3 之后需要应用开启 UIFileSharingEnabled 权限不然会报错
bundleId = 'cn.xxx.xxx' # APP名字
driver.push_file('@{bundleId}/Documents/xx/element.png'.format(bundleId=bundleId), source_path='D:\works\element.png') 复制代码

Разница между инициализацией Pytest и Unittest

Многие люди использовали Unitest, Позвольте мне рассказать о некоторых различиях между Pytest и Unitest в методе Hook:

1. Pytest похож на Unitest, но с некоторыми отличиями

Ниже приведен pytest

class TestExample:
 def setup(self):
 print("setup             class:TestStuff")

 def teardown(self):
 print ("teardown          class:TestStuff")

 def setup_class(cls):
 print ("setup_class       class:%s" % cls.__name__)

 def teardown_class(cls):
 print ("teardown_class    class:%s" % cls.__name__)

 def setup_method(self, method):
 print ("setup_method      method:%s" % method.__name__)

 def teardown_method(self, method):
 print ("teardown_method   method:%s" % method.__name__) 复制代码

2. Используйте pytest.fixture()

@pytest.fixture() 
def driver_setup(request):
    request.instance.Action = DriverClient().init_driver('android')
 def driver_teardown():
        request.instance.Action.quit()
    request.addfinalizer(driver_teardown) 复制代码

инициализировать экземпляр

1. Вызов метода Setup_class

class Singleton(object):
 """单例 
    ElementActions 为自己封装操作类"""
 Action = None

 def __new__(cls, *args, **kw):
 if not hasattr(cls, '_instance'):
            desired_caps={}
            host = "http://localhost:4723/wd/hub"
            driver = webdriver.Remote(host, desired_caps)
 Action = ElementActions(driver, desired_caps)
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
            cls._instance.Action = Action
 return cls._instance

class DriverClient(Singleton):
 pass 复制代码

Вызывается в тестовом примере

class TestExample:
 def setup_class(cls):
        cls.Action = DriverClient().Action

 def teardown_class(cls):
        cls.Action.clear()


 def test_demo(self)
        self.Action.driver.launch_app()
        self.Action.set_text('123') 复制代码

2. вызов pytest.fixture()

class DriverClient():

 def init_driver(self,device_name):
        desired_caps={}
        host = "http://localhost:4723/wd/hub"
        driver = webdriver.Remote(host, desired_caps)
 Action = ElementActions(driver, desired_caps)
 return Action



# 该函数需要放置在 conftest.py, pytest 运行时会自动拾取
@pytest.fixture()
def driver_setup(request):
    request.instance.Action = DriverClient().init_driver()
 def driver_teardown():
        request.instance.Action.clear()
    request.addfinalizer(driver_teardown) 复制代码

Вызывается в тестовом примере

#该装饰器会直接引入driver_setup函数
@pytest.mark.usefixtures('driver_setup')
class TestExample:

 def test_demo(self):
        self.Action.driver.launch_app()
        self.Action.set_text('123') 复制代码

Параметризованные методы Pytest

1. Первый метод параметризации метода параметризации декоратора

@pytest.mark.parametrize(('kewords'), [(u"小明"), (u"小红"), (u"小白")])
def test_kewords(self,kewords):
 print(kewords)

# 多个参数    
@pytest.mark.parametrize("test_input,expected", [
 ("3+5", 8),
 ("2+4", 6),
 ("6*9", 42),
]) 
def test_eval(test_input, expected):
 assert eval(test_input) == expected 复制代码

2. Второй метод с использованием хука pytest для пакетного добавления параметризации.

#  conftest.py
def pytest_generate_tests(metafunc):
 """
    使用 hook 给用例加加上参数
    metafunc.cls.params 对应类中的 params 参数

    """
 try:
 if metafunc.cls.params and metafunc.function.__name__ in metafunc.cls.params: ## 对应 TestClass params
          funcarglist = metafunc.cls.params[metafunc.function.__name__]
          argnames = list(funcarglist[0])
          metafunc.parametrize(argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist])
 except AttributeError:
 pass

# test_demo.py
class TestClass:
 """
    :params 对应 hook 中 metafunc.cls.params
    """
 # params = Parameterize('TestClass.yaml').getdata()

    params = {
 'test_a': [{'a': 1, 'b': 2}, {'a': 1, 'b': 2}],
 'test_b': [{'a': 1, 'b': 2}, {'a': 1, 'b': 2}],
 }
 def test_a(self, a, b):
 assert a == b
 def test_b(self, a, b):
 assert a == b 复制代码

Зависимости вариантов использования Pytest

Зависимости можно создавать с помощью библиотеки pytest-dependency.

В случае сбоя варианта использования верхнего уровня последующие варианты использования зависимостей будут пропущены напрямую и могут быть отфильтрованы по классам. Если вам нужно пробежаться по .pyфайлам, вам нужно установить site-packages/pytest_dependency.pyфайл

class DependencyManager(object):
 """Dependency manager, stores the results of tests.
    """

 ScopeCls = {'module':pytest.Module, 'session':pytest.Session}

 @classmethod
 def getManager(cls, item, scope='session'): # 这里修改成 session 复制代码

если

> pip install pytest-dependency


class TestExample(object):

 @pytest.mark.dependency()
 def test_a(self):
 assert False

 @pytest.mark.dependency()
 def test_b(self):
 assert False

 @pytest.mark.dependency(depends=["TestExample::test_a"])
 def test_c(self):
 # TestExample::test_a 没通过则不执行该条用例
 # 可以跨 Class 筛选
 print("Hello I am in test_c")

 @pytest.mark.dependency(depends=["TestExample::test_a","TestExample::test_b"])
 def test_d(self):
 print("Hello I am in test_d")


pytest -v test_demo.py    
2 failed
 - test_1.py:6 TestExample.test_a
 - test_1.py:10 TestExample.test_b
2 skipped复制代码

Пользовательские теги Pytest для фильтрации вариантов использования

1. Используйте модуль @pytest.mark, чтобы пометить классы или функции для проверки при выполнении вариантов использования.

@pytest.mark.webtest
def test_webtest():
 pass 


@pytest.mark.apitest
class TestExample(object):
 def test_a(self):
 pass

 @pytest.mark.httptest
 def test_b(self):
 pass 复制代码

Выполнять только вариант использования с пометкой webtest

pytest -v -m webtest

Results (0.03s):
 1 passed
 2 deselected 复制代码

Отметка выполнения для нескольких вариантов использования

pytest -v -m "webtest or apitest"

Results (0.05s):
 3 passed 复制代码

Только вариант использования, который не выполняет отмеченный веб-тест

pytest -v -m "not webtest"

Results (0.04s):
 2 passed
 1 deselected 复制代码

Отметить несколько вариантов использования без выполнения

pytest -v -m "not webtest and not apitest"

Results (0.02s):
 3 deselected 复制代码

2. Выберите вариант использования на основе тестового узла.

pytest -v Test_example.py::TestClass::test_a
pytest -v Test_example.py::TestClass
pytest -v Test_example.py Test_example2.py 复制代码

3. Используйте хук pytest, чтобы отметить варианты использования в пакетах

# conftet.py

def pytest_collection_modifyitems(items):
 """
    获取每个函数名字,当用例中含有该字符则打上标记
    """
 for item in items:
 if "http" in item.nodeid:
            item.add_marker(pytest.mark.http)
 elif "api" in item.nodeid:
            item.add_marker(pytest.mark.api)


class TestExample(object):
 def test_api_1(self):
 pass

 def test_api_2(self):
 pass

 def test_http_1(self):
 pass

 def test_http_2(self):
 pass
 def test_demo(self):
 pass复制代码

Варианты использования только для выполнения для маркерного API

pytest -v -m api
Results (0.03s):
 2 passed
 3 deselected
可以看到使用批量标记之后,测试用例中只执行了带有 api 的方法 复制代码

Используйте снимки экрана для обработки ошибок, журналы приложений и т. д.

1. Первый способ использования декоратора функций Python

def monitorapp(function):
 """
     用例装饰器,截图,日志,是否跳过等
     获取系统log,Android logcat、ios 使用syslog
    """

 @wraps(function)
 def wrapper(self, *args, **kwargs):
 try:
            allure.dynamic.description('用例开始时间:{}'.format(datetime.datetime.now()))
            function(self, *args, **kwargs)
            self.Action.driver.get_log('logcat')
 except Exception as E:
            f = self.Action.driver.get_screenshot_as_png()
            allure.attach(f, '失败截图', allure.attachment_type.PNG)
            logcat = self.Action.driver.get_log('logcat')
            c = '\n'.join([i['message'] for i in logcat])
            allure.attach(c, 'APPlog', allure.attachment_type.TEXT)
 raise E
 finally:
 if self.Action.get_app_pid() != self.Action.Apppid:
 raise Exception('设备进程 ID 变化,可能发生崩溃')
 return wrapper 复制代码

2. Второй способ с использованием хука pytest (выберите один из способов 1)

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
 Action = DriverClient().Action
    outcome = yield
    rep = outcome.get_result()
 if rep.when == "call" and rep.failed:
        f = Action.driver.get_screenshot_as_png()
        allure.attach(f, '失败截图', allure.attachment_type.PNG)
        logcat = Action.driver.get_log('logcat')
        c = '\n'.join([i['message'] for i in logcat])
        allure.attach(c, 'APPlog', allure.attachment_type.TEXT)
 if Action.get_app_pid() != Action.apppid:
 raise Exception('设备进程 ID 变化,可能发生崩溃') 复制代码

Как использовать другие хуки в Pytest

1. Настройте параметры Pytest

> pytest -s -all


# content of conftest.py
def pytest_addoption(parser):
 """
    自定义参数
    """
    parser.addoption("--all", action="store_true",default="type1",help="run all combinations")

def pytest_generate_tests(metafunc):
 if 'param' in metafunc.fixturenames:
 if metafunc.config.option.all: # 这里能获取到自定义参数    
            paramlist = [1,2,3]
 else:
            paramlist = [1,2,4]
        metafunc.parametrize("param",paramlist) # 给用例加参数化

# 怎么在测试用例中获取自定义参数呢
# content of conftest.py
def pytest_addoption(parser):
 """
    自定义参数
    """
    parser.addoption("--cmdopt", action="store_true",default="type1",help="run all combinations")


@pytest.fixture
def cmdopt(request):
 return request.config.getoption("--cmdopt")


# test_sample.py 测试用例中使用
def test_sample(cmdopt):
 if cmdopt == "type1":
 print("first")
 elif cmdopt == "type2":
 print("second")
 assert 1

> pytest -q --cmdopt=type2
second
.
1 passed in 0.09 seconds复制代码

2. Тестовый каталог фильтра Pytest

#过滤 pytest 需要执行的文件夹或者文件名字
def pytest_ignore_collect(path,config):
 if 'logcat' in path.dirname:
 return True #返回 True 则该文件不执行 复制代码

Некоторые распространенные методы pytest

1. Приоритет варианта использования Pytest (например, приоритет входа в систему или что-то в этом роде)

> pip install pytest-ordering


@pytest.mark.run(order=1)
class TestExample:
 def test_a(self):复制代码

2. Повторная попытка неудачного теста Pytest

#原始方法
pytet -s test_demo.py
pytet -s --lf test_demo.py #第二次执行时,只会执行失败的用例
pytet -s --ll test_demo.py #第二次执行时,会执行所有用例,但会优先执行失败用例
#使用第三方插件
pip install pytest-rerunfailures #使用插件
pytest --reruns 2 # 失败case重试两次 复制代码

3. Другие общие параметры Pytest

pytest --maxfail=10 #失败超过10次则停止运行
pytest -x test_demo.py #出现失败则停止 复制代码

Подведем итог

Выше общие проблемы и полезные методы были максимально обобщены, и я надеюсь, что это будет полезно для тестируемых! В следующей статье будет объяснено, как использовать функцию ловушки Pytest для запуска файлов yaml, чтобы заставить Appium выполнять автоматическое тестирование, и предоставить исходный код теста, так что следите за обновлениями!

Преимущества

Если у вас все еще много путаницы, то видеоресурсы и документы, которые я собрал, будут вашими хорошими учителями и полезными друзьями, и могут принести вам некоторую практическую помощь и прорыв [гарантировано 100% бесплатно]

​Эти материалы должны стать самым полным и полным подготовительным складом для друзей, которые занимаются [тестированием ПО].Этот склад также сопровождал меня в самом сложном путешествии, надеюсь, поможет и вам!

Наконец: вы можете бесплатно получить 216-страничный документ с интервью с инженером-испытателем программного обеспечения в моей общедоступной учетной записи VX: [Programmer Xiaohao]. И соответствующие обучающие видеоуроки можно свободно распространять! , который включает в себя базовые знания, основы Linux, Shell, принципы интернет-программирования, базу данных Mysql, темы инструментов захвата пакетов, инструменты тестирования интерфейса, расширенное тестирование — программирование на Python, автоматическое веб-тестирование, автоматическое тестирование приложений, автоматическое тестирование интерфейса, тестирование Расширенная непрерывная интеграция, тестовая среда разработки тестовая среда, тестирование производительности, тестирование безопасности и т. д. Вы также можете взаимодействовать со мной

Supongo que te gusta

Origin blog.csdn.net/myh919/article/details/131424897
Recomendado
Clasificación