Appium pytest 的 conftest fixture 和 pytest.mark.parametrize 如何协调?

醋精测试媛 · 2021年04月06日 · 最后由 lin1990 回复于 2021年04月25日 · 5173 次阅读

看了【 https://testerhome.com/topics/29298 】,没有看很懂😂

以及【 https://testerhome.com/topics/17292 】中使用好像是直接 scope 为 function,然后在参数中传入 driver
类似:

  @pytest.fixture()
  def driver():
      driver = webdriver.Remote(remote_url, desired_caps)
      yield driver 
     driver.quit()

def test_001(driver):
    driver.find_element(By.ID, "xxx")

如果我想要用@pytest.fixture(scope="session", autouse=True), 但是这样的话,driver 如果要返回,只能在每个用例函数的参数中添加 driver,如上所示,第一,比较麻烦,第二,可能也会影响使用@pytest.mark.parametrize,请问大家都是怎么初始化 driver 的呢?

另外,想要不写死数据,要用到参数化,如果放到 yml 文件中,数据就只能写死了,如果放到 py 文件中,数据可能是活的,但是可读性太差,这个问题有办法协调吗?

比如说 login,有密码错误和账号错误的情况,这种情况应该把账号写死吗,举个例子,手机号 13122226666,这个手机号是未注册的手机号,会提示错误,这个用例应该每次使用不同的错误的手机号吗,下次使用 15922229999 之类的。

最佳回复

账号空、密码空、账号密码都为空、账号不存在、密码错误、账号密码正确的情况,每次测试的数据应该一样吗?

llyyff 回复

我还是决定把这两个问题先解决。
针对这个需求,我想到了一个比较笨的想法,就是在 yaml 中使用 ${变量或者函数}的形式,但是我可以在 get yaml 中数据的时候,对里面符合上述形式的内容进行替换。代码如下:

def get_yaml(filepath, field):
    """
    读取yaml文件
    :param filepath: 文件路径
    :param field: yaml中的key
    :return: dict,list
    """
    with open(filepath, 'r', encoding='utf-8') as f:
        file_data = f.read()
        data = yaml.load(file_data, Loader=yaml.FullLoader)
        f.close()
    raw_data = data[field]
    # 将原生数据转为字符串类型
    string = str(raw_data)
    # 从字符串中搜索符合${函数/变量}的字符串,并将其中的函数/变量计算出来
    for expression in re.findall("(?<=\\${)[A-Za-z_][A-Za-z0-9_()^{}]+(?=})", string):
        value = str(eval(expression))
        # 将其代替其中的函数/变量
        string = string.replace(f"${{{expression}}}", value)
    return eval(string)

比较笨,而且里面的正则表达式不知道有没有囊括所有情况,但是算是一种办法。

共收到 28 条回复 时间 点赞

想要不写死数据,要用到参数化,如果放到 yml 文件中,数据就只能写死了,如果放到 py 文件中,数据可能是活的,但是可读性太差,这个问题有办法协调吗?

就像 https://testerhome.com/topics/29298
一楼提到的,直接获取 driver 时使用单例不行吗?

闲云野鹤 回复

他们也在说这个不是特别好的办法,我之前是这么做的,既然@pytest.fixture(scope="session", autouse=True)存在,我觉得应该也是可以用的。

个人感觉单例挺好的,哈哈哈,看到别人有这么做的,在 fixture 里把 driver 设置成 global (我没试过是否可以😂 😂 )

@pytest.fixture(scope='session', autouse=True)
def drivers(request):
    global driver
    if driver is None:
        driver = webdriver.Chrome()
        driver.maximize_window()

    def fn():
        driver.quit()

    request.addfinalizer(fn)
    return driver
闲云野鹤 回复

好的,我试试,谢谢您

账号空、密码空、账号密码都为空、账号不存在、密码错误、账号密码正确的情况,每次测试的数据应该一样吗?

放在 yaml 中的数据好像可以不是写死的。。类似 key:$.data 的方式,不过我不清楚别人是如何实现的。。

llyyff 回复

yaml python 好像只能有锚点和引用 即&*

我曾在社区看到过一位大佬在 yaml 中用过 key:$.data,也在别的地方看到过一次,就是 python 的,具体实现就不太清楚了。。

llyyff 回复

可以发一下链接给我吗,我查了很多资料,但是没看到类似的内容


链接找不太到了。。只找到当初的一张截图。。

yaml 中:

读取后输出:

好像是无法使用的。

我截图的这一部分作者说是借鉴了 jmeter 中函数助手的类似方法,没写该部分的源码。。具体我就不太清楚了、、、

llyyff 回复

应该是搞不出来了😭

😂 不影响使用的话就先放一下吧。。这两个问题我都是先一放的。。

llyyff 回复

我还是决定把这两个问题先解决。
针对这个需求,我想到了一个比较笨的想法,就是在 yaml 中使用 ${变量或者函数}的形式,但是我可以在 get yaml 中数据的时候,对里面符合上述形式的内容进行替换。代码如下:

def get_yaml(filepath, field):
    """
    读取yaml文件
    :param filepath: 文件路径
    :param field: yaml中的key
    :return: dict,list
    """
    with open(filepath, 'r', encoding='utf-8') as f:
        file_data = f.read()
        data = yaml.load(file_data, Loader=yaml.FullLoader)
        f.close()
    raw_data = data[field]
    # 将原生数据转为字符串类型
    string = str(raw_data)
    # 从字符串中搜索符合${函数/变量}的字符串,并将其中的函数/变量计算出来
    for expression in re.findall("(?<=\\${)[A-Za-z_][A-Za-z0-9_()^{}]+(?=})", string):
        value = str(eval(expression))
        # 将其代替其中的函数/变量
        string = string.replace(f"${{{expression}}}", value)
    return eval(string)

比较笨,而且里面的正则表达式不知道有没有囊括所有情况,但是算是一种办法。

llyyff 回复

但是还是有缺陷的,比如 yaml 中的函数必须和 get_yaml 函数在同一个 py 文件中,不然计算不出来。但是还是放到这里,期望有人可以提出其他的想法吧。

非必须在同一个 py 文件中, 你可以看下 python 的反射

driver 问题有两种比较优雅的方式。
方式一,autouse,参考代码:

class TestClass:
    @pytest.fixture(autouse=True)
    def transact(self, request, db):
        db.begin(request.function.__name__)
        yield
        db.rollback()

    def test_method1(self, db):
        assert db.intransaction == ["test_method1"]

    def test_method2(self, db):
        assert db.intransaction == ["test_method2"]

方式二,conftest,参考代码:

https://dongfanger.gitee.io/blog/pytest%E5%8E%9F%E7%94%9F%E6%A1%86%E6%9E%B6/003-%E7%B2%BE%E9%80%9Afixture.html#fixture-setup-teardown

目前采用的是方法一,因为 fixture 有返回,所以应该无法用方法二吧。

幺叁叁 回复

好的,我看看!

这篇文章件介绍了 yaml 文件如何参数化
https://www.cnblogs.com/yoyoketang/p/14119435.html

我的是自己封装了 driver 类 然后用了单例装饰器 这样整个脚本运行期间用的都是同一个 session。另外 参数化用的是 excel 读取 excel 的数据转换成列表,用@pytest.mark.parametrize 直接搞定参数化

回复

谢谢,确实解决了变量的问题,但是好像不能解析函数。
比如说,账号错误,密码错误的情况,每次测试的数据应该是一样的呢,还是应该不一样?如果是一样的,那可以写死,如果不一样,可能就要用到 faker 库,random 库之类的,并且用到里面的函数,比如像:
account: ${random.randint(2, 49)}
这种的,请问应该怎么解决?

yanl 文件应该不能放函数,可以在替换的时候使用函数:

account: $account
name: $name
from string import Template
import yaml
import random
from faker import Faker

fake = Faker(locale='zh_CN')
with open("login.yaml", encoding='utf-8') as fp:
    read_yml_str = fp.read()

    tempTemplate1 = Template(read_yml_str)
    c = tempTemplate1.safe_substitute({"account": random.randint(2, 49), "name": fake.name()})
    print(c)

yaml_data = yaml.safe_load(c)
print(yaml_data)
回复

确实可以这样做,想问一下用例的错误情况,比如说手机号码的格式,10 位数,11 位数但是是 3 开头,12 位数,没有通过认证的手机号码,这种都是不符合的情况,会出现提示信息,这算是一种用例,我想知道的是,这几种情况需要每次输入的错误数据相同吗?如果需要每次输入的错误数据不同,那么 tempTemplate1.safe_substitute({"account": random.randint(2, 49)}) 中的函数便会太多太多了,有 random_phone(length=10),random_phone(length=12) 等等。

没太懂你的意思,是要在 yaml 文件使用参数么?
我是这样做的:
yaml:

解析 yaml 文件,替换 ${}
def _replace_method(self,filename="",**kwargs):
data = Yaml_read.yaml_read(filename)
data1 = json.dumps(data)
for key,value in kwargs.items():
data1 = data1.replace(f'${{{key}}}',value)
data2 = json.loads(data1)
return data2
这样可以通过传参的方式动态替换 yaml 文件的变量

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册