概述
文章目录
- 1. 前言
- 2. 在常规函数中使用外部传参
- 3. fixture 函数入参方法
- 4. 相同用户免登录策略
1. 前言
最近把之前写的基于 unitest 的测试项目迁到pytest 了,虽然 pytest 无缝支持 unitest 的写法,但是还是按照 pytest 的规范改动了不少。本文就来记录一下实际使用过程中遇到的问题。
pytest有一个fixture概念,甚至推荐setup、 setdown也用fixture的yield来实现。
*fixture不能手动调用,只能使用在其他fixture函数或test函数以入参形式使用。
2. 在常规函数中使用外部传参
一般测试框架都有一个需求就是通过命令行来指定当前测试环境
这里可以使用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
... 省略
敲重点!!!
测试环境以 --env 入参方式被接收,以 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. 相同用户免登录策略
有些公司的登录接口,会做一个QPS限制,请求太多会响应"请勿频繁操作"。所以我们一个test类登录一次,可能都会太频繁。
正在想怎么解决的时候,突然想起来,python万物皆对象,func本身也是对象。再利用fixture的特性岂不是。。
conftest.py
@pytest.fixture(scope="class")
def login_with_phone():
def __login_with_phone(phone, code):
# 如果已经有cookies了
if hasattr(login_with_phone, "cookies") and login_with_phone.cookies:
# 并且用户名密码与之前成功登录的相同才登录
if login_with_phone.username == phone and login_with_phone.password == code:
return login_with_phone.cookies
# 登录操作
from request.enterprise.login.enterprise_user_login_request import EnterpriseUserLoginRequest
login_api = EnterpriseUserLoginRequest()
cookies = login_api.login_and_get_cookies(phone, code)
# 如果登录成功了,把cookies传递给外部函数的cookies属性
if login_api.request().isSuccess:
login_with_phone.cookies = cookies
login_with_phone.username = phone
login_with_phone.password = code
return cookies
yield __login_with_phone
这样的话,会如果检查如果本次登录与上次登录账密一样,就继承cookies,否则重新登录。
*注意这里的fixture的scope需要改成class或module。session和function都不行。当然,如果你只希望同一文件内共享,那么就用function好了。
你甚至还可以使用骚操作,即使交错着登录不同的账号也总能共享账密。
@pytest.fixture(scope="class")
def login_with_phone():
def __login_with_phone(phone, code):
prev_user_key = phone + code
if hasattr(login_with_phone, "cookies"):
# 如果用户存在于用户池,并且密码也一样,返回之前这个用户的cookies
if prev_user_key in login_with_phone.cookies and login_with_phone.cookies[prev_user_key]:
return login_with_phone.cookies[prev_user_key]
else:
login_with_phone.cookies = {}
# 登录操作
from request.enterprise.login.enterprise_user_login_request import EnterpriseUserLoginRequest
login_api = EnterpriseUserLoginRequest()
cookies = login_api.login_and_get_cookies(phone, code)
# 如果登录成功了,把用户信息与cookies存入用户池中
if login_api.is_success:
login_with_phone.cookies[prev_user_key] = cookies
return cookies
yield __login_with_phone
@pytest.fixture(scope="class")
def login_with_user(login_main_site_with_phone):
def __login_with_user(user: dict) -> str:
if isinstance(user, dict) and 'phone' in user and 'code' in user:
return login_with_phone(user['phone'], user['code'])
yield __login_with_user
最后
以上就是义气鸵鸟为你收集整理的Pytest 巧用环境变量实现常规方法使用 fixture 对象、fixture 函数入参方法、免登录策略示例1. 前言2. 在常规函数中使用外部传参3. fixture 函数入参方法4. 相同用户免登录策略的全部内容,希望文章能够帮你解决Pytest 巧用环境变量实现常规方法使用 fixture 对象、fixture 函数入参方法、免登录策略示例1. 前言2. 在常规函数中使用外部传参3. fixture 函数入参方法4. 相同用户免登录策略所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复