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.
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
command is generated and printed in the test output.
ip interface brief
(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: --------------------------------------------------------------------------------