Python: Use fixture for unittest.TextTestRunner.run() - python-3.x

Have defined Cases.py file with defined suite:
import unittest
import pytest
from adminzone_tests.Clients import TestClients
def collect_suite():
suite = unittest.TestSuite()
suite.addTest(TestClients)
return suite
#pytest.mark.usefixtures('admin_session')
def run():
unittest.TextTestRunner(verbosity=2).run(collect_suite())
#pytest.mark.usefixtures('client_session')
def run():
unittest.TextTestRunner(verbosity=2).run(collect_suite())
conftest.py file:
import pytest
from base.Common import Common
from base.Users import Users
#pytest.fixture(scope='session')
def admin_session(request):
Users.users('admin')
def admin_session_teardown():
Common.logout()
request.addfinalizer(admin_session_teardown)
#pytest.fixture(scope='session')
def client_session(request):
Users.users('client')
def client_session_teardown():
Common.logout()
request.addfinalizer(client_session_teardown)
Idea: execute the same suite one by one with different user's sessions.
But fixture executes only if has param autouse=True
How to make fixture executable without autouse=True and run the same suite with different setup ?

Finally i found solution that i was looking for, could be useful for somebody:
Used hook with command line args
conftest.py file:
import pytest
def pytest_addoption(parser):
# adding command line arg --user
parser.addoption('--users', action='append', default=['user1'])
def pytest_generate_tests(metafunc):
if 'users' in metafunc.fixturenames:
us = set(metafunc.config.option.users)
if not us:
us = ['user1']
elif 'all' in us:
us = ['user2', 'user1']
else:
us = list(us)
metafunc.parametrize('users', us, scope='session')
#pytest.yield_fixture(scope='session', autouse=True)
def define_users(request, users):
# Execute login here, as example called function with param of user_type
login(users)
def teardown():
# Execute logout here
logout()
request.addfinalizer(teardown)
Cases.py file:
import unittest
suite = unittest.TestSuite()
def collect_suite():
suite.addTest(TestClass1)
# adding more test classes here
def run():
unittest.TextTestRunner(verbosity=2).run(suite)
Now via command line execute command(in project's root dir)
python -m pytest Cases.py -s -v -n auto --users all
Test suite will be executed with login as user1, after will be executed with login as user2

Related

Starting a Flask Server from Within Test Code?

I'm working through the Flasky tutorial from Miguel Grinberg's book Flask Web Development 2e and I've run into a snag with the end-to-end testing in Chapter 15. When I try to run the code I get a console message
* Ignoring a call to 'app.run()' that would block the current 'flask' CLI command.
Only call 'app.run()' in an 'if __name__ == "__main__"' guard.
followed by the browser reporting "Firefox cannot establish a connection..." This suggest to me that the test server is not starting.
Here's the code, from pages 231-233 of the book (the file is tests/test_selenium.py):
import threading
import unittest
from selenium import webdriver
from app import create_app, db, fake
from app.models import Role, User
class SeleniumTestCase(unittest.TestCase):
browser = None
#classmethod
def setUpClass(cls) -> None:
try:
cls.browser = webdriver.Firefox()
except Exception as e:
pass
if cls.browser:
cls.app = create_app('testing')
cls.app_context = cls.app.app_context()
cls.app_context.push()
import logging
logger = logging.getLogger('werkzeug')
logger.setLevel('ERROR')
db.create_all()
Role.insert_roles()
fake.users(10)
fake.posts(10)
admin_role = Role.query.filter_by(permissions=0xff).first()
admin = User(email='john#example.com', password='cat', username='john', role=admin_role, confirmed=True)
db.session.add(admin)
db.session.commit()
cls.server_thread = threading.Thread(
target=cls.app.run,
kwargs={
'debug': 'false',
'use_reloader': False,
'use_debugger': False,
'host': '0.0.0.0',
'port': 5000
}
)
cls.server_thread.start()
#classmethod
def tearDownClass(cls) -> None:
if cls.browser:
cls.browser.get('http://localhost:5000/shutdown')
cls.browser.quit()
cls.server_thread.join()
db.drop_all()
db.session.remove()
cls.app_context.pop()
def setUp(self) -> None:
if not self.browser:
self.skipTest('Web browser not available')
def tearDown(self) -> None:
pass
def test_admin_home_page(self):
self.browser.get('http://localhost:5000/') # fails here
self.assertRegex(self.browser.page_source, 'Hello,\s+Stranger!')
self.fail('Finish the test!')
How can I get a test server up and running from within the test code? (I putzed around with Flask-Testing for a few days before giving it up as unmaintained.)
ADDENDUM: Further experimentation has determined that the problem lies in the explicit call to app.run() conflicting with the Flask CLI's implicit call to app.run(), but without the explicit call the test server doesn't start.
I want to run this from the Flask CLI the same as my unit tests. This means I need to find a way to start the test server after the test database is populated, which happens after the test class's code begins to run. The CLI command code is:
#app.cli.command()
#click.argument('test_names', nargs=-1)
def test(coverage, test_names):
"""Run the unit tests"""
import unittest
if test_names:
tests = unittest.TestLoader().loadTestsFromNames(test_names)
else:
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
so running from __main__ would bypass the tests' load/run sequence.
I found a feasible solution using Timer
import unittest
from threading import Timer
Create two variables on top in your code
timer = None
myapp = None
class ApplicationTest(unittest.TestCase):
Now at the bottom of the file create main method and custom method of timer, I assume startTest as method name
In the main method you can call create_app and put it in global variable and use that myapp variable inside your selenium testing code
unittest.main() will manually trigger your test class and run the test cases one after one, unfortunately the test runs twice, I don't know why
def startTest():
timer.cancel()
unittest.main()
if __name__ == '__main__':
timer = Timer(6.0, startTest)
timer.start()
myapp = create_app()
myapp.run(debug=True, threaded=True)

Unittest for flask app not running as expected

I have created a simple unit test for my flask app, these tests are saved in a different folder, and then called from the manage.py file. However, when I run the tests, none of the test functions is executed.
Here is the test.py file:
from flask import current_app
import unittest
from app import create_app, db
class BasicTestCase(unittest.TestCase):
def setUp(self):
self.app=create_app('testing')
self.app_context=self.app.app_context()
self.app_context.push()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_app_exists(self):
self.assertFalse(current_app is None)
def test_app_is_testing(self):
self.assertTrue(current_app.config['TESTING'])
Also here is the manage.py file.
from app import create_app
app=create_app('default')
def test():
import unittest
tests=unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
if __name__=='__main__':
app.run(debug=True)
When I run the test() function from the command line, I get the following response.
`----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK`
Is there something is am doing wrong?

Fixture not found pytest

Hey I got a simple test where the fixure is not found. I am writting in vsc and using windows cmd to run pytest.
def test_graph_add_node(test_graph):
E fixture 'test_graph' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
This is the error I get, here is the test code:
import pytest
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'giddeon1.settings')
import django
django.setup()
from graphs.models import Graph, Node, Tag
#pytest.fixture
def test_graph():
graph = Graph.objects.get(pk='74921f18-ed5f-4759-9f0c-699a51af4307')
return graph
def test_graph():
new_graph = Graph()
assert new_graph
def test_graph_add_node(test_graph):
assert test_graph.name == 'Test1'
im using python 3.9.2, pytest 6.2.5.
I have see some similar questions but they all handle wider or bigger problems.
You appear to be defining test_graph twice, which means that the second definition will overwrite the first. And you added #pytest.fixture to a test_ method when you used it, but #pytest.fixture should be added to non test methods so that tests can use that fixture. Here's how the code should probably look:
import pytest
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'giddeon1.settings')
import django
django.setup()
from graphs.models import Graph, Node, Tag
#pytest.fixture
def graph():
graph = Graph.objects.get(pk='74921f18-ed5f-4759-9f0c-699a51af4307')
return graph
def test_graph():
new_graph = Graph()
assert new_graph
def test_graph_add_node(graph):
assert graph.name == 'Test1'
Above, the first method has been renamed to graph so that the next method doesn't override it (and now #pytest.fixture is applied to a non-test method). Then, the 3rd method uses the graph fixture. Make any other changes as needed.

Pytest: no tests ran

I have the following class file and a corresponding test file
dir.py:
import os
class Dir:
def __init__(self, path=''):
self.path = path
#property
def path(self):
return self._path
#path.setter
def path(self, path):
abspath = os.path.abspath(path)
if abspath.exists():
self._path = path
else:
raise IOError(f'{path} does not exist')
and dir_test.py:
import unittest
from ..dir import Dir
class TestDir(unittest.TestCase):
def IOErrorIfPathNotExists(self):
with self.assertRaises(IOError):
Dir.path = "~/invalidpath/"
with self.assertRaises(IOError):
Dir('~/invalidpath/')
if __name__ == "__main__":
unittest.main()
but when I run
pytest -x dir_test.py
it just prints no tests ran in 0.01 seconds
and I have no idea why. It is my first time using pytest except with exercises from exercism.io, and I can't spot any difference to their test files.
I am running it in a virtual environment (Python 3.6.5), with pytest and pytest-cache installed via pip.
That's because your test method is not named properly.
By default, pytest will consider any class prefixed with Test as a test collection.
Yours is TestDir, this matches.
By default, pytest will consider any function prefixed with test as a test.
Yours is IOErrorIfPathNotExists, which does not start with test and is not executed.
Source.

custom config variable with pytest

AIM - I am trying to pass a config variable 'db_str' to my pytest script (test_script.py)
The db_str variable is defined in development.ini
I have tried using command
pytest -c development.ini regression_tests/test_script.py
But it didn't work
Error
> conn_string = config['db_string']
KeyError: 'db_string'
I tried using conftest.py, but didn't work
#contest.py code
import pytest
def pytest_addoption(parser):
parser.addoption("--set-db_st",
action="store",help="host='localhost' dbname='xyz' user='portaladmin'")
#pytest.fixture
def db_str(request):
return request.config.getoption("--set-db_str")
Pytest code
from S4M_pyramid.modelimport MyModel
from S4M_pyramid.lib.deprecated_pylons_globals import config
import subprocess
config['db_str'] = db_str
def test_get_dataset_mapping_id():
result = MyModel.get_dataset_mapping_id()
assert len(result) >1
How can I pass variable 'db_str' from development.ini or any other ini file to pytest script
The logic is the fillowing:
Define CLI argument that will be used to pass information about environment/config file
Get CLI argument value in pytest fixture
Parse config file
Use parsed config in get_database_string fixture to get database connection string
Use get_database_string fixture in your tests to get connection string
conftest.py
import os
from configparser import ConfigParser
# in root of the project there is file project_paths.py
# with the following code ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
import project_paths
def pytest_addoption(parser):
"""Pytest hook that defines list of CLI arguments with descriptions and default values
:param parser: pytest specific argument
:return: void
"""
parser.addoption('--env', action='store', default='development',
help='setup environment: development')
#pytest.fixture(scope="function")
def get_database_string(get_config):
"""Fixture that returns db_string
:param get_config: fixture that returns ConfigParser object that access
to config file
:type: ConfigParser
:return: Returns database connection string
:rtype: str
"""
return get_config['<section name>']['db_string']
#pytest.fixture(scope="function")
def get_config(request):
"""Functions that reads and return ConfigParser object that access
to config file
:rtype: ConfigParser
"""
environment = request.config.getoption("--env")
config_parser = ConfigParser()
file_path = os.path.join(project_paths.ROOT_DIR, '{}.ini'.format(environment))
config_parser.read(file_path)
return config_parser
test_file.py
import pytest
def test_function(get_database_string)
print(get_database_string)
>>> <data base string from development.ini>
As described on pytest_addoption:
add options:
To add command line options, call parser.addoption(...).
To add ini-file values call parser.addini(...).
get options:
Options can later be accessed through the config object, respectively:
config.getoption(name) to retrieve the value of a command line option.
config.getini(name) to retrieve a value read from an ini-style file.
conftest.py:
def pytest_addoption(parser):
parser.addini('foo', '')
test.py:
def test_func(request):
request.config.getini('foo')

Resources