当前位置: 移动技术网 > IT编程>脚本编程>Python > 荐 pytest框架走出 test -> fixture <-> fixture 调用限制的魔咒

荐 pytest框架走出 test -> fixture <-> fixture 调用限制的魔咒

2020年08月10日  | 移动技术网IT编程  | 我要评论
文章目录1. 前言2. 外部入参走出fixture魔咒3. fixture函数入参方法4. 把case失败的接口会话写入报告1. 前言最近把之前写的基于unitest的测试项目迁到pytest了,虽然pytest无缝支持unitest的写法,但是还是按照pytest的规范改动了不少。本文就来记录一下实际使用过程中遇到的问题。pytest有一个fixture概念,甚至推荐setup、 setdown也用fixture的yield来实现。*fixture不能手动调用,只能使用在其他fixture函数或

1. 前言

最近把之前写的基于unitest的测试项目迁到pytest了,虽然pytest无缝支持unitest的写法,但是还是按照pytest的规范改动了不少。本文就来记录一下实际使用过程中遇到的问题。

pytest有一个fixture概念,甚至推荐setup、 setdown也用fixture的yield来实现。
*fixture不能手动调用,只能使用在其他fixture函数或test函数以入参形式使用。

2. 外部入参走出fixture魔咒

一般测试框架都有一个需求就是通过命令行来指定当前测试环境
这里可以使用pytest内置pytest_addoption函数来收集入参

conftest.py

# pytest_addoption是保留函数
# --env 是接收命令的指令,例: pytest --env dev
# dest 是它的名字,读取参数时要用到
def pytest_addoption(parser):
    parser.addoption("--env", action="store", dest="environment", default="dev", help="environment: dev, dev-01, dev-02")

# 这是自定义的fixture函数
# request.config.getoption('environment') 来读取上面函数中收集到的入参的dest名字
# 这个函数的意义是通过--dev 入参去读取指定的 dev.yaml dev-01.yaml 配置文件
@pytest.fixture(scope="session", autouse=True)
def env(request):
    config_path = os.path.join(request.config.rootdir, "config", f"{request.config.getoption('environment')}.yaml")
    with open(config_path) as f:
        env_config = yaml.load(f.read(), Loader=yaml.SafeLoader)
    return env_config

使用示例
get_assessment_list_test.py

# 这里是一个测试类中的fixture函数
# env['ASSESSMENT_URL'] 读取了--env 入参对应的yaml文件中的ASSESSMENT_URL值
class GetAssessmentListTest:
    @pytest.fixture()
    def api(self, env, login_enterprise):
        api = GraphqlApi()
        api.url = env["ASSESSMENT_URL"] + api.path
        ... 省略

敲重点!!!
测试环境以 --dev 入参方式被接收,以 request.config.getoption(‘dest_name’)来使用
但是很快我发现request是一个fixture,所以只能在fixture 和test函数中使用!??

那我的工具类,封装的接口类怎么搞?难道要从test函数一层一层往里丢?


所以我想到了一个很鸡贼的方法,那就是扔进环境变量

conftest.py

# 接收命令行入参方式不变
def pytest_addoption(parser):
    parser.addoption("--env", action="store", dest="environment", default="dev", help="environment: dev, dev-01, dev-02")

# 把接收到的外部入参写入环境变量,这样就不受框架限制了
# scope="session" 是全局的意思,autouse是自动执行的意思,执行顺序在最前面
@pytest.fixture(scope="session", autouse=True)
def add_system_env(request):
    # 写入环境变量
    os.environ["TEST_ENV"] = request.config.getoption('environment')

使用示例

由于autouse是pytest框架一启动就执行了,所以执行道test类test函数的时候,环境变量里已经有值了。所以想怎么用,就怎么用。

# os.environ是一个字典
print(os.environ.get("TEST_ENV"))
# 输出的结果 {"TEST_ENV": "dev"}

应用场景

我觉得读取数据库就很合适,因为我不想把查找数据库维护在test类中,或者就算维护在fixture里也要test类传一遍,这很繁琐也不符合逻辑。

class Mysql:
    def __init__(self, mysql_conf=None):
        if not mysql_conf:
            mysql_conf = Tool.get_config("MYSQL_CONFIG")

        self.conn = pymysql.connect(**mysql_conf, autocommit=False)
        self.cur = self.conn.cursor(pymysql.cursors.DictCursor)
    ... 省略代码

*我把写入/ 读取os.environ封装成Tool类方法了,这里就不贴代码了。这里的Tool.get_config就是从环境变量取到dev环境,再去读dev.yaml文件。

3. fixture函数入参方法

先给一个错误的示范
根据我们正常理解,可能会这样写,IDE也没报错,但是执行肯定会报错。

import pytest

class TwoSumTest():
    @pytest.fixture()
    def two_sum(self, a, b):
        yield a + b

    def test_two_sum(self, two_sum):
        print("two_sum", two_sum(1, 2))

这里有个很鸡贼的方法就是返回一个其他函数
*fixture甚至可以返回一个类对象

import pytest

class TwoSumTest():
    @pytest.fixture()
    def two_sum(self):
        def _two_sum(a, b):
            return a + b
        yield _two_sum

    def test_two_sum(self, two_sum):
        print("two_sum", two_sum(1, 2))

应用场景

我觉得登录就很适合,登录的账密入参总不能hard code吧。

conftest.py

@pytest.fixture(scope="session")
def login_enterprise(env):
    def _login_enterprise(username, password):
        from request.enterprise.login.enterprise_user_login_request import EnterpriseUserLoginRequest
        login_api = EnterpriseUserLoginRequest()
        login_api.url = env["ENTERPRISE_URL"] + login_api.path
        login_api.data_variables = {"login": username, "password": password}
        cookies = login_api.request().cookies
        return cookies
    yield _login_enterprise

4. 把case失败的接口会话写入报告

这一段是我从其他大佬那 copy 的失败后截图的的一段
conftest.py

@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, 'extra', [])
    # 如果错误了
    if report.when == 'call' or report.when == "setup":
        xfail = hasattr(report, 'wasxfail')
        if (report.skipped and xfail) or (report.failed and not xfail):
            try:
                html = item.funcargs["api"].info
                extra.append(pytest_html.extras.html(f'<pre>{html}</pre>'))
            except Exception:
                pass
        report.extra = extra
    report.description = str(item.function.__doc__)
    report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")  # 解决乱码

这个需要很多前期工作,只能意会的讲一下了,下面这2行代码是核心内容
· 从item.funcargs字段中取出一个对象
· 把取出的东西放进html的extras里

html = item.funcargs["api"].info
extra.append(pytest_html.extras.html(f'<pre>{html}</pre>'))

那么这个item.funcargs[“api”]是怎么来的呢?其实是我们test函数中的fixture入参
get_assessment_list_test.py

class GetAssessmentListTest:
    @pytest.fixture()
    def api(self, env, login_enterprise):
        api = GraphqlApi()
        api.url = env["ASSESSMENT_URL"] + api.path
        ... 省略
	
	# 这个api是我定义的接口类,包含了被测接口的属性 & 方法
    def test_get_assessment_list(self, api):
       """正常流程

       :return:
       """
       _api = api
       # 这个请求函数,除了自身逻辑外,还会把接口的method, url, request, response 拼接后扔进self.info
       _api.request() 
       _api.assertion("0x000000")

graphql_api.py

def request(self):
	... 省略代码
	self.info = Log.generate_log_info(self)
	... 省略代码

本文地址:https://blog.csdn.net/tomoya_chen/article/details/107890481

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网