You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Pytest will execute all the python files that have the name test_ prepended or _test appended to the name of the script.
Simply type pytest in the directory where the tests are located.
You can run all test in paralle with pytest-xdist. Instal it with pip install pytest-xdist and run it as pytest -n 4.
If you are using mocking make sure you have it installed: pip install pytest-mock.
To have a nice report, install pip install pytest-html and then run pytest --html=report.html
conftest.py
This file helps maintain cleaner and more maintainable test code by centralizing common setup, configuration, and customization logic. It is used to define fixtures, hooks, or configurations that are shared across multiple test files.
Fixtures can be used for both setting up and tearing down resources, as well as for grouping shared pieces of code. They provide a way to encapsulate common setup and teardown logic, making tests cleaner and more maintainable. Additionally, fixtures can help in reducing code duplication by allowing shared code to be defined once and reused across multiple tests.
# test_no_fixiture.pyimportpytestclassDatabase:
def__init__(self):
self.connected=Falsedefconnect(self):
# Simulate connecting to a databaseself.connected=Truedefdisconnect(self):
# Simulate disconnecting from a databaseself.connected=Falsedeftest_database_connection():
# Set up the database connectiondb=Database()
db.connect()
# Ensure that the database is connectedassertdb.connected==True# Tear down the database connectiondb.disconnect()
deftest_database_disconnection():
# Set up the database connectiondb=Database()
db.connect()
# Ensure that the database is connectedassertdb.connected==True# Disconnect from the databasedb.disconnect()
# Ensure that the database is disconnectedassertdb.connected==False
# test_with_fixiture.pyimportpytestclassDatabase:
def__init__(self):
self.connected=Falsedefconnect(self):
# Simulate connecting to a databaseself.connected=Truedefdisconnect(self):
# Simulate disconnecting from a databaseself.connected=False@pytest.fixturedefdatabase():
# Set up the database connectiondb=Database()
db.connect()
returndb# Provide the fixture object to the test# Tear down the database connection#db.disconnect()deftest_database_connection(database):
# Ensure that the database is connectedassertdatabase.connected==Truedeftest_database_disconnection(database):
# Disconnect from the databasedatabase.disconnect()
# Ensure that the database is disconnectedassertdatabase.connected==False
@pytest.parametrize
Consider the scenario where we have 4 different but very similar tests. There is quite a lot of boiler plate going on.
function: The default scope. The fixture is setup/teardown for each test function.
class: The fixture is setup/teardown once per test class.
module: The fixture is setup/teardown once per module.
session: The fixture is setup/teardown once per session (typically the entire test run).
Here's an example that demonstrates the number of setups and teardowns executed using different fixture scopes (function, class, module, and session). We'll use a counter to track the number of times the setup and teardown functions are called.
# test_scopes.pyimportpytestsetup_counter= {
'function': 0,
'class': 0,
'module': 0,
'session': 0
}
teardown_counter= {
'function': 0,
'class': 0,
'module': 0,
'session': 0
}
classDatabase:
def__init__(self):
self.connected=Falsedefconnect(self):
self.connected=Truedefdisconnect(self):
self.connected=False# Fixture with function scope@pytest.fixture(scope="function")defdb_function():
setup_counter['function'] +=1db=Database()
db.connect()
yielddbdb.disconnect()
teardown_counter['function'] +=1# Fixture with class scope@pytest.fixture(scope="class")defdb_class():
setup_counter['class'] +=1db=Database()
db.connect()
yielddbdb.disconnect()
teardown_counter['class'] +=1# Fixture with module scope@pytest.fixture(scope="module")defdb_module():
setup_counter['module'] +=1db=Database()
db.connect()
yielddbdb.disconnect()
teardown_counter['module'] +=1# Fixture with session scope@pytest.fixture(scope="session")defdb_session():
setup_counter['session'] +=1db=Database()
db.connect()
yielddbdb.disconnect()
teardown_counter['session'] +=1# Tests using function scopedeftest_function_scope_1(db_function):
assertdb_function.connecteddeftest_function_scope_2(db_function):
assertdb_function.connected# Tests using class scopeclassTestClassScope:
deftest_class_scope_1(self, db_class):
assertdb_class.connecteddeftest_class_scope_2(self, db_class):
assertdb_class.connected# Tests using module scopedeftest_module_scope_1(db_module):
assertdb_module.connecteddeftest_module_scope_2(db_module):
assertdb_module.connected# Tests using session scopedeftest_session_scope_1(db_session):
assertdb_session.connecteddeftest_session_scope_2(db_session):
assertdb_session.connected# Print setup and teardown countersdeftest_print_counters():
print("\nSetup Counters:", setup_counter)
print("Teardown Counters:", teardown_counter)
MagicMock is not directly used in this specific example, but pytest-mock uses the mock library under the hood.
# test_mocking.pyfromunittest.mockimportMagicMockimportpytestclassDatabase:
defconnect(self):
# Simulate a real database connectionreturn"Connected"deftest_mocking(mocker):
db=Database()
mocker.patch.object(db, 'connect', return_value=True)
# The connect method is now mocked and will return Trueassertdb.connect() ==True
@pytest.mark.skip
@pytest.mark.skip(reason="Skipping this test for now")deftest_to_skip():
assertFalse
@pytest.mark
@pytest.mark.xfail(reason="This test is expected to fail")deftest_expected_to_fail():
assertFalse