AEtest loop.mark is a feature of AEtest in pyATS used to mark a testcase or test section for iteration. It is often applied with different parameters and is particularly useful when you want to run the same test logic multiple times with different input values.

AEtest loop.mark Fundamental

To help you easily understand how aetest.loop.mark works, let’s go through a simple example. Suppose you have a test that needs to ping multiple IP addresses. In this case, you can write a single test subsection that pings a single IP address, and then use loop.mark() to run that same subsection multiple times with different IPs.

The aetest.loop.mark() function takes two parameters:
1. The name of the test case or test subsection to loop over.
2. A list of input values to iterate through.

AEtest loop.mark
AEtest loop.mark
from pyats import aetest

class CommonSetupSection(aetest.CommonSetup):

    @aetest.subsection
    def mark_tests(self):
        aetest.loop.mark(PingTest.ping, ip=['8.8.8.8', '1.1.1.1'])

class PingTest(aetest.Testcase):

    @aetest.test
    def ping(self, ip):
        print(f"Pinging {ip}...")

if __name__ == '__main__':
    aetest.main()
...
2025-05-02T13:18:48: %AETEST-INFO: |                      Starting section ping[ip=8.8.8.8]                       |
2025-05-02T13:18:48: %AETEST-INFO: +------------------------------------------------------------------------------+
Pinging 8.8.8.8...
2025-05-02T13:18:48: %AETEST-INFO: The result of section ping[ip=8.8.8.8] is => PASSED
2025-05-02T13:18:48: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-02T13:18:48: %AETEST-INFO: |                      Starting section ping[ip=1.1.1.1]                       |
2025-05-02T13:18:48: %AETEST-INFO: +------------------------------------------------------------------------------+
Pinging 1.1.1.1...
2025-05-02T13:18:48: %AETEST-INFO: The result of section ping[ip=1.1.1.1] is => PASSED
2025-05-02T13:18:48: %AETEST-INFO: The result of testcase PingTest is => PASSED
...
2025-05-02T13:18:48: %AETEST-INFO: |-- common_setup                                                          PASSED
2025-05-02T13:18:48: %AETEST-INFO: |   `-- mark_tests                                                        PASSED
2025-05-02T13:18:48: %AETEST-INFO: `-- PingTest                                                              PASSED
2025-05-02T13:18:48: %AETEST-INFO:     |-- ping[ip=8.8.8.8]                                                  PASSED
2025-05-02T13:18:48: %AETEST-INFO:     `-- ping[ip=1.1.1.1]                                                  PASSED
...

aetest loop.mark to run a testcase over multiple devices

One common application of aetest.loop.mark in network automation is to run a test case against a list of devices. In other words, the input value for loop.mark is a list of devices on which we want to execute a specific test case.

In this script, within the CommonSetup section, we collect the devices defined in the testbed into a list variable named devices. This list can be customized based on various criteria such as device type, role, or operating system. The list is then passed to aetest.loop.mark to iterate over each device and execute the DeviceTest test case.

In the test case itself, we simply print the parsed output of the show ip interface brief command. However, this can easily be extended to perform additional checks—such as verifying if interfaces are up, whether they have IP addresses assigned, or running any other relevant validations.

from pyats import aetest
from pyats.topology import loader

testbed = loader.load('testbed.yaml')

class CommonSetup(aetest.CommonSetup):
    @aetest.subsection
    def load_testbed(self):
        devices = [device for device in testbed.devices]
        aetest.loop.mark(DeviceTest, device=devices)

class DeviceTest(aetest.Testcase):
    @aetest.test
    def test_device(self, device):
        device = testbed.devices[device]
        device.connect(log_stdout=False)
        print(f"{device} interfaces ...",device.parse("show ip interface brief"))
        device.disconnect()

if __name__ == '__main__':
    aetest.main()

When we run the script, we observe that the test case is executed sequentially for each device. This can be time-consuming, especially when dealing with a long list of devices. For each device, the parsed output of the show
ip interface brief
command is generated and printed in the test output.

(majid) majid@majid-ubuntu:~/devnet/pyats$ python 6.2.3.aetest_iterate_devices_loop.mark.py
2025-05-03T12:15:50: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:15:50: %AETEST-INFO: |                            Starting common setup                             |
2025-05-03T12:15:50: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:15:50: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:15:50: %AETEST-INFO: |                       Starting subsection load_testbed                       |
2025-05-03T12:15:50: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:15:50: %AETEST-INFO: The result of subsection load_testbed is => PASSED
2025-05-03T12:15:50: %AETEST-INFO: The result of common setup is => PASSED
2025-05-03T12:15:50: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:15:50: %AETEST-INFO: |                   Starting testcase DeviceTest[device=R1]                    |
2025-05-03T12:15:50: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:15:50: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:15:50: %AETEST-INFO: |                         Starting section test_device                         |
2025-05-03T12:15:50: %AETEST-INFO: +------------------------------------------------------------------------------+
Device R1, type ios interfaces ... {'interface': {'GigabitEthernet1': {'ip_address': '10.13.14.16', 'interface_is_ok': 'YES', 'method': 'NVRAM', 'status': 'up', 'protocol': 'up'}, 'GigabitEthernet2': {'ip_address': '10.13.15.16', 'interface_is_ok': 'YES', 'method': 'NVRAM', 'status': 'up', 'protocol': 'up'}, 'GigabitEthernet3': {'ip_address': '7.8.9.11', 'interface_is_ok': 'YES', 'method': 'NVRAM', 'status': 'administratively down', 'protocol': 'down'}, 'Loopback0': {'ip_address': '1.1.1.1', 'interface_is_ok': 'YES', 'method': 'NVRAM', 'status': 'up', 'protocol': 'up'}, 'Loopback10': {'ip_address': '10.10.10.10', 'interface_is_ok': 'YES', 'method': 'NVRAM', 'status': 'up', 'protocol': 'up'}, 'Loopback100': {'ip_address': '192.168.200.200', 'interface_is_ok': 'YES', 'method': 'NVRAM', 'status': 'up', 'protocol': 'up'}}}
2025-05-03T12:16:04: %AETEST-INFO: The result of section test_device is => PASSED
2025-05-03T12:16:04: %AETEST-INFO: The result of testcase DeviceTest[device=R1] is => PASSED
2025-05-03T12:16:04: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:16:04: %AETEST-INFO: |                   Starting testcase DeviceTest[device=R2]                    |
2025-05-03T12:16:04: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:16:04: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:16:04: %AETEST-INFO: |                         Starting section test_device                         |
2025-05-03T12:16:04: %AETEST-INFO: +------------------------------------------------------------------------------+
Device R2, type iosxe interfaces ... {'interface': {'GigabitEthernet1': {'ip_address': '10.13.14.17', 'interface_is_ok': 'YES', 'method': 'manual', 'status': 'up', 'protocol': 'up'}, 'GigabitEthernet2': {'ip_address': '10.13.15.17', 'interface_is_ok': 'YES', 'method': 'manual', 'status': 'up', 'protocol': 'up'}, 'GigabitEthernet3': {'ip_address': '12.12.12.2', 'interface_is_ok': 'YES', 'method': 'manual', 'status': 'administratively down', 'protocol': 'down'}, 'Loopback0': {'ip_address': '2.2.2.2', 'interface_is_ok': 'YES', 'method': 'manual', 'status': 'up', 'protocol': 'up'}, 'Loopback2': {'ip_address': '4.2.2.4', 'interface_is_ok': 'YES', 'method': 'manual', 'status': 'up', 'protocol': 'up'}, 'Loopback3': {'ip_address': '8.8.8.8', 'interface_is_ok': 'YES', 'method': 'manual', 'status': 'up', 'protocol': 'up'}, 'NVI0': {'ip_address': 'unassigned', 'interface_is_ok': 'YES', 'method': 'unset', 'status': 'up', 'protocol': 'up'}}}
2025-05-03T12:16:17: %AETEST-INFO: The result of section test_device is => PASSED
2025-05-03T12:16:17: %AETEST-INFO: The result of testcase DeviceTest[device=R2] is => PASSED
2025-05-03T12:16:17: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:16:17: %AETEST-INFO: |                               Detailed Results                               |
2025-05-03T12:16:17: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:16:17: %AETEST-INFO:  SECTIONS/TESTCASES                                                      RESULT
2025-05-03T12:16:17: %AETEST-INFO: --------------------------------------------------------------------------------
2025-05-03T12:16:17: %AETEST-INFO: .
2025-05-03T12:16:17: %AETEST-INFO: |-- common_setup                                                          PASSED
2025-05-03T12:16:17: %AETEST-INFO: |   `-- load_testbed                                                      PASSED
2025-05-03T12:16:17: %AETEST-INFO: |-- DeviceTest[device=R1]                                                 PASSED
2025-05-03T12:16:17: %AETEST-INFO: |   `-- test_device                                                       PASSED
2025-05-03T12:16:17: %AETEST-INFO: `-- DeviceTest[device=R2]                                                 PASSED
2025-05-03T12:16:17: %AETEST-INFO:     `-- test_device                                                       PASSED
2025-05-03T12:16:17: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:16:17: %AETEST-INFO: |                                   Summary                                    |
2025-05-03T12:16:17: %AETEST-INFO: +------------------------------------------------------------------------------+
2025-05-03T12:16:17: %AETEST-INFO:  Number of ABORTED                                                            0
2025-05-03T12:16:17: %AETEST-INFO:  Number of BLOCKED                                                            0
2025-05-03T12:16:17: %AETEST-INFO:  Number of ERRORED                                                            0
2025-05-03T12:16:17: %AETEST-INFO:  Number of FAILED                                                             0
2025-05-03T12:16:17: %AETEST-INFO:  Number of PASSED                                                             3
2025-05-03T12:16:17: %AETEST-INFO:  Number of PASSX                                                              0
2025-05-03T12:16:17: %AETEST-INFO:  Number of SKIPPED                                                            0
2025-05-03T12:16:17: %AETEST-INFO:  Total Number                                                                 3
2025-05-03T12:16:17: %AETEST-INFO:  Success Rate                                                            100.0%
2025-05-03T12:16:17: %AETEST-INFO: --------------------------------------------------------------------------------
Back to: Network Automation with pyATS & Genie (in Progress) > Automating Network Testcases with AEtest

Leave a Reply

Your email address will not be published. Required fields are marked *


Post comment