[Day #88 PyATS Series] Write Unit Tests for Your pyATS Libraries (Pytest) Using pyATS for Cisco [Python for Network Engineer]

[Day #88 PyATS Series] Write Unit Tests for Your pyATS Libraries (Pytest) Using pyATS for Cisco [Python for Network Engineer]


Introduction on the Key Points

Welcome to Day #87 of our 101 Days of pyATS (Vendor-Agnostic) series, where we focus on elevating your network automation workflows into production-grade frameworks. In this Article, we will deep-dive into writing unit tests for pyATS libraries using Pytest, an essential skill for every Python for Network Engineer aiming to build robust, maintainable automation solutions.

Unit testing ensures that every function in your pyATS library behaves exactly as expected, delivering confidence in your automation workflows. We will focus on realistic, production-ready examples, emphasizing both CLI and GUI validation.

You’ll learn to:

  • Structure pyATS libraries for testability.
  • Use Pytest for efficient unit test automation.
  • Validate mock network interactions.
  • Create repeatable, scalable test cases.
  • Build a strong test-driven automation pipeline.

By the end of this Article, you’ll have a complete, validated workflow for developing reliable automation libraries, ready to integrate into CI/CD pipelines and production environments.


Topology Overview

Although unit tests focus on isolated code behavior, it helps to visualize a simplified network topology to understand the data flow context.

  • The unit tests simulate interactions with multi-vendor devices.
  • Real CLI commands and responses are replaced by mocks for reproducibility.
  • The goal: Ensure that our custom library methods return correct parsed data, handle exceptions gracefully, and interact with devices as expected.

Topology & Communications

We focus purely on simulated interactions:

  • No physical device connection is made during unit tests.
  • Device methods are mocked using Python’s unittest.mock framework.
  • Example interactions:
    • Sending show version CLI commands.
    • Parsing responses for device serial numbers, uptime, or interfaces.
    • Raising simulated exceptions for negative test cases.

Workflow Script (Library and Unit Test Example)

a) Sample pyATS Library (network_utils.py)

from genie.libs.conf.base import Device

class NetworkUtils:

    def __init__(self, device: Device):
        self.device = device

    def get_serial_number(self):
        try:
            output = self.device.execute('show version | include System serial number')
            serial = output.split(":")[1].strip()
            return serial
        except Exception as e:
            raise RuntimeError(f"Failed to retrieve serial number: {str(e)}")

    def get_interface_status(self):
        try:
            output = self.device.execute('show interfaces status')
            interfaces = []
            for line in output.splitlines():
                if 'connected' in line:
                    interfaces.append(line.split()[0])
            return interfaces
        except Exception as e:
            raise RuntimeError(f"Failed to retrieve interface status: {str(e)}")

b) Unit Test Script (test_network_utils.py)

import pytest
from unittest.mock import MagicMock
from network_utils import NetworkUtils

@pytest.fixture
def mock_device():
    mock = MagicMock()
    return mock

def test_get_serial_number_success(mock_device):
    mock_device.execute.return_value = "System serial number: CISCO12345"
    utils = NetworkUtils(mock_device)
    serial = utils.get_serial_number()
    assert serial == "CISCO12345"
    mock_device.execute.assert_called_with('show version | include System serial number')

def test_get_serial_number_failure(mock_device):
    mock_device.execute.side_effect = Exception("Connection Error")
    utils = NetworkUtils(mock_device)

    with pytest.raises(RuntimeError) as excinfo:
        utils.get_serial_number()
    assert "Failed to retrieve serial number" in str(excinfo.value)

def test_get_interface_status_success(mock_device):
    mock_device.execute.return_value = "Gi1/0/1 connected\nGi1/0/2 notconnect\nGi1/0/3 connected"
    utils = NetworkUtils(mock_device)
    interfaces = utils.get_interface_status()
    assert interfaces == ['Gi1/0/1', 'Gi1/0/3']
    mock_device.execute.assert_called_with('show interfaces status')

def test_get_interface_status_failure(mock_device):
    mock_device.execute.side_effect = Exception("CLI Error")
    utils = NetworkUtils(mock_device)

    with pytest.raises(RuntimeError) as excinfo:
        utils.get_interface_status()
    assert "Failed to retrieve interface status" in str(excinfo.value)

Explanation by Line

a) Library Code Breakdown

  • get_serial_number():
    • Executes show version command.
    • Parses serial number from output.
    • Raises RuntimeError if something goes wrong.
  • get_interface_status():
    • Executes show interfaces status.
    • Parses and returns a list of connected interfaces.

b) Unit Test Breakdown

  • Fixtures:
    • mock_device() provides a reusable mock object.
  • test_get_serial_number_success():
    • Mocks successful CLI output.
    • Asserts correct serial is returned.
  • test_get_serial_number_failure():
    • Mocks CLI failure.
    • Asserts exception is raised.
  • test_get_interface_status_success():
    • Mocks a typical interfaces output.
    • Asserts correct interfaces are returned.
  • test_get_interface_status_failure():
    • Mocks CLI failure.
    • Asserts proper exception handling.

testbed.yml Example

testbed:
  name: unit_test_testbed
  credentials:
    default:
      username: admin
      password: Cisco123

devices:
  cisco_test_device:
    os: iosxe
    type: router
    connections:
      cli:
        protocol: ssh
        ip: 10.1.1.100

Although unit tests run in isolation, having a testbed helps define expected devices and structure.


Post-validation CLI (Simulated Expected Output)

$ pytest test_network_utils.py

=========================== test session starts ============================
platform linux -- Python 3.8.10, pytest-7.1.2
collected 4 items

test_network_utils.py ....                                         [100%]

============================ 4 passed in 0.85s =============================

Example Output of get_serial_number():

System serial number: CISCO12345

Example Output of get_interface_status():

Gi1/0/1
Gi1/0/3

FAQs

Q1. Why should network engineers write unit tests for pyATS libraries?
A1. Unit tests ensure that individual functions or modules in your pyATS libraries behave as expected. They help catch errors early, improve code reliability, and provide confidence when making changes or refactoring automation scripts.


Q2. How does Pytest integrate with pyATS?
A2. Pytest is a Python testing framework that can run pyATS library functions as unit tests. By importing pyATS modules into Pytest test cases, you can validate outputs, handle exceptions, and ensure your automation logic works as intended.


Q3. What types of functions should be unit-tested in pyATS libraries?
A3. Functions that interact with device data, parse CLI output, perform validation checks, or handle configuration changes should be unit-tested. This includes both utility functions and workflow-level functions that are reused across scripts.


Q4. How do you mock network devices in unit tests?
A4. You can use mocking libraries like unittest.mock to simulate device responses. This allows you to test parsing, validation, and business logic without needing live devices, making testing faster and safer.


Q5. Can unit tests verify multi-vendor compatibility?
A5. Yes. By creating mocked responses for different vendor OS outputs (Cisco IOS, Arista EOS, Juniper Junos), you can ensure that your pyATS library handles all supported platforms consistently.


Q6. How are test results reported?
A6. Pytest provides detailed reports showing passed, failed, or skipped tests. Combined with structured logging, it can highlight which function or module failed and why, facilitating quick debugging.


Q7. How do unit tests fit into CI/CD pipelines for network automation?
A7. Unit tests can be run automatically whenever changes are made to pyATS libraries. This ensures that updates do not break existing workflows and supports production-ready, reliable network automation deployments.


YouTube Link

Watch the Complete Python for Network Engineer: Write Unit Tests for Your pyATS Libraries (Pytest) Using pyATS for Cisco [Python for Network Engineer] Lab Demo & Explanation on our channel:

Master Python Network Automation, Ansible, REST API & Cisco DevNet
Master Python Network Automation, Ansible, REST API & Cisco DevNet
Master Python Network Automation, Ansible, REST API & Cisco DevNet
Why Robot Framework for Network Automation?

Join Our Training

You now have a full production-ready framework to write robust unit tests for your pyATS automation libraries using Pytest. This approach empowers you to build reliable, scalable, and maintainable automation solutions.

Elevate your career as a Python for Network Engineer by mastering automated testing, network programmability, and advanced Python scripting.

Join Trainer Sagar Dhawan’s 3-Month Instructor-Led Training Program for hands-on guidance, real-world use cases, and career-focused skills development.

Enroll now:
https://course.networkjourney.com/python-ansible-api-cisco-devnet-for-network-engineers/

Enroll Now & Future‑Proof Your Career
Emailinfo@networkjourney.com
WhatsApp / Call: +91 97395 21088