Testing SNMP Automation Pipelines with pytest and ezsnmp¶
Note
Published: October 30, 2026
Authors: Written by Gemini 3.8 Flash, Co-Authored & Reviewed by Carlos Santos (@carlkidcrypto)
Estimated Reading Time: 5 minutes
Tags: Python • Snmp • Pytest • Testing • Ci-Cd • Ezsnmp • Test-Automation
The Dilemma of Testing Network Management Code¶
Developing network automation software—such as custom telemetry daemons, exporters, and configuration crawlers—presents a fundamental testing challenge:
How do you unit and integration test SNMP code in ephemeral CI/CD pipelines without physical network switches and routers?
Traditionally, developers resort to two extremes:
Heavy Mocking (``unittest.mock``): Mocking Python session methods tests only application logic while completely bypassing Net-SNMP C bindings, MIB resolution, ASN.1 type conversion, and network socket behavior.
Dedicated Lab Hardware: Running tests against real switches in physical staging racks introduces hardware dependencies, flakiness, and concurrency bottlenecks across CI runner queues.
The modern solution is hermetic integration testing: combining pytest
fixtures with local Net-SNMP daemon instances (snmpd) or lightweight
simulators.
ezsnmp provides sub-millisecond execution and thread-safe session lifecycles, enabling test suites to run hundreds of real UDP queries against local test daemons in seconds.
Hermetic Architecture: Sandboxed Local Daemons¶
Rather than mocking network calls, a hermetic test suite spins up a sandboxed
snmpd process bound to a non-privileged UDP port on 127.0.0.1.
Key advantages:
Real C-Binding Execution: Validates actual Net-SNMP C library behavior, including MIB parsing, type translation, and PDU serialization.
Deterministic Fixtures: Provides predictable OID trees that run identically on local developer laptops and cloud CI runners (GitHub Actions).
Fault Injection: Allows intentional simulation of packet drops, timeouts, bad community strings, and malformed ASN.1 structures.
Practical Implementation: Pytest Fixture & Test Suite¶
1. The Daemon Pytest Fixture (conftest.py)¶
The following fixture creates a temporary, self-contained snmpd.conf,
starts an unprivileged local Net-SNMP daemon on port 11161, and tears it
down after the test session:
import os
import subprocess
import tempfile
import time
import pytest
from ezsnmp import Session
TEST_PORT = 11161
COMMUNITY = "test_community"
@pytest.fixture(scope="session")
def snmp_daemon():
"""Launch a sandboxed local snmpd daemon for integration testing."""
with tempfile.TemporaryDirectory() as tmp_dir:
conf_path = os.path.join(tmp_dir, "snmpd.conf")
pid_path = os.path.join(tmp_dir, "snmpd.pid")
# Minimal test configuration with custom system metadata
with open(conf_path, "w") as f:
f.write(f"""
rocommunity {COMMUNITY} 127.0.0.1
sysLocation TestLab-Rack42
sysContact ci-runner@example.internal
sysName switch-test-01
""")
# Start local snmpd as an unprivileged process
cmd = [
"snmpd",
"-f", # Foreground
"-r", # Do not require root
"-C", # Read only specified config
"-c", conf_path,
"-p", pid_path,
f"udp:127.0.0.1:{TEST_PORT}"
]
proc = subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
time.sleep(0.5) # Allow socket initialization
yield {"port": TEST_PORT, "community": COMMUNITY}
proc.terminate()
proc.wait()
2. Testing Client Operations (test_telemetry.py)¶
With the daemon fixture running, write clean, deterministic tests verifying scalar queries, walks, and error handling:
import pytest
from ezsnmp import Session
from ezsnmp.exceptions import TimeoutError
def test_snmp_get_scalar(snmp_daemon):
"""Verify successful retrieval of scalar system metadata."""
session = Session(
hostname=f"127.0.0.1:{snmp_daemon['port']}",
community=snmp_daemon["community"],
version=2,
timeout=1,
retries=1
)
sys_location = session.get("sysLocation.0")[0]
assert sys_location.value == "TestLab-Rack42"
assert sys_location.type == "STRING"
def test_snmp_batch_get(snmp_daemon):
"""Verify multi-OID batch queries execute cleanly."""
session = Session(
hostname=f"127.0.0.1:{snmp_daemon['port']}",
community=snmp_daemon["community"],
version=2
)
results = session.get(["sysLocation.0", "sysContact.0", "sysName.0"])
# Strip potential MIB module qualifiers (e.g. SNMPv2-MIB::sysLocation)
val_map = {v.oid.split("::")[-1]: v.value for v in results}
assert val_map["sysLocation"] == "TestLab-Rack42"
assert val_map["sysContact"] == "ci-runner@example.internal"
assert val_map["sysName"] == "switch-test-01"
def test_snmp_timeout_fault_injection():
"""Verify client raises TimeoutError on silent drops."""
# Query an unreachable blackhole address
session = Session(
hostname="192.0.2.1:161", # RFC 5737 TEST-NET address
community="public",
version=2,
timeout=1,
retries=1
)
with pytest.raises(TimeoutError):
session.get("sysDescr.0")
CI/CD Integration: GitHub Actions Workflow¶
Run your hermetic test suite automatically in GitHub Actions on every pull request across multiple Python versions:
name: CI Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Install Net-SNMP Tools, Headers & MIBs
run: |
sudo apt-get update
sudo apt-get install -y snmp snmpd libsnmp-dev snmp-mibs-downloader
sudo download-mibs
sudo sed -i 's/^mibs :/#mibs :/g' /etc/snmp/snmp.conf
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
pip install --upgrade pip
pip install pytest pytest-timeout ezsnmp
- name: Execute Pytest Suite
run: pytest --timeout=30
Production Best Practices¶
Enforce Test Timeouts (pytest-timeout): Because UDP is connectionless, dropped packets in misconfigured CI environments can stall runners. Always include
pytest --timeout=30to fail fast.Avoid Root Privileges: Net-SNMP’s
snmpd -rflag permits running the daemon as an unprivileged local user, ensuring safe execution in locked-down containerized CI agents.Matrix Test SNMPv3 Credentials: Include dedicated test cases validating USM security levels (
authNoPrivandauthPriv) against test users defined in the sandboxedsnmpd.conf.
Migration Notes: Easy SNMP vs ezsnmp¶
Socket & Memory Cleanup in Loops: Test suites instantiate hundreds of ephemeral
Sessionobjects. Legacyeasysnmpleaked C file descriptors and memory during repeated session cycles, causing test runners to crash onEMFILE: Too many open files.RAII Cleanliness in ezsnmp: Modern C++17 RAII wrappers ensure Net-SNMP sockets and session memory are reclaimed immediately when Python session objects go out of scope, guaranteeing robust, leak-free CI test runs.
Useful Links & Community¶
Source Repository: carlkidcrypto/ezsnmp on GitHub
PyPI Distribution: ezsnmp Package on PyPI
Documentation: ezsnmp Online Documentation
Blog & Updates: Carlos Santos Blog Updates
Warning
AI-generated content may contain misinformation or mistakes. A human does review this, but we are only human.