How to pass a value to a Pytest fixture - python

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.

Related

pytest: How to access tmp_path in pytest_sessionstart()?

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

Python Unit test for method contains request

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])

Run pytest for each file in directory

I'm trying to build a routine that calls a Pytest class for each PDF document in current directoy... Let me explain
Lets say i have this test file
import pytest
class TestHeader:
#asserts...
class TestBody:
#asserts...
This script needs to test each pdf document in my cwd
Here is my best attempt:
import glob
import pytest
class TestHeader:
#asserts...
class TestBody:
#asserts...
filelist = glob.glob('*.pdf')
for file in filelist:
#magically call pytest for each file
How would i approach this?
EDIT: Complementing my question.
I have a huge function that extracts each document's data, lets call it extract_pdf
this function returns a tuple (header, body).
Current attempt looks like this:
import glob
import pytest
class TestHeader:
#asserts...
class TestBody:
#asserts...
filelist = glob.glob('*.pdf')
for file in filelist:
header, body = extract_pdf(file)
pytest.main(<pass header and body as args for pytest>)
I need to parse each document prior to testing. Can it be done this way?
The best way to do this through parameterization of the testcases dynamically..
This can be achieved using the pytest_generate_tests hook..
def pytest_generate_tests(metafunc):
filelist = glob.glob('*.pdf')
metafunc.parametrize("fileName", filelist )
NOTE: fileName should be one of the argument to your test function.
This will result in executing the testcase for each of the file in the directory and the testcase will be like
TestFunc[File1]
TestFunc[File2]
TestFunc[File3]
.
.
and so on..
This is expanding on the existing answer by #ArunKalirajaBaskaran.
The problem is that you have different test classes that want to use the same data, but you want to parse the data only once. If it is ok for you to read all data at once, you could read them into global variables and use these for parametrizing your tests:
def extract_data():
filenames = []
headers = []
bodies = []
for filename in glob.glob('*.pdf'):
header, body = extract_pdf(filename)
filenames.append(filename)
headers.append(header)
bodies.append(body)
return filenames, headers, bodies
filenames, headers, bodies = extract_data()
def pytest_generate_tests(metafunc):
if "header" in metafunc.fixturenames:
# use the filename as ID for better test names
metafunc.parametrize("header", headers, ids=filenames)
elif "body" in metafunc.fixturenames:
metafunc.parametrize("body", bodies, ids=filenames)
class TestHeader:
def test_1(header):
...
def test_2(header):
...
class TestBody:
def test_1(body):
...
This is the same as using
class TestHeader:
#pytest.mark.parametrize("header", headers, ids=filenames)
def test_1(header):
...
#pytest.mark.parametrize("header", headers, ids=filenames)
def test_2(header):
...
pytest_generate_tests just adds a bit of convenience so you don't have to repeat the parametrize decorator for each test.
The downside of this is of course that you will read in all of the data at once, which may cause a problem with memory usage if there is a lot of files. Your approach with pytest.main will not work, because that is the same as calling pytest on the command line with the given parameters. Parametrization can be done at the fixture level or on the test level (like here), but both need the parameters alreay evaluated at load time, so I don't see a possibility to do this lazily (apart from putting it all into one test). Maybe someone else has a better idea...

How do I use tmpdir with my pytest.fixture?

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)

Database read only settings are not loaded from a conftest.py in a subfolder

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

Categories