Testresources: Unittest Extension for Resource Management
Testresources extends Python's `unittest` module with a clean and simple API to optimize tests by managing expensive, common resources. This includes scenarios like setting up sample working directories for version control systems, establishing reference databases, or spinning up web servers. The library is currently at version 2.0.2, with its latest release in April 2025, indicating active maintenance.
Common errors
-
AttributeError: 'NoneType' object has no attribute 'some_method'
cause A custom `TestResourceManager`'s `make()` method (or `getResource()` in older terms) returned `None`, which `testresources` doesn't inherently flag as an error, leading to tests trying to use a `None` object.fixReview your `TestResourceManager` implementation. Ensure its `make()` method always returns a valid, non-`None` object representing the resource, or explicitly raises an exception if the resource cannot be created. -
MyClass.setUpClass was never called / MyModule.setUpModule was never called
cause You are using `OptimisingTestSuite` to run your tests, which flattens test suites and bypasses the `setUpClass` and `setUpModule` hooks from standard `unittest` classes.fixRefactor your class or module-level setup logic to be handled by a `testresources.TestResourceManager`. Define your resource manager, then declare it in your `ResourcedTestCase`'s `resources` attribute to leverage `testresources`'s optimized lifecycle management. -
Resource is re-created for every test despite using OptimisingTestSuite.
cause The `testresources` framework re-creates or resets resources if it determines they are 'dirty'. This often happens if `TestResourceManager.isDirty()` is not correctly implemented or if `resource.dirtied()` is not called after a test modifies a shared resource.fixIf your resource can be reused without full recreation, implement `isDirty(self, resource)` in your `TestResourceManager` to return `False` if the resource is in a reusable state. If tests modify the resource and it needs to be reset, call `self.resource_manager.dirtied(self.resource)` within the test method to signal a state change.
Warnings
- breaking When using `OptimisingTestSuite`, `unittest.TestCase.setUpClass` and `unittest.TestCase.setUpModule` are bypassed. The suite is flattened for resource optimization, meaning these class/module-level setup methods will not run.
- gotcha If a `TestResourceManager.make()` method successfully completes but returns `None`, `testresources` does not consider this an error. This can lead to `AttributeError` in tests attempting to use the `None` resource.
- gotcha Dynamically requesting resources inside a test method is generally discouraged. `testresources` is designed for statically declared resources to allow for optimal test ordering and resource reuse.
Install
-
pip install testresources
Imports
- ResourcedTestCase
from testresources import ResourcedTestCase
- TestResourceManager
from testresources import TestResourceManager
- OptimisingTestSuite
from testresources import OptimisingTestSuite
- TestResource
from testresources import TestResource
Quickstart
import unittest
from testresources import TestResource, ResourcedTestCase, OptimisingTestSuite
# 1. Define your expensive resource and how to set it up/tear it down
class DatabaseResource(TestResource):
def make(self):
print("\n--- Creating database connection ---")
# Simulate a database connection object
self.connection = {'host': 'localhost', 'port': 5432, 'db': 'test'}
return self.connection
def clean(self, connection):
print("--- Closing database connection ---")
self.connection = None
# Optional: Implement isDirty if the resource can become dirty and needs resetting
# def isDirty(self, connection):
# return False # Assume it's always clean for this example
# Create a singleton instance of the resource manager
db_resource_manager = DatabaseResource()
# 2. Define your test case, inheriting from ResourcedTestCase
class MyDatabaseTest(ResourcedTestCase):
# Declare the resources needed by tests in this class
# The resource will be assigned to self.db_conn
resources = [('db_conn', db_resource_manager)]
def test_query_data(self):
print(f"Test 1: Querying data with {self.db_conn}")
self.assertIn('host', self.db_conn)
def test_insert_data(self):
print(f"Test 2: Inserting data with {self.db_conn}")
# Simulate modifying the resource, then mark it dirty if necessary
# db_resource_manager.dirtied(self.db_conn) # Uncomment if isDirty is implemented
self.assertEqual(self.db_conn['db'], 'test')
def test_another_query(self):
print(f"Test 3: Another query with {self.db_conn}")
self.assertTrue(self.db_conn['port'] == 5432)
# 3. Use OptimisingTestSuite to run your tests efficiently
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(MyDatabaseTest))
# Wrap the suite in OptimisingTestSuite
optimised_suite = OptimisingTestSuite(suite)
# Run the tests
runner = unittest.TextTestRunner(verbosity=2)
runner.run(optimised_suite)