请耐心等待,当我试图解释我的困境时,我仍然是Python新手,所以我的术语可能不正确。此外,我对这篇文章不可避免的冗长感到抱歉,但我会尽可能多地阐述相关细节。
简要介绍:
我目前正在为一组功能基本相同的网站开发一套Selenium测试,使用py.test
-
使用pytest插件pytest TestRail将测试结果上传到TestRail。
-
测试用decorator@pytestrail.case(id(标记,并带有唯一的用例id
我的一个典型测试如下:
@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
我编写的代码将使用inspect模块来查找正在运行的测试的名称,找到相关的测试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
因此,如果我为website1运行test_login,装饰器将看起来像:
@pytestrail.case('C100123')
如果我为website2运行test_login,装饰器将是:
@pytestrail.case('C222123')
我为自己想出这个解决方案感到非常自豪,并尝试了一下。。。它不起作用。虽然代码本身可以工作,但我会得到一个异常,因为test_id是未定义的(我理解为什么在decorator之后执行-gettestcase
,所以它当然会崩溃
我能处理这个问题的唯一其他方法是在执行任何测试代码之前应用csv和testIDs。我的问题是——我怎么知道如何将测试与其测试ID相关联?一个优雅的、最小的解决方案是什么?
很抱歉问了这么长的问题。如果你需要更多的解释,我会密切关注,回答任何问题。
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
将向其添加带有测试ID的pytestrail.case
标记。
我相信这没有像你预期的那样起作用的原因与python读取和执行文件的方式有关。当python开始执行时,它读取链接的python文件,并依次逐个执行每一行。对于"根"缩进级别的东西(函数/类定义、装饰器、变量赋值等(,这意味着它们在加载时只运行一次,再也不会运行了。在您的案例中,python解释器读取pytest testrail装饰器,然后读取pytest装饰器,最后读取函数定义,每次执行一次。
(附带说明,这就是为什么永远不应该使用可变对象作为函数参数默认值:Common Gotchas(
假设您想首先推导出当前的测试名称,然后将其与测试用例ID相关联,最后将该ID与decorator一起使用,我不确定这在pytest-testrail的当前功能中是否可行。至少,如果没有一些深奥且难以使用描述符等进行调试/维护的破解,这是不可能的。
我认为您实际上有一个选择:使用不同的TestRail客户端,并更新您的pytest结构以使用新客户端。我可以推荐的两个python客户端是testrail python和TRAW(testrail Api Wrapper((*(
您将需要更多的工作来创建用于开始运行、更新结果和结束运行的固定装置,但我认为最终您将拥有一套更可移植的测试和更好的结果报告。
(*(全面披露:我是TRAW的创建者/维护者,也为testrail-python做出了重大贡献