Test Bench Development

A test bench or testing workbench is a virtual environment used to verify the correctness or soundness of a design or model.

A customized test bench is the best way to approach Python development. Consider a Python test bench as simply an executable .py file that loads all the packages, modules & dependencies and provides a self-sufficient development environment, where users can iteratively develop and test their code.

In the case of pyATS, a successful test bench should include the following components:

  • import all necessary packages & modules

  • testbed devices, and live connections to testbed devices

  • interactive development/prompt

  • iterative code & test, with the final code pluggable into a deliverable

A successful test bench bridges the gap in between knowing the Python language, and designing/writing a first library/testscript in pyATS.

Iterative Development

Everyone can elect to write their own code test bench. Here’s an idea/template that could help you to get started. Keep in mind that the primary goal is to have something where you can code & test what you’ve just written without having to re-run your entire test script, which can be costly when you are loading large number of libraries and connecting to multiple devices.

# Example
# -------
#
#   example test bench (testbench.py)

#! /bin/env python

# import any modules/libraries/packages & etc
import sys
# ...

# code here
# --------------------------------------------------------------------------
#
#   develop your test case, library apis & etc here.

# ...

# main block to execute the above test code
# --------------------------------------------------------------------------
if __name__ == '__main__':

    # setup test bench
    # ----------------
    #   setup your work environment, eg:
    #       - load yaml
    #       - connect to devices
    #       - require necessary tcl packages
    #       - etc

    # ...

    # go interactive
    # --------------
    #   note that there's a few ways to go into interactive:
    #       - using IPython
    #       - using pdb.set_trace()
    #       - using code.interact()

    # uncomment/pick one of these:
    from IPython import embed; embed()
    # import code; code.interact(local = locals())
    # import pdb; pdb.set_trace()
    # import ipdb; ipdb.set_trace()

    # cleanup work bench
    # ------------------
    #   do any necessary cleaning, such as:
    #       - remove lingering configurations
    #       - disconnecting from tb devices
    #       - etc

    # ...

    # exit script
    # -----------
    sys.exit()


    # any code from here onwards is just for your copy/paste purposes
    # ---------------------------------------------------------------
    #   use this space for anything you'd do to copy/paste into your
    #   interactive prompt.

    # ...

When run, the above test bench code stops after setup and present an interactive prompt (assuming working with testbench.py). From there onwards, you can code in the editor, and copy/paste code into the interactive shell and run it to get quick results.

bash$ python testbench.py

Python 3.8.2 (default, Apr  8 2020, 11:06:18)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.13.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: sys
Out[1]: <module 'sys' (built-in)>

In [2]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:
:def my_test_proc():
:    print('this is my test proc working')
:
:--

In [3]: my_test_proc()
this is my test proc working

In [4]:

Note

using IPython (http://ipython.org/) as an example. IPython is available on PyPI and is the most popular interactive python shell.

Tip

IPython %cpaste functionality copes with copy/paste much better than the built-in interactive shell.

Test Bench Example

Here’s a example of writing and executing an aetest based testcase on the fly while maintaing a live device connection without disconnecting.

# Example
# -------
#
#   aetest test bench with device connections

# import what's necessary
import sys
from pyats import aetest, topology
from genie.utils import Dq

# write a testcase here
# --------------------------------------------------------------------------

class Testcase(aetest.Testcase):
    '''
    a testcase written in a test bench environment
    '''

    @aetest.setup
    def setup(self, uut, intf = None):

        if intf is None:
            # default to picking the first available interface
            intf = next(iter(uut.interfaces.values()))

        self.intf = intf

    @aetest.test
    def check_version_string_has_iosxe(self, uut):
        version = uut.execute('show version')
        assert 'IOS XE' in version

    @aetest.test
    def interface_can_be_configured(self, uut):
        # interface is assumed down, configure it
        uut.configure('''
            interface %s
                ip address 1.1.1.1 255.255.255.0
                no shutdown
        ''' % self.intf.name)

        # get the interface information from router_show
        result = uut.parse('show interfaces %s' % self.intf.name)

        # check that the status is reflected
        assert Dq(result).get_values('enabled') == [True]
        assert Dq(result).get_values('ip') == ['1.1.1.1']

    @aetest.test
    def hostname_can_be_changed(self, uut):
        # configure hostname
        uut.configure('hostname %s' % uut.name)

        config = uut.execute('show running-config | include hostname')

        assert uut.name in config


# run test code
# --------------------------------------------------------------------------
if __name__ == '__main__':

    # load testbed from yaml file &
    # grab a device by its alias (uut) for testing
    testbed = topology.loader.load('/path/to/testbed.yaml')
    uut = testbed.devices['uut']

    # connect to the device
    uut.connect()

    # go interactive, do whatever
    from IPython import embed; embed()

    # exit
    sys.exit()

    # copy paste code from here onwards..
    # -----------------------------------

    # create a testcase instance from the above code
    tc = Testcase()

    # run the testcase by calling it and providing it with
    # necessary parameters (in this case uut)
    # the result is printed to screen
    tc(uut = uut)

    # --------------------- BACKUP CODE ---------------------

    # Manually Creating Objects
    # -------------------------
    # note that alternatively, instead of loading from YAML, you may also
    # elect to create device and interface objects manually. this avoids the
    # usage of a testbed input file.

    # create a uut device
    uut = topology.Device(name = 'iol',
                          type = 'iol',
                          connections = {
                             'a': {'protocol': 'telnet',
                                               'ip': '1.2.3.4',
                                               'port': 10000}
                          },
                          credentials = {
                              'default' : {'username': 'admin',
                                           'password': 'adminpw'},
                              'enable' : {'password': 'lab'},
                          })

    # add an interface to it
    uut.add_interface(topology.Interface(name = 'Ethernet1/1',
                                         type = 'Ethernet'))

Here’s what the shortest YAML testbed file would look like (with a single interface):

# Example
# -------
#
#   testbench YAML testbed file

devices:
    device_name:                            # use actual device name here
        type: "device_type"
        alias: "alias of device"            # optional
        connections:
          a:                                # define a single connection
            protocol: "telnet"
            ip: 1.1.1.1
            port: 8888

topology:
    device_name:                            # use actual device name here
        interfaces:
            Ethernet1/1:                    # define a single interface
                alias: "test_intf"          # optional
                type: "ethernet"

The execution of the above code looks like this:

(pyats) [tony@jarvis pyats]$ python testbench.py

... initial connection output ...

Python 3.8.2 (default, Apr  8 2020, 11:06:18)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.13.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: tc = Testcase()

In [2]: tc()

... output of the actual testing ...

Out[2]: Passed

In [3]:

And … the testcase passed.

Notice that within this bench environment, you can simply instantiate a Testcase object tc = Testcase(), and run it by calling that instance tc() and passing it any testcase parameters it requires. This, combined with %cpaste copy/pasting functionality, enables users to get immediate feedback of the code (in this case, a Testcase) that’s just been written, by simply treating them as callable class instances.

Tip

this is a method of development. the concept also extends to other test script sections, as well as the development of libraries & packages.

Tip

use the python debugger (pdb) when encountering issues.

Tip

use Ctrl-D to exit