Py.test - 将变量应用于来自 csv 的装饰器?

Py.test - Applying variables to decorators from a csv?

请耐心等待我解释我的困境,我仍然是一个 Python 新手,所以我的术语可能不正确。另外,我很抱歉post不可避免地冗长,但我会尽量详细地解释。

简要说明:

我目前正在为一组功能基本相同的网站开发一套 Selenium 测试,使用 py.test

我的典型测试如下所示:

@pytestrail.case('C100123')  # associates the function with the relevant TR case
@pytest.mark.usefixtures()
def test_login():
   # test code goes here

正如我之前提到的,我的目标是创建一组代码来处理我们的许多具有(几乎)相同功能的网站,因此上面示例中的硬编码装饰器将不起作用。

我在 TestRail 中尝试了一种使用 csv 和测试列表及其案例 ID 的数据驱动方法。

示例:

website1.csv:
Case ID | Test name
C100123 | test_login


website2.csv:
Case ID | Test name
C222123 | test_login

我编写的代码将使用检查模块查找测试名称 运行,找到相关的测试 ID 并将其放入名为 test_id:

的变量中
import csv
import inspect
class trp(object):
def __init__(self):
    pass


with open(testcsv) as f:  # testcsv could be website1.csv or website2.csv
    reader = csv.reader(f)
    next(reader)  # skip header
    tests = [r for r in reader]


def gettestcase(self):
    self.current_test = inspect.stack()[3][3]
    for row in trp.tests:
        if self.current_test == row[2]:
            self.test_id = (row[0])
            print(self.test_id)
            return self.test_id, self.current_test


def gettestid(self):
    self.gettestcase()

当时的想法是装饰器会根据我当时使用的 csv 动态更改。

@pytestrail.case(test_id)  # now a variable
@pytest.mark.usefixtures()
def test_login():
   trp.gettestid()
   # test code goes here

所以如果我 运行 test_login 为 website1,装饰器看起来像:

@pytestrail.case('C100123')

如果我 运行 test_login 对于 website2,装饰器将是:

@pytestrail.case('C222123')

我为自己想出这个解决方案而感到无比自豪并尝试了一下......它没有用。虽然代码本身确实有效,但我会得到一个异常,因为 test_id 未定义(我理解为什么 - gettestcase 在装饰器之后执行,所以它当然会崩溃。

我可以处理此问题的唯一其他方法是在执行任何测试代码之前应用 csv 和 testID。我的问题是——我怎么知道如何将测试与其测试 ID 相关联?什么是优雅的、最小的解决方案?

抱歉这个冗长的问题。如果您需要更多解释,我会密切关注并回答任何问题。

我认为这不能像您预期的那样工作的原因与 python 读取和执行文件的方式有关。当 python 开始执行时,它会读取链接的 python 文件并依次逐行执行。对于 'root' 缩进级别的东西(function/class 定义、装饰器、变量赋值等),这意味着它们在加载时恰好获得 运行 一次,并且再也不会。在你的例子中,python 解释器读入 pytest-testrail 装饰器,然后是 pytest 装饰器,最后是函数定义,每一个都执行一次,永远。

(旁注,这就是为什么你不应该使用可变对象作为函数参数默认值:Common Gotchas

假设您想首先推断出当前测试名称,然后将其与测试用例 ID 相关联,最后将该 ID 与装饰器一起使用,我不确定 pytest-testrail 的当前功能是否可行。至少,如果没有一些深奥且难以 debug/maintain 使用描述符等进行破解是不可能的。

我认为您实际上有一个选择:使用不同的 TestRail 客户端并更新您的 pytest 结构以使用新客户端。我可以推荐的两个 python 客户是 testrail-python and TRAW (TestRail Api Wrapper)(*)

您需要做更多的工作来创建用于启动 运行、更新结果和关闭 运行 的固定装置,但我认为最终您将拥有更便携的测试套件和更好的结果报告。

(*) 全面披露:我是TRAW的creator/maintainer,也为testrail做出了重大贡献-python

pytest 非常擅长为测试做各种元编程。如果我正确理解你的问题,下面的代码将使用 pytestrail.case 标记进行动态测试标记。在项目根目录中,创建一个名为 conftest.py 的文件并将此代码放入其中:

import csv
from pytest_testrail.plugin import pytestrail


with open('website1.csv') as f:
    reader = csv.reader(f)
    next(reader)
    tests = [r for r in reader]


def pytest_collection_modifyitems(items):
    for item in items:
        for testid, testname in tests:
            if item.name == testname:
                item.add_marker(pytestrail.case(testid))

现在您根本不需要使用 @pytestrail.case() 标记测试 - 只需编写其余代码,pytest 将负责标记:

def test_login():
    assert True

pytest 启动时,上面的代码将读取 website1.csv 并像您在代码中所做的那样存储测试 ID 和名称。在测试 运行 开始之前,pytest_collection_modifyitems 挂钩将执行,分析收集的测试 - 如果测试与 csv 文件中的名称相同,pytest 将添加 pytestrail.case 标记带有测试 ID。