torment.fixtures
— Torment Fixtures¶
Fixture¶
-
class
torment.fixtures.
Fixture
(context: 'torment.TestContext') → None[source]¶ Collection of data and actions for a particular test case.
Intended as a base class for custom fixtures. Fixture provides an API that simplifies writing scalable test cases.
Creating Fixture objects is broken into two parts. This keeps the logic for a class of test cases separate from the data for particular cases while allowing re-use of the data provided by a fixture.
The first part of Fixture object creation is crafting a proper subclass that implements the necessary actions:
__init__: pre-data population initialization Initialize: post-data population initialization Setup: pre-run setup Run: REQUIRED—run code under test Check: verify results of run Note
initialize
is run during__init__
and setup is run after; otherwise, they serve the same function. The split allows different actions to occur in different areas of the class heirarchy and generally isn’t necessary.By default all actions are noops and simply do nothing but run is required. These actions allow complex class hierarchies to provide nuanced testing behavior. For example, Fixture provides the absolute bare minimum to test any Fixture and no more. By adding a set of subclasses, common initialization and checks can be performed at one layer while specific run decisions and checks can happen at a lower layer.
The second part of Fixture object creation is crafting the data. Tying data to a Fixture class should be done with
torment.fixtures.register
. It provides a declarative interface that binds a dictionary to a Fixture (keys of dictionary become Fixture properties).torment.fixtures.register
creates a subclass that the rest of the torment knows how to transform into test cases that are compatible with nose.Examples
Simplest Fixture subclass:
class MyFixture(Fixture): pass
Of course, to be useful the Fixture needs definitions of setup, run, and check that actually test the code we’re interested in checking:
def add(x, y): return x + y class AddFixture(Fixture): def run(self): self.result = add(self.parameters['x'], self.parameters['y']) def check(self): self.context.assertEqual(self.result, self.expected)
This fixture uses a couple of conventions (not requirements):
self.parameters
as a dictionary of parameter names to valuesself.expected
as the value we expect as a resultself.result
as the holder inside the fixture betweenrun
andcheck
This show-cases the ridiculity of using this testing framework for simple functions that have few cases that require testing. This framework is designed to allow many cases to be easily and declaritively defined.
The last component required to get these fixtures to actually run is hooking them together with a context:
from torment import contexts class AddUnitTest(contexts.TestContext, metaclass = contexts.MetaContext): fixture_classes = ( MyFixture, AddFixture, )
The context that wraps a Fixture subclass should eventually inherit from TestContext (which inherits from
unittest.TestCase
and provides its assert methods). In order for nose to find and execute thisTestContext
, it must have a name that contains Test.Properties
category
description
(override)name
(do not override)
Methods To Override
__init__
check
initialize
run (required)
setup
Instance Variables
Context: the torment.TestContext
this case is running in which provides the assertion methods ofunittest.TestCase
.-
category
¶ Fixture’s category (the containing testing module name)
Examples
Module: test_torment.test_unit.test_fixtures.fixture_a44bc6dda6654b1395a8c2cbd55d964d Category: fixtures
-
check
() → None[source]¶ Check that run ran as expected.
Note
Override as necessary. Default provided so re-defenition is not necessary.
Called after
run
and should be used to verify that run performed the expected actions.
-
description
¶ Test name in nose output (intended to be overridden).
-
initialize
() → None[source]¶ Post-data population initialization hook.
Note
Override as necessary. Default provided so re-defenition is not necessary.
Called during
__init__
and after properties have been populated bytorment.fixtures.register
.
-
name
¶ Method name in nose runtime.
Registration¶
-
torment.fixtures.
register
(namespace, base_classes: typing.Tuple, properties: typing.Dict) → None[source]¶ Register a Fixture class in namespace with the given properties.
Creates a Fixture class (not object) and inserts it into the provided namespace. The properties is a dict but allows functions to reference other properties and acts like a small DSL (domain specific language). This is really just a declarative way to compose data about a test fixture and make it repeatable.
Files calling this function are expected to house one or more Fixtures and have a name that ends with a UUID without its hyphens. For example: foo_38de9ceec5694c96ace90c9ca37e5bcb.py. This UUID is used to uniquely track the Fixture through the test suite and allow Fixtures to scale without concern.
Parameters
Namespace: dictionary to insert the generated class into Base_classes: list of classes the new class should inherit Properties: dictionary of properties with their values Properties can have the following forms:
Functions: invoked with the Fixture as it’s argument Classes: instantiated without any arguments (unless it subclasses torment.fixtures.Fixture
in which case it’s passed context)Literals: any standard python type (i.e. int, str, dict) Note
function execution may error (this will be emitted as a logging event). functions will continually be tried until they resolve or the same set of functions is continually erroring. These functions that failed to resolve are left in tact for later processing.
Properties by the following names also have defined behavior:
Description: added to the Fixture’s description as an addendum Error: must be a dictionary with three keys: :class: class to instantiate (usually an exception) :args: arguments to pass to class initialization :kwargs: keyword arguments to pass to class initialization Mocks: dictionary mapping mock symbols to corresponding values Properties by the following names are reserved and should not be used:
- name