文本主要介绍下 Pytest+Allure+Appium 记录一些过程和经历。
法主要用了啥:
Python3
 Appium
 Allure-pytest
 Pytest
Appium 不常见却好用的方法
Appium 直接执行 adb shell 方法
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’]
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’)
该方式能直接获取到登录按钮区域的截图
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 测试报告中
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 method上的一些区别
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.第一种方法 parametrize 装饰器参数化方法
@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 hook 批量加参数化
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 库可以创造依赖关系
 当上层用例没通过,后续依赖关系用例将直接跳过,可以跨 Class 类筛选
 如果需要跨.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
仅不执行标记 webtest 的用例
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.根据 test 节点选择用例
pytest -v Test_example.py::TestClass::test_a
 pytest -v Test_example.py::TestClass
 pytest -v Test_example.py Test_example2.py
3.使用 pytest hook 批量标记用例
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 的方法
用例错误处理截图,app 日志等
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 hook 方法 (与方法一选一)
@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 另一些 hook 的使用方法
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 一些常用方法
Pytest 用例优先级(比如优先登录什么的)
pip install pytest-ordering
@pytest.mark.run(order=1)
 class TestExample:
 def test_a(self):
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重试两次
Pytest 其他常用参数
pytest --maxfail=10 #失败超过10次则停止运行
 pytest -x test_demo.py #出现失败则停止
学习安排上
如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片进群即可自行领取。



















