conftest.py
def pytest_addoption(parser):
parser.addoption('--env', action='store', default='qa',
help='setup environment: development')
#pytest.fixture(scope="session", autouse=True)
def get_config(request):
environment = request.config.getoption("--env")
with open(environment, "r") as f:
config = json.load(f)
return config
lib.py
class lib1():
def cal_message():
# get something from config, do something and return it, but how to get the config here
test_config.py
import lib
def test_lib():
a = lib.lib1()
a.cal_message()
The question is how to get config from lib.py?
You can add a custom attribute to pytest and pass variables that way.
def pytest_addoption(parser):
parser.addoption('--env', action='store', default='qa',
help='setup environment: development')
#pytest.fixture(scope="session", autouse=True)
def get_config(request):
environment = request.config.getoption("--env")
pytest.custom_config = {"env":environment}
with open(environment, "r") as f:
config = json.load(f)
return config
And, get it in lib.py:
import pytest
class lib1():
def cal_message():
env = pytest.custom_config["env"]
A caveat: This will work only when lib.py is called from test module of a pytest test session. If you try using it in a standalone script, it won't work. e.g. python lib.py --env a will not work.
Related
So, I have my app structured like this:
# container.py
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(modules=[".routes"])
print(f"$$$$$$$\n testinggggg - {os.environ.get('ENV')}\n$$$$$$")
config = providers.Configuration(
yaml_files=[
"./src/conf/common.yaml", # load the common config first
config_file_path[
os.environ.get("ENV", "DEV")
], # env specific config - will override values in common.yaml
]
)
config.load(envs_required=True)
print(f"$$$$$$$\nconfig loaded as - {config.get('app')}\n$$$$$$")
db_repository = providers.Resource(DBRepository, config.get("app.db_repository"))
and
# __init__.py
def create_app():
print("###### in create_app() #######")
container = Container()
container.init_resources()
app = Flask(__name__)
app.container = container
# connect url rules and register error handlers
routes.configure(app)
return app
My folder structure is as follows:
tests
|--unit_tests
|-- test_service.py
|--functional_tests
|-- test_functional.py
My functional test looks like this:
import json
import os
from unittest import mock
import pytest
import requests
from flask import url_for
from test.util.mock_db import MockDB
"""
These tests spin up a testing instance of the app and test the APIs end to end
"""
#pytest.fixture(scope='module', autouse=True)
def mock_settings_env_vars():
with mock.patch.dict(
os.environ,
{
"ENV": "TEST",
"DB_USERNAME": os.environ.get("DB_USERNAME", "root"),
"DB_PASSWORD": os.environ.get("DB_PASSWORD", "admin"),
"DB_HOST": os.environ.get("DB_HOST", "localhost"),
"DB_PORT": os.environ.get("DB_PORT", "3306"),
},
):
yield
#pytest.fixture(scope='session', autouse=True)
def app():
# test if pytest fixture for env vars works as expected
assert os.environ["ENV"] == "TEST", f"ENV is set to {os.environ['ENV']}"
print(f"********* HIIIII ********* {os.environ['ENV']}")
from src import create_app
mock_db = MockDB(
os.environ["DB_USERNAME"],
os.environ["DB_PASSWORD"],
os.environ["DB_HOST"],
os.environ["DB_PORT"],
)
mock_db.setup()
app = create_app()
yield app
mock_db.teardown()
app.container.unwire()
def test_check(client, app):
response = client.get(url_for("check"))
assert response.status_code == 200
assert response.data == b"You hit /"
I invoke pytest from the command line as:
/tests $ pytest -s -rAf
When I run this command, so I can just run all tests with a single command - functional & unit - the create_app() function does NOT get the environment variables
However, when I run:
/tests $ pytest functional_tests/test_functional -s -rAF
The test executes successfully.
How/why does this affect the environment variables while running the tests? Any ideas?
This is most probably due to the execution order of the fixtures. app fixture seems to rely on what mock_settings_env_vars sets up. However, app has the scope set to session and should therefore run before mock_settings_env_vars that has the scope set to module. The unexpected thing in my opinion is that it actually works when you run pytest functional_tests/test_functional.
You could start by checking Pytest's execution plan:
$ pytest -s -rAf --setup-plan
...vs...
$ pytest -s -rAF --setup-plan functional_tests/test_functional
To alter the fixture order, you might make app dependent on mock_settings_env_vars. However, you would need to alter the scopes. I'm not sure why these two have a different scope but you can try to set them both to session or module:
#pytest.fixture(scope='module', autouse=True)
def mock_settings_env_vars():
...
#pytest.fixture(scope='module', autouse=True)
def app(mock_settings_env_vars):
...
I have an AWS S3 directory containing several JSON files, which are used as test inputs.
I've created a PyTest module that downloads all JSON files once using a module wide fixture, and then runs several test functions - each being parameterized over the set of JSONs:
import pytest
import os
from tempfile import mkdtemp, TemporaryDirectory
from glob import glob
JSONS_AWS_PATH = 's3://some/path/'
def download_jsons():
temp_dir = mkdtemp()
aws_sync_dir(JSONS_AWS_PATH, temp_dir)
json_filenames = glob(os.path.join(local_path, "*.json"))
return json_filenames
#pytest.fixture(scope='module', params=download_jsons()) #<-- Invoking download_jsons() directly
def json_file(request):
return request.param
def test_something(json_file):
# Open the json file and test something
def test_something_else(json_file):
# Open the json file and test something else
def test_another_thing(json_file):
# you got the point...
This test module in itself works - the only pain point is how to cleanup the temp_dir at the end of the module\session.
Since download_jsons() is being invoked directly, before json_file fixture is even started - it has no context of its own. So I can't make it clean temp_dir after all the tests are done.
I would like to make download_jsons() a module\session scope fixture in itself. Something like:
fixture(scope='module')
def download_jsons():
temp_dir = mkdtemp()
# Download and as glob, as above
yield json_filenames
shutil.rmtree(temp_dir)
or
fixture(scope='module')
def download_jsons(tmpdir_factory):
#...
as #Gabriela Melo has suggested.
The question is how to make the json_file fixture parameterized over the list returned by download_jsons(), without invoking it directly?
I've tried implementing this solution with either mark.parametrize, setup_module(), or pytest_generate_tests(metafunc) - but wasn't able to implement the exact functionality I was looking for.
This seems to be what you're looking for: https://docs.pytest.org/en/latest/tmpdir.html#the-tmpdir-factory-fixture
(Using Pytest's tmpdir_factory fixture and setting the scope of your json_file function to session instead of module)
If you want to use a resource for parametrization, it can't be returned by a fixture (at least with the current version of pytest). However, you can move the setup/teardown code out to the hooks - this will also enable parametrizing via pytest_generate_tests hook. Example: in the root dir of your project, create a file named conftest.py with the following contents:
from tempfile import TemporaryDirectory
from pathlib import Path
def pytest_sessionstart(session):
# this is where we create the temp dir and download the files
session.config._tempdir = TemporaryDirectory()
d = session.config._tempdir.name
aws_sync_dir(JSONS_BLOBBY_PATH, d)
session.config._json_files = Path(d).glob("*.json")
def pytest_sessionfinish(session):
# this is where we delete the created temp dir
session.config._tempdir.cleanup()
def pytest_generate_tests(metafunc):
if "file" in metafunc.fixturenames:
# parametrize the file argument with the downloaded files
files = metafunc.config._json_files
metafunc.parametrize("file", files)
You can now use the file argument in tests as usual, e.g.
def test_ends_with_json(file):
assert file.suffix == ".json"
I'm writing tests for the config file of a Flask application. To make sure the env variables set in the system do not influence the results of the test I'm using pytest's monkeypatch to create predictable test outcomes.
I'm testing the config file once in a 'clean' state with a fixture with no env variables set and once in a 'fake' config, with a fixture where I let monkeypatch set the variables before running the test.
Both fixtures set env variables and then import the config object before passing it on to the test function.
When the config object is loaded at the head of the document instead of inside the fixtures, both fixtures use a version based on the actual system env variables.
It seems like the second fixture does not import the config object, but reuses the one created by the cleanConfig fixture. How can I force the fixture to reimport the config object?
test_config.py:
import pytest
from config import config
class TestConfigSettings(object):
#pytest.fixture(scope='function')
def cleanConfig(config_name, monkeypatch):
def makeCleanConfig(config_name):
monkeypatch.delenv('SECRET_KEY', raising=False)
monkeypatch.delenv('DEV_DATABASE_URL', raising=False)
from config import config
configObject = config[config_name]
return configObject
return makeCleanConfig
#pytest.fixture(scope='function')
def fakeEnvConfig(config_name, monkeypatch):
def makeFakeEnvConfig(config_name):
monkeypatch.setenv('SECRET_KEY', 'fake difficult string')
monkeypatch.setenv('DEV_DATABASE_URL', 'postgresql://fake:5432/fakeDevUrl')
from config import config
configObject = config[config_name]
return configObject
return makeFakeEnvConfig
def test_configObject_withDevelopmentConfig_containsCorrectSettings(self, cleanConfig):
configObject = cleanConfig('development')
assert configObject.SECRET_KEY == 'hard to guess string'
assert configObject.DEBUG == True
assert configObject.SQLALCHEMY_DATABASE_URI == None
def test_configObject_withDevelopmentConfigAndEnvSet_copiesEnvSettings(self, fakeEnvConfig):
configObject = fakeEnvConfig('development')
assert configObject.SECRET_KEY == 'fake difficult string'
assert configObject.SQLALCHEMY_DATABASE_URI == 'postgresql://fake:5432/fakeDevUrl'
Config.py:
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL')
config = {
'default': DevelopmentConfig,
'development': DevelopmentConfig,
...
}
I finally found a solution for my problem. By using the reload() function, you can import a module again after changing the content (the loaded env variables in this case). To be able to use it I had to change the import to the config module instead of the config dictionary I was importing before, since the reload() object only works on modules.
The new code:
import pytest
from importlib import reload
import config
class TestConfigSettings(object):
#pytest.fixture(scope='function')
def cleanConfig(config_name, monkeypatch):
def makeCleanConfig(config_name):
monkeypatch.delenv('SECRET_KEY', raising=False)
monkeypatch.delenv('DEV_DATABASE_URL', raising=False)
reload(config)
configObject = config.config[config_name]
return configObject
return makeCleanConfig
#pytest.fixture(scope='function')
def fakeEnvConfig(config_name, monkeypatch):
def makeFakeEnvConfig(config_name):
monkeypatch.setenv('SECRET_KEY', 'fake difficult string')
monkeypatch.setenv('DEV_DATABASE_URL', 'postgresql://fake:5432/fakeDevUrl')
reload(config)
configObject = config.config[config_name]
return configObject
return makeFakeEnvConfig
Lets say my code looks like this
import pytest
import json
#pytest.fixture
def test_item():
test_item = json.load(open('./directory/sample_item_for_test.json','rb'))
return test_item
def test_fun(test_document):
assert type(test_item.description[0]) == unicode
And I would like to run this test via Py.Test
If I run Py.test from the directory that it is in, it is fine. BUT if I run it from an above directory, it fails due to not being able to find 'sample_item_for_test.json'. Is there a way to make this test run correctly no matter where I execute Py.test?
The magic attribute __file__ is the path to the python file on the filesystem. So, you can use that with some magic from os to get the current directory...
import pytest
import json
import os
_HERE = os.path.dirname(__file__)
_TEST_JSON_FILENAME = os.path.join(_HERE, 'directory', 'sample_item_for_test.json')
#pytest.fixture
def test_item():
with open(_TEST_JSON_FILENAME, 'rb') as file_input:
return json.load(file_input)
When I migrated to py.test, I had a large set of legacy tests that were accustomed to being executed in the directory where the test file lives. Instead of tracking down every test failure, I added a pytest hook to my conftest.py to chdir to the test directory before each test starts:
import os
import functools
def pytest_runtest_setup(item):
"""
Execute each test in the directory where the test file lives.
"""
starting_directory = os.getcwd()
test_directory = os.path.dirname(str(item.fspath))
os.chdir(test_directory)
teardown = functools.partial(os.chdir, starting_directory)
# There's probably a cleaner way than accessing a private member.
item.session._setupstate.addfinalizer(teardown, item)
I have file tree:
f:/src/
restore.ini
config.py
log.py
service.py
test.py
the test.py code like this:
import service
import log
import config
class Test(object):
def __init__(self):
super(Test, self).__init__()
def setUp(self):
self.currentRound = int(config.read_config_co(r'restore.ini', 'Record')['currentRound'])
def testAction(self):
log.info(self.currentRound)
def tearDown(self):
config.write_config_update_co(self.currentRound-1, 'Record', 'currentRound', r'restore.ini')
class PerfServiceThread(service.NTServiceThread):
def run (self):
while self.notifyEvent.isSet():
try:
test = Test()
test.setUp()
test.testAction()
test.tearDown()
except:
import traceback
log.info(traceback.format_exc())
class PerfService(pywinservice.NTService):
_svc_name_ = 'myservice'
_svc_display_name_ = "My Service"
_svc_description_ = "This is what My Service does"
_svc_thread_class = PerfServiceThread
if __name__ == '__main__':
pywinservice.handleCommandLine(PerfService)
Now, I use cmdline python test.py install and python test.py start to action service, but error.
If I move all files in directory src to C:\Python27\Lib\site-packages\win32\src, and change code:
self.currentRound = int(config.read_config_co(r'src\restore.ini', 'Record')['currentRound'])
config.write_config_update_co(self.currentRound-1, 'Record', 'currentRound', r'src\restore.ini')
Now, it's OK!
I want not move directory src, how do I do?
Thanks!
if you use relative paths for file or directory names python will look for them (or create them) in your current working directory (the $PWD variable in bash; something similar on windows?).
if you want to have them relative to the current python file, you can use (python 3.4)
from pathlib import Path
HERE = Path(__file__).parent.resolve()
RESTORE_INI = HERE / 'restore.ini'
or (python 2.7)
import os.path
HERE = os.path.abspath(os.path.dirname(__file__))
RESTORE_INI = os.path.join(HERE, 'restore.ini')
if your restore.ini file lives in the same directory as your python script.
then you can use that in
def setUp(self):
self.currentRound = int(config.read_config_co(RESTORE_INI,
'Record')['currentRound'])