Object Model¶
In aetest
scripts, sections are defined via the use of Python classes and
class methods. During runtime, aetest
infrastructure discovers these
sections based on their class inheritance & decorator markers, sorts them to
their appropriate run order, and instantiates them into their corresponding
instance objects.
This section provides insight on how this delicate process is carried out.
TestScript Class¶
aetest
testscripts are simply standard python files with .py
extension.
They are called aetest
testscripts because they import the AEtest module
(from pyats import aetest
), and has section definitions such as
CommonSetup
, Testcase
, and CommonCleanup
.
In Python, .py
files are imported into Python as module objects. During
aetest
execution, the infrastructure internally wraps the running testscript
module into a TestScript
class instance.
# Example
# -------
#
# what this means...
# assuming there's a script called l3vpn_traffic.py
# importing some helper modules for demonstration
import types
# import the script as a module
import l3vpn_traffic
# l3vpn_traffic is a module
assert isinstance(l3vpn_traffic, types.ModuleType)
# True
# TestScript is created with the module as input:
from pyats.aetest.script import TestScript
testscript = TestScript(module = l3vpn_traffic)
This class and its object instance during execution is not normally directly
accessible by the user (unless through Reserved Parameters). However, it
is still important for users to know about this class, as its instances enables
testscripts to have an overall result, where Script Arguments are stored as
TestScript
parameters, and is where all major script section’s parent
attribute points to (eg, Testcase.parent
).
+--------------------------------------------------------------------------+
| TestScript (base cls: TestItem) |
+==========================================================================+
| attribute | description |
|-----------------+--------------------------------------------------------|
| module | testscript module object |
| source | full path/name of the testscript file |
+==========================================================================+
| properties | description |
|-----------------+--------------------------------------------------------|
| uid | name of the testscript |
| description | testscript header (module docstring) |
| result | rolled-up result of entire testscript |
| parameters | dictionary of parameters relative to this testscript |
+--------------------------------------------------------------------------+
Container Classes¶
CommonSetup
, Testcase
and CommonCleanup
are the 3 different
container classes in aetest
. They are called container classes because they
“house” other test sections, and all inherit from the base TestContainer
.
+--------------------------------------------------------------------------+
| CommonSetup, CommonCleanup, Testcase (base cls: TestContainer) |
+==========================================================================+
| attribute | description |
|-----------------+--------------------------------------------------------|
| source | file/line information where the class was defined |
+==========================================================================+
| properties | description |
|-----------------+--------------------------------------------------------|
| uid | common_setup/common_cleanup, or uid of Testcase |
| description | class header (docstring) |
| parent | TestScript object |
| result | rolled-up result of this section |
| parameters | dictionary of parameters relative to this section |
+--------------------------------------------------------------------------+
Container classes are seen directly in the user script: they are inherited directly within testscripts to define their corresponding sections.
# Example
# -------
#
# defining container classes
from pyats import aetest
# inheriting CommonSetup
# contains subsections
class MyCommonSetup(aetest.CommonSetup):
'''
this is the description for CommonSetup
'''
@aetest.subsection
def subsection_one(self):
self.a = 1
print('hello world')
@aetest.subsection
def subsection_two(self):
assert self.a == 1
# inheriting Testcase
# contains setup/test/cleanup
class MyTestcase(aetest.Testcase):
'''
this is the description for Testcase
'''
@aetest.test
def test_one(self):
print('inside testcase: %s' % self.uid)
Container classes instances are iterables. Iterating through them yields each of its enclosed sections. This could be useful during iterative script development and testing.
# Example
# -------
#
# looping on container class instances
from pyats import aetest
# define a container & some inhabitants
class MyCommonSetup(aetest.CommonSetup):
@aetest.subsection
def subsection_one(self):
self.a = 1
print('hello world')
@aetest.subsection
def subsection_two(self):
assert self.a == 1
# let's instantiate the class
common_setup = MyCommonSetup()
# loop through to see what we get:
for i in common_setup:
print(i)
# subsection_one
# subsection_two
Container class instances are callable. Calling a container class instance effectively runs it, and the result object is returned.
# Example
# -------
#
# calling container class instance
# continuing from last example..
# run the common setup section instance
# save into result variable
result = common_setup()
# hello world
# print the result variable
print(result)
# passed
Function Classes¶
Subsection
, SetupSection
, TestSection
and CleanupSection
classes
are function classes: they carry out a specific test function, and inherits
from the base TestFunction
class.
Function classes and their object instances during execution is not normally
directly accessible by the user (unless through Reserved Parameters).
However, they are still important because they correspond directly to aetest
decorators. Their object instances are short-lived: they are created internally
during runtime, right before the execution of the section it represents, and
only lives until the end of that test section.
+--------------------------------------------------------------------------+
| Subsection, [Setup|Test|Cleanup]Section (base cls: TestFunction) |
+==========================================================================+
| attribute | description |
|-----------------+--------------------------------------------------------|
| function | function/method that was decorated to be this section |
| source | file/line information where the method was defined |
+==========================================================================+
| properties | description |
|-----------------+--------------------------------------------------------|
| uid | name of the function/method |
| description | function header (docstring) |
| parent | container (CommonSetup/Testcase/CommonCleanup) |
| result | rolled-up result of this test function |
| parameters | dictionary of parameters relative to this function |
+--------------------------------------------------------------------------+
Internally, when any class method is decorated with aetest
section
decorators, they are instantiated into their corresponding function class. This
allow the infrastructure to open & close each section’s reporting context,
enables result tracking, and a multitude of of other feature sets specific to
test section methods.
# Example
# -------
#
# peeking into function class internals
from pyats import aetest
class MyCommonSetup(aetest.CommonSetup):
# subsection corresponds to Subsection cls
@aetest.subsection
def subsection_one(self):
pass
class MyTestcase(aetest.Testcase):
# setup corresponds to SetupSection cls
@aetest.setup
def setup(self):
pass
# test corresponds to TestSection cls
@aetest.test
def test_one(self):
pass
# cleanup corresponds to CleanupSection cls
@aetest.cleanup
def cleanup(self):
pass
# when container instances are iterated,
# the returned objects are function class instances
tc = MyTestcase()
for obj in tc:
print(type(obj))
print(obj.function)
# <class 'pyats.aetest.sections.SetupSection'>
# <bound method MyTestcase.setup of <class 'MyTestcase' uid='MyTestcase'>>
# <class 'pyats.aetest.sections.TestSection'>
# <bound method MyTestcase.test_one of <class 'MyTestcase' uid='MyTestcase'>>
# <class 'pyats.aetest.sections.CleanupSection'>
# <bound method MyTestcase.cleanup of <class 'MyTestcase' uid='MyTestcase'>>
Base Classes¶
The following are a description of internal, base classes within aetest
.
You are not required to know any of these for normal script development. They
are here for power-user references only.
TestItem Class¶
TestItem
is the base class of all other classes within aetest
: all other
classes inherit from it, thus establishing the basic properties of all
aetest
object/classes:
objects must have
uid
anddescription
objects may have a
parent
: the container where this object is housed inobjects must have
result
objects have
parameters
: a dictionary of key-value pairs that affects the behavior of testing. This includes parameters that are specific to thisTestItem
, and all of its parent’s parameters.
+--------------------------------------------------------------------------+
| TestItem Objects |
+==========================================================================+
| properties | description |
|-----------------+--------------------------------------------------------|
| uid | item unique id string, cannot contain spaces |
| description | item descriptive text |
| parent | parent object where this object is contained in |
| result | item test result object |
| parameters | dictionary of parameters related to this TestItem, |
| | a summation of parameters specific to itself, and all |
| | of its parent's parameters |
+--------------------------------------------------------------------------+
TestContainer Class¶
TestContainer
class extends the base TestItem
and serves as a base class
to all testscript’s major sections such as Testcase
. The only technical
difference between TestContainer
and TestItem
is at the implementation
level:
TestContainer
may contain otherTestItem
iterating a
TestContainer
instance yields its childTestItem
executing a
TestContainer
executes all childTestItem
.
All other model behavior should be the exact same.