I'm using pytest to write some unit tests.
I know I can access the tmp_path temporary directory in any test or fixture, but is it possible to access it in the pytest_sessionstart() method too?
Essentially, this is a sample of what I'm trying to achieve
def pytest_sessionstart(session, tmp_path):
"""Create hello.txt before any test is ran and make available to all tests"""
p = tmp_path.join("hello.txt")
p.write("content")
Thanks
The recommended way to create a temp file for all the tests is to use a session scoped fixture with the inbuilt tmp_path_factory fixture.
From pytest docs :
# contents of conftest.py
import pytest
#pytest.fixture(scope="session")
def image_file(tmp_path_factory):
img = compute_expensive_image()
fn = tmp_path_factory.mktemp("data").join("img.png")
img.save(str(fn))
return fn
# contents of test_image.py
def test_histogram(image_file):
img = load_image(image_file)
# compute and test histogram
Related
In py.test I need to dynamically define tests, depending on tests I defined in a file.
So what I was thinking is to define a fixture in conftest.py that reads the file and returns a dictionary with the tests.
File tests.json:
{
"test1": "text",
"test2": "42",
"test3": 1
}
I then define a fixture in conftest.py to return the dictionary with the tests:
def pytest_addoption(parser):
parser.addoption(
"--tests",
default="tests.json",
)
#pytest.fixture
def mytests(request):
testfile = request.config.getoption("--tests")
with open(testfile) as f:
tests = json.load(f)
return tests
and then I can use a parametrized test as follows in test_pytest.py:
#pytest.mark.parametrize("test_name", [(key) for key, value in mytests.items()])
def test1(test_name):
print(test_name)
which does not work as, at this point, py.test does not seem to 'know' that mytests is a fixture. I get an error
E NameError: name 'mytests' is not defined
How to handle this correctly? I just want to be able to either run all the test that are defined in the json file, or to be able to select a single test from it with the -k option if py.test.
How to do it?
Based on some comments given below I tried to implement something as follows:
#pytest.hookimpl
def pytest_generate_tests(metafunc):
if "myparam" in metafunc.fixturenames:
with open(metafunc.config.option.tests) as f:
tests = json.load(f)
# add parametrization for each fixture name
for name, value in tests.items():
print(name, value)
metafunc.parametrize("mparam", (name, value))
def test1(myparam):
print(myparam)
But with this I got an error
ERROR test_pytest.py - ValueError: duplicate 'myparam'
As mentioned in the comments, you cannot use a fixture in mark.parametrize. Fixtures can only be used in test functions and other fixtures.
To have dynamic parametrization like in this case, you can implement the hook function pytest_generate_tests:
#pytest.hookimpl
def pytest_generate_tests(metafunc):
if "test_name" in metafunc.fixturenames:
testfile = metafunc.config.getoption("--tests")
with open(testfile) as f:
tests = json.load(f)
metafunc.parametrize("test_name", tests.items())
def test1(test_name):
print(test_name)
This will parametrize all tests with a "test_name" argument (e.g. fixture) with the items in the config file.
Running this with the given json file will result in something like:
$ python -m pytest -s
...
collected 3 items
test_pytest.py ('test1', 'text')
.('test2', '42')
.('test3', 1)
.
I have a method, which contains external rest-api calls.
ex:
def get_dataset():
url=requests.get("http://api:5001/get_trainingdata")
filename=url.text[0]
return filename
When I do #patch for this function, I can able to do unittest. But, coverage in not covering whole function.
How can write unittest case for this method with full coverage?
My testcase
#mock.patch('api.get_dataset')
def test_using_decorator1(self, mocked_get_dataset):
file = [{"file":"ddddd"}]
mocked_get_dataset.return_value = Mock()
mocked_get_dataset.return_value.json.return_value = file
filename = file[0]
self.assertEqual(filename, file[0])
I have a unit tests class that is testing what is inside a txt file. I am using the tmpdir fixture with pytest. This is my current class:
from objects.TicketCounter import TicketCounter
from objects.ConfigReader import ConfigReader
import os
import pytest
class TestTicketCounter():
# #pytest.fixture(scope="module") #<---Could I use this instead of passing tmpdir each time?
# def my_filepath(self, tmpdir):
# return tmpdir.mkdir("sub").join("testCurrentTicketCount.txt")
def test_createNewTicketCountFile(self, tmpdir):
x = tmpdir.mkdir("sub").join("testCurrentTicketCount.txt") #<----Repeated
ticketCounter = TicketCounter(x)
assert os.path.getsize(x) > 0
def test_addOneTicketCounter(self, tmpdir):
x = tmpdir.mkdir("sub").join("testCurrentTicketCount.txt") #<----Repeated
ticketCounter = TicketCounter(x)
beforeCount = int(ticketCounter.readTicketCountFromFile())
ticketCounter.addOneTicketCounter()
afterCount = int(ticketCounter.readTicketCountFromFile())
assert beforeCount + 1 == afterCount
def test_readTicketCountFromFile(self, tmpdir):
x = tmpdir.mkdir("sub").join("testCurrentTicketCount.txt") #<----Repeated
ticketCounter = TicketCounter(x)
print(ticketCounter.readTicketCountFromFile())
assert int(ticketCounter.readTicketCountFromFile()) >= 0
I would like to get rid of the repeated code and pass in the same path each time with the fixture that I commented out, my_filepath. When I try to use the my_parser pytest fixture, I am getting an error, saying:
ScopeMismatch: You tried to access the 'function' scoped fixture 'tmpdir' with a 'module' scoped request object, involved factories
unit_tests\test_TicketCounter.py:12:
So you are not able to use tmpdir with a pytest fixture? Is it because tmpdir is a fixture?
Any thoughts on how I could cut out the repeated code and use a function or fixture to pass the path?
As the error message says, tmpdir is a function based fixture, e.g. it creates a new temp dir for each test, and deletes it after the test. Therefore you cannot use it in a module scoped fixture, that is instantiated only once after module load. If you could do that, your temp dir would be removed after the first test, and you would not be able to access it in the next test.
In your current code, the tmpdir fixture is used as a function scoped fixture, so a new directory is created for each test - what is usually wanted. You can use your fixture without problems if you remove the module scope:
#pytest.fixture
def my_filepath(self, tmpdir):
return tmpdir.mkdir("sub").join("testCurrentTicketCount.txt")
If you want to use the same temp dir in each test for some reason, you cannot use the tmpdir fixture. In this case you just can create your own tmp dir, for example:
import os
import tempfile
import shutil
#pytest.fixture(scope="module")
def my_filepath(self):
tmpdir = tempfile.mkdtemp()
subdir = os.path.join(tmpdir, "sub")
os.mkdir(subdir)
yield os.path.join(subdir, "testCurrentTicketCount.txt")
shutil.rmtree(tmpdir)
The thing is that I need to have a conftest with a special setting for a set of tests. To use a live database in readonly mode only for these tests. Other tests from unit folder working with an empty db created by pytest. If I run pytest tests or pytest tests/unit conftest from tests_readonly_db are not recognized. It is recognized when I put conftest file in unit folder. And run any of these two commands.
But it works perfectly when I run pytest tests/unit/tests_readonly_db with conftest in tests_readonly_db folder. Also tried to creat another subfolder in tests_readonly_db, put tests in there, with conftest.py staying level up. Doesn't work.
I wonder if there is way to implement desired behaviour. Found some related answers, but couldn't fully understand how it helps in my case. For example:
https://stackoverflow.com/a/13686206/7744657
But if
conftest.py files from sub directories are by default not loaded at tool startup.
Why it was loaded from a unit folder?
conftest.py
import pytest
#pytest.fixture(scope='session')
def django_db_setup():
"""Avoid creating/setting up the test database"""
pass
#pytest.fixture
def db_access_without_rollback_and_truncate(request, django_db_setup, django_db_blocker):
django_db_blocker.unblock()
request.addfinalizer(django_db_blocker.restore)
App structure:
EDIT
I've just tested this with a simple and stupid fixture. And it works. So it's actually a problem with this specific database settings not being discovered in some cases. This simple fixture seems to work in any case.
import pytest
#pytest.fixture(scope='session')
def django_db_setup():
"""Avoid creating/setting up the test database"""
pass
#pytest.fixture
def db_access_without_rollback_and_truncate(request, django_db_setup, django_db_blocker):
django_db_blocker.unblock()
request.addfinalizer(django_db_blocker.restore)
#pytest.fixture
def msg():
return 'AAAAAAAAAAA----------------------------------------AAAAAAAAAA'
tests_views.py
#pytest.mark.django_db()
#pytest.mark.parametrize('uri, args', [
some parameters.....
])
def test_views(uri, args, client, msg):
print(msg)
username = "john doe"
password = "123456"
client.login(username=username, password=password)
if args:
response = client.get(reverse(uri, args=args))
else:
response = client.get(reverse(uri))
assert response.status_code == 200
I am using Pytest to test an executable. This .exe file reads a configuration file on startup.
I have written a fixture to spawn this .exe file at the start of each test and closes it down at the end of the test. However, I cannot work out how to tell the fixture which configuration file to use. I want the fixture to copy a specified config file to a directory before spawning the .exe file.
#pytest.fixture
def session(request):
copy_config_file(specific_file) # how do I specify the file to use?
link = spawn_exe()
def fin():
close_down_exe()
return link
# needs to use config file foo.xml
def test_1(session):
session.talk_to_exe()
# needs to use config file bar.xml
def test_2(session):
session.talk_to_exe()
How do I tell the fixture to use foo.xml for test_1 function and bar.xml for test_2 function?
Thanks
John
One solution is to use pytest.mark for that:
import pytest
#pytest.fixture
def session(request):
m = request.node.get_closest_marker('session_config')
if m is None:
pytest.fail('please use "session_config" marker')
specific_file = m.args[0]
copy_config_file(specific_file)
link = spawn_exe()
yield link
close_down_exe(link)
#pytest.mark.session_config("foo.xml")
def test_1(session):
session.talk_to_exe()
#pytest.mark.session_config("bar.xml")
def test_2(session):
session.talk_to_exe()
Another approach would be to just change your session fixture slightly to delegate the creation of the link to the test function:
import pytest
#pytest.fixture
def session_factory(request):
links = []
def make_link(specific_file):
copy_config_file(specific_file)
link = spawn_exe()
links.append(link)
return link
yield make_link
for link in links:
close_down_exe(link)
def test_1(session_factory):
session = session_factory('foo.xml')
session.talk_to_exe()
def test_2(session):
session = session_factory('bar.xml')
session.talk_to_exe()
I prefer the latter as its simpler to understand and allows for more improvements later, for example, if you need to use #parametrize in a test based on the config value. Also notice the latter allows to spawn more than one executable in the same test.